Skip to content

Commit 42011b3

Browse files
committed
md5 password hash impl
1 parent c53b11f commit 42011b3

File tree

3 files changed

+54
-13
lines changed

3 files changed

+54
-13
lines changed

Package.swift

+4-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ let package = Package(
1212

1313
// Core extensions, type-aliases, and functions that facilitate common tasks.
1414
.package(url: "https://github.com/vapor/core.git", .branch("beta")),
15+
16+
// Cryptography modules (formerly CryptoKitten)
17+
.package(url: "https://github.com/vapor/crypto.git", .branch("beta")),
1518

1619
// Core services for creating database integrations.
1720
.package(url: "https://github.com/vapor/database-kit.git", .branch("beta")),
@@ -26,7 +29,7 @@ let package = Package(
2629
.package(url: "https://github.com/vapor/sockets.git", .branch("beta")),
2730
],
2831
targets: [
29-
.target(name: "PostgreSQL", dependencies: ["Async", "Bits", "DatabaseKit", "Service", "TCP"]),
32+
.target(name: "PostgreSQL", dependencies: ["Async", "Bits", "Crypto", "DatabaseKit", "Service", "TCP"]),
3033
.testTarget(name: "PostgreSQLTests", dependencies: ["PostgreSQL"]),
3134
]
3235
)

Sources/PostgreSQL/Connection/PostgreSQLConnection.swift

+49-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Async
2+
import Crypto
23

34
/// A PostgreSQL frontend client.
45
public final class PostgreSQLConnection {
@@ -52,7 +53,7 @@ public final class PostgreSQLConnection {
5253
}
5354

5455
/// Authenticates the `PostgreSQLClient` using a username with no password.
55-
public func authenticate(username: String, database: String? = nil) -> Future<Void> {
56+
public func authenticate(username: String, database: String? = nil, password: String? = nil) -> Future<Void> {
5657
let startup = PostgreSQLStartupMessage.versionThree(parameters: [
5758
"user": username,
5859
"database": database ?? username
@@ -70,18 +71,55 @@ public final class PostgreSQLConnection {
7071
throw PostgreSQLError(identifier: "authRequest", reason: "No authorization request / status sent.")
7172
}
7273

74+
let input: [PostgreSQLMessage]
7375
switch auth {
74-
case .ok: return .done
75-
case .plaintext: throw PostgreSQLError(identifier: "plaintext", reason: "Plaintext password not supported. Use MD5.")
76+
case .ok:
77+
guard password == nil else {
78+
throw PostgreSQLError(identifier: "trust", reason: "No password is required")
79+
}
80+
input = []
81+
case .plaintext:
82+
guard let password = password else {
83+
throw PostgreSQLError(identifier: "password", reason: "Password is required")
84+
}
85+
let passwordMessage = PostgreSQLPasswordMessage(password: password)
86+
input = [.password(passwordMessage)]
7687
case .md5(let salt):
77-
/// FIXME: hash password
78-
let password = PostgreSQLPasswordMessage(password: "123")
79-
return self.queueStream.enqueue([.password(password)]) { message in
80-
switch message {
81-
case .error(let error):
82-
throw error
83-
default: return true
84-
}
88+
guard let password = password else {
89+
throw PostgreSQLError(identifier: "password", reason: "Password is required")
90+
}
91+
guard let passwordData = password.data(using: .utf8) else {
92+
throw PostgreSQLError(identifier: "passwordUTF8", reason: "Could not convert password to UTF-8 encoded Data.")
93+
}
94+
95+
guard let usernameData = username.data(using: .utf8) else {
96+
throw PostgreSQLError(identifier: "usernameUTF8", reason: "Could not convert username to UTF-8 encoded Data.")
97+
}
98+
99+
let hasher = MD5()
100+
// pwdhash = md5(password + username).hexdigest()
101+
var passwordUsernameData = passwordData + usernameData
102+
hasher.update(sequence: &passwordUsernameData)
103+
hasher.finalize()
104+
guard let pwdhash = hasher.hash.hexString.data(using: .utf8) else {
105+
throw PostgreSQLError(identifier: "hashUTF8", reason: "Could not convert password hash to UTF-8 encoded Data.")
106+
}
107+
hasher.reset()
108+
// hash = ′ md 5′ + md 5(pwdhash + salt ).hexdigest ()
109+
var saltedData = pwdhash + salt
110+
hasher.update(sequence: &saltedData)
111+
hasher.finalize()
112+
let passwordMessage = PostgreSQLPasswordMessage(password: "md5" + hasher.hash.hexString)
113+
input = [.password(passwordMessage)]
114+
}
115+
116+
return self.queueStream.enqueue(input) { message in
117+
switch message {
118+
case .error(let error): throw error
119+
case .readyForQuery: return true
120+
case .authenticationRequest: return false
121+
case .parameterStatus, .backendKeyData: return false
122+
default: throw PostgreSQLError(identifier: "authenticationMessage", reason: "Unexpected authentication message: \(message)")
85123
}
86124
}
87125
}

Tests/PostgreSQLTests/PostgreSQLConnectionTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ extension PostgreSQLConnection {
285285
static func makeTest() throws -> (PostgreSQLConnection, EventLoop) {
286286
let eventLoop = try DefaultEventLoop(label: "codes.vapor.postgresql.client.test")
287287
let client = try PostgreSQLConnection.connect(on: eventLoop)
288-
_ = try client.authenticate(username: "secure", database: "tanner").await(on: eventLoop)
288+
_ = try client.authenticate(username: "postgres", database: "postgres").await(on: eventLoop)
289289
return (client, eventLoop)
290290
}
291291
}

0 commit comments

Comments
 (0)