Skip to content

Commit 7b37f4d

Browse files
committed
Mozilla shared secret proof
1 parent 760117d commit 7b37f4d

File tree

4 files changed

+129
-63
lines changed

4 files changed

+129
-63
lines changed

Sources/SRP/client.swift

Lines changed: 56 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ public struct SRPClient<H: HashFunction> {
3939

4040
/// return shared secret given the username, password, B value and salt from the server
4141
/// - Parameters:
42-
/// - username: user identifier
43-
/// - password: password
44-
/// - salt: salt
45-
/// - clientKeys: client public/private keys
46-
/// - serverPublicKey: server public key
42+
/// - username: user identifier (I)
43+
/// - password: password (p)
44+
/// - salt: salt (s)
45+
/// - clientKeys: client public/private keys (A,a)
46+
/// - serverPublicKey: server public key (B)
4747
/// - Throws: `nullServerKey`
4848
/// - Returns: shared secret
4949
public func calculateSharedSecret(username: String, password: String, salt: [UInt8], clientKeys: SRPKeyPair, serverPublicKey: SRPKey) throws -> SRPKey {
@@ -54,10 +54,10 @@ public struct SRPClient<H: HashFunction> {
5454

5555
/// return shared secret given a binary password, B value and salt from the server
5656
/// - Parameters:
57-
/// - password: password
58-
/// - salt: salt
59-
/// - clientKeys: client public/private keys
60-
/// - serverPublicKey: server public key
57+
/// - password: password (p)
58+
/// - salt: salt (s)
59+
/// - clientKeys: client public/private keys (A,a)
60+
/// - serverPublicKey: server public key (B)
6161
/// - Throws: `nullServerKey`
6262
/// - Returns: shared secret
6363
public func calculateSharedSecret(password: [UInt8], salt: [UInt8], clientKeys: SRPKeyPair, serverPublicKey: SRPKey) throws -> SRPKey {
@@ -66,40 +66,56 @@ public struct SRPClient<H: HashFunction> {
6666
return SRPKey(sharedSecret)
6767
}
6868

69-
/// calculate proof of shared secret to send to server
69+
/// calculate proof of shared secret to send to server.
70+
///
71+
/// There doesn't seem to be any agreement on the simple version of the client/server proof.
72+
/// Some versions use K (H(S)) instead of S, some pad all values regardless of whether their
73+
/// source is a hash or a big num. This is a simple version of the client proof as used by
74+
/// the mozilla srp library https://github.com/mozilla/node-srp
75+
///
7076
/// - Parameters:
71-
/// - clientPublicKey: client public key
72-
/// - serverPublicKey: server public key
73-
/// - sharedSecret: shared secret
77+
/// - clientPublicKey: client public key (A)
78+
/// - serverPublicKey: server public key (B)
79+
/// - sharedSecret: shared secret (S)
7480
/// - Returns: The client verification code which should be passed to the server
75-
public func calculateSimpleClientProof(clientPublicKey: SRPKey, serverPublicKey: SRPKey, sharedSecret: SRPKey) -> [UInt8] {
81+
public func calculateMozillaClientProof(clientPublicKey: SRPKey, serverPublicKey: SRPKey, sharedSecret: SRPKey) -> [UInt8] {
7682
// get verification code
77-
return SRP<H>.calculateSimpleClientProof(clientPublicKey: clientPublicKey, serverPublicKey: serverPublicKey, sharedSecret: sharedSecret, padding: configuration.sizeN)
83+
return SRP<H>.calculateMozillaClientProof(clientPublicKey: clientPublicKey, serverPublicKey: serverPublicKey, sharedSecret: sharedSecret, padding: configuration.sizeN)
7884
}
7985

80-
/// If the server returns that the client verification code was valiid it will also return a server verification code that the client can use to verify the server is correct
81-
///
86+
/// If the server returns that the client verification code was valid it will also return
87+
/// a server verification code that the client can use to verify the server is correct
88+
///
89+
/// There doesn't seem to be any agreement on the simple version of the client/server proof.
90+
/// Some versions use K (H(S)) instead of S, some pad all values regardless of whether their
91+
/// source is a hash or a big num. This is a simple version of the server proof as used by
92+
/// the mozilla srp library https://github.com/mozilla/node-srp
93+
///
8294
/// - Parameters:
83-
/// - code: Verification code returned by server
84-
/// - state: Authentication state
95+
/// - serverProof: Proof returned by server (M2)
96+
/// - clientProof: Proof created by client (M1)
97+
/// - clientKeys: client public key (A,a)
98+
/// - sharedSecret: shared secret (S)
8599
/// - Throws: `requiresVerificationKey`, `invalidServerCode`
86-
public func verifySimpleServerProof(serverProof: [UInt8], clientProof: [UInt8], clientKeys: SRPKeyPair, sharedSecret: SRPKey) throws {
100+
public func verifyMozillaServerProof(serverProof: [UInt8], clientProof: [UInt8], clientKeys: SRPKeyPair, sharedSecret: SRPKey) throws {
87101
// get out version of server proof
88-
let HAMS = SRP<H>.calculateSimpleServerVerification(clientPublicKey: clientKeys.public, clientProof: clientProof, sharedSecret: sharedSecret, padding: configuration.sizeN)
102+
let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes(padding: configuration.sizeN)))
103+
let HAMK = SRP<H>.calculateMozillaServerVerification(clientPublicKey: clientKeys.public, clientProof: clientProof, hashSharedSecret: hashSharedSecret, padding: configuration.sizeN)
89104
// is it the same
90-
guard serverProof == HAMS else { throw SRPClientError.invalidServerCode }
105+
guard serverProof == HAMK else { throw SRPClientError.invalidServerCode }
91106
}
92107

93108
/// calculate proof of shared secret to send to server
109+
///
110+
/// This is the client proof detailed in https://www.rfc-editor.org/rfc/rfc2945
94111
/// - Parameters:
95-
/// - username: username
96-
/// - salt: The salt value associated with the user returned by the server
97-
/// - clientPublicKey: client public key
98-
/// - serverPublicKey: server public key
99-
/// - sharedSecret: shared secret
112+
/// - username: username (I)
113+
/// - salt: The salt value associated with the user returned by the server (s)
114+
/// - clientPublicKey: client public key (A)
115+
/// - serverPublicKey: server public key (B)
116+
/// - sharedSecret: shared secret (S)
100117
/// - Returns: The client verification code which should be passed to the server
101118
public func calculateClientProof(username: String, salt: [UInt8], clientPublicKey: SRPKey, serverPublicKey: SRPKey, sharedSecret: SRPKey) -> [UInt8] {
102-
103119
let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes(padding: configuration.sizeN)))
104120

105121
// get verification code
@@ -110,24 +126,26 @@ public struct SRPClient<H: HashFunction> {
110126
/// verification code that the client can use to verify the server is correct. This is the calculation
111127
/// to verify it is correct
112128
///
129+
/// This is the server proof detailed in https://www.rfc-editor.org/rfc/rfc2945
113130
/// - Parameters:
114-
/// - clientPublicKey: Client public key
115-
/// - clientProof: Client proof
116-
/// - sharedSecret: Shared secret
131+
/// - clientPublicKey: Client public key (A)
132+
/// - clientProof: Client proof (M1)
133+
/// - sharedSecret: Shared secret (M2)
117134
public func calculateServerProof(clientPublicKey: SRPKey, clientProof: [UInt8], sharedSecret: SRPKey) -> [UInt8] {
118135
let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes(padding: configuration.sizeN)))
119-
// get out version of server proof
136+
// get our version of server proof
120137
return SRP<H>.calculateServerVerification(clientPublicKey: clientPublicKey, clientProof: clientProof, hashSharedSecret: hashSharedSecret, padding: configuration.sizeN)
121138
}
122139

123140
/// If the server returns that the client verification code was valid it will also return a server
124141
/// verification code that the client can use to verify the server is correct
125142
///
143+
/// This is the client/server proof detailed in https://www.rfc-editor.org/rfc/rfc2945
126144
/// - Parameters:
127-
/// - clientProof: Server proof
128-
/// - clientProof: Client proof
129-
/// - clientKeys: Client keys
130-
/// - sharedSecret: Shared secret
145+
/// - clientProof: Server proof (M2)
146+
/// - clientProof: Client proof (M1)
147+
/// - clientKeys: Client keys (A,a)
148+
/// - sharedSecret: Shared secret (S)
131149
/// - Throws: `requiresVerificationKey`, `invalidServerCode`
132150
public func verifyServerProof(serverProof: [UInt8], clientProof: [UInt8], clientKeys: SRPKeyPair, sharedSecret: SRPKey) throws {
133151
// get our version of server proof
@@ -141,8 +159,8 @@ public struct SRPClient<H: HashFunction> {
141159
/// server never knows your password so can never leak it.
142160
///
143161
/// - Parameters:
144-
/// - username: username
145-
/// - password: user password
162+
/// - username: username (I)
163+
/// - password: user password (p)
146164
/// - Returns: tuple containing salt and password verifier
147165
public func generateSaltAndVerifier(username: String, password: String) -> (salt: [UInt8], verifier: SRPKey) {
148166
let salt = [UInt8].random(count: 16)

Sources/SRP/server.swift

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ public struct SRPServer<H: HashFunction> {
4747

4848
/// calculate the shared secret
4949
/// - Parameters:
50-
/// - clientPublicKey: public key received from client
51-
/// - serverKeys: server key pair
52-
/// - verifier: password verifier
50+
/// - clientPublicKey: public key received from client (A)
51+
/// - serverKeys: server key pair (B,b)
52+
/// - verifier: password verifier (v)
5353
/// - Returns: shared secret
5454
public func calculateSharedSecret(clientPublicKey: SRPKey, serverKeys: SRPKeyPair, verifier: SRPKey) throws -> SRPKey {
5555
guard clientPublicKey.number % configuration.N != BigNum(0) else { throw SRPServerError.nullClientKey }
@@ -63,35 +63,44 @@ public struct SRPServer<H: HashFunction> {
6363
return SRPKey(S)
6464
}
6565

66-
/// verify proof that client has shared secret and return a server verification proof. If verification fails a `invalidClientCode` error is thrown
66+
/// verify proof that client has shared secret and return a server verification proof. If verification
67+
/// fails a `invalidClientCode` error is thrown
68+
///
69+
/// There doesn't seem to be any agreement on the simple version of the client/server proof.
70+
/// Some versions use K (H(S)) instead of S, some pad all values regardless of whether their
71+
/// source is a hash or a big num. This is a simple version of the client/server proof as used by
72+
/// the mozilla srp library https://github.com/mozilla/node-srp
6773
///
6874
/// - Parameters:
69-
/// - proof: Client proof
70-
/// - clientPublicKey: Client public key
71-
/// - serverPublicKey: Server public key
72-
/// - sharedSecret: Shared secret
75+
/// - proof: Client proof (M1)
76+
/// - clientPublicKey: Client public key (A)
77+
/// - serverPublicKey: Server public key (B)
78+
/// - sharedSecret: Shared secret (S)
7379
/// - Throws: invalidClientCode
7480
/// - Returns: The server verification code
75-
public func verifySimpleClientProof(proof: [UInt8], clientPublicKey: SRPKey, serverPublicKey: SRPKey, sharedSecret: SRPKey) throws -> [UInt8] {
76-
let clientProof = SRP<H>.calculateSimpleClientProof(
81+
public func verifyMozillaClientProof(proof: [UInt8], clientPublicKey: SRPKey, serverPublicKey: SRPKey, sharedSecret: SRPKey) throws -> [UInt8] {
82+
let clientProof = SRP<H>.calculateMozillaClientProof(
7783
clientPublicKey: clientPublicKey,
7884
serverPublicKey: serverPublicKey,
7985
sharedSecret: sharedSecret,
8086
padding: configuration.sizeN
8187
)
8288
guard clientProof == proof else { throw SRPServerError.invalidClientProof }
83-
return SRP<H>.calculateSimpleServerVerification(clientPublicKey: clientPublicKey, clientProof: clientProof, sharedSecret: sharedSecret, padding: configuration.sizeN)
89+
let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes(padding: configuration.sizeN)))
90+
return SRP<H>.calculateMozillaServerVerification(clientPublicKey: clientPublicKey, clientProof: clientProof, hashSharedSecret: hashSharedSecret, padding: configuration.sizeN)
8491
}
8592

86-
/// verify proof that client has shared secret and return a server verification proof. If verification fails a `invalidClientCode` error is thrown
93+
/// verify proof that client has shared secret and return a server verification proof. If verification
94+
/// fails a `invalidClientCode` error is thrown
8795
///
96+
/// This is the client/server proof detailed in https://www.rfc-editor.org/rfc/rfc2945
8897
/// - Parameters:
89-
/// - code: verification code sent by user
90-
/// - username: username
91-
/// - salt: salt stored with user
92-
/// - clientPublicKey: Client public key
93-
/// - serverPublicKey: Server public key
94-
/// - sharedSecret: Shared secret
98+
/// - code: verification code sent by user (M1)
99+
/// - username: username (I)
100+
/// - salt: salt stored with user (s)
101+
/// - clientPublicKey: Client public key (A)
102+
/// - serverPublicKey: Server public key (B)
103+
/// - sharedSecret: Shared secret (S)
95104
/// - Throws: invalidClientCode
96105
/// - Returns: The server verification code
97106
public func verifyClientProof(proof: [UInt8], username: String, salt: [UInt8], clientPublicKey: SRPKey, serverPublicKey: SRPKey, sharedSecret: SRPKey) throws -> [UInt8] {

Sources/SRP/srp.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ public struct SRP<H: HashFunction> {
99
BigNum(bytes: [UInt8].init(H.hash(data: clientPublicKey + serverPublicKey)))
1010
}
1111

12-
/// Calculate a simpler client verification code H(A | B | S)
13-
static func calculateSimpleClientProof(
12+
/// Calculate client verification code H(A | B | S) as used by Mozilla
13+
static func calculateMozillaClientProof(
1414
clientPublicKey: SRPKey,
1515
serverPublicKey: SRPKey,
1616
sharedSecret: SRPKey,
@@ -20,14 +20,14 @@ public struct SRP<H: HashFunction> {
2020
return [UInt8](HABK)
2121
}
2222

23-
/// Calculate a simpler client verification code H(A | M1 | S)
24-
static func calculateSimpleServerVerification(
23+
/// Calculate a server verification code H(A | M1 | K) as used by Mozilla
24+
static func calculateMozillaServerVerification(
2525
clientPublicKey: SRPKey,
2626
clientProof: [UInt8],
27-
sharedSecret: SRPKey,
27+
hashSharedSecret: [UInt8],
2828
padding: Int
2929
) -> [UInt8] {
30-
let HABK = H.hash(data: clientPublicKey.bytes(padding: padding) + clientProof.pad(to: padding) + sharedSecret.bytes(padding: padding))
30+
let HABK = H.hash(data: clientPublicKey.bytes(padding: padding) + clientProof + hashSharedSecret)
3131
return [UInt8](HABK)
3232
}
3333

Tests/SRPTests/SRPTests.swift

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,35 @@ final class SRPTests: XCTestCase {
6565
}
6666
}
6767

68+
func testMozillaVerifySRP<H: HashFunction>(configuration: SRPConfiguration<H>) {
69+
let username = "adamfowler"
70+
let password = "testpassword"
71+
let client = SRPClient<H>(configuration: configuration)
72+
let server = SRPServer<H>(configuration: configuration)
73+
74+
let (salt, verifier) = client.generateSaltAndVerifier(username: username, password: password)
75+
76+
do {
77+
// client initiates authentication
78+
let clientKeys = client.generateKeys()
79+
// provides the server with an A value and username from which it gets the password verifier.
80+
// server initiates authentication
81+
let serverKeys = server.generateKeys(verifier: verifier)
82+
// server passes back B value and a salt which was attached to the user
83+
// client calculates verification code from username, password, current authenticator state, B and salt
84+
let clientSharedSecret = try client.calculateSharedSecret(username: username, password: password, salt: salt, clientKeys: clientKeys, serverPublicKey: serverKeys.public)
85+
let clientProof = client.calculateMozillaClientProof(clientPublicKey: clientKeys.public, serverPublicKey: serverKeys.public, sharedSecret: clientSharedSecret)
86+
// client passes proof key to server
87+
// server validates the key and then returns a server validation key
88+
let serverSharedSecret = try server.calculateSharedSecret(clientPublicKey: clientKeys.public, serverKeys: serverKeys, verifier: verifier)
89+
let serverProof = try server.verifyMozillaClientProof(proof: clientProof, clientPublicKey: clientKeys.public, serverPublicKey: serverKeys.public, sharedSecret: serverSharedSecret)
90+
// client verifies server validation key
91+
try client.verifyMozillaServerProof(serverProof: serverProof, clientProof: clientProof, clientKeys: clientKeys, sharedSecret: clientSharedSecret)
92+
} catch {
93+
XCTFail("\(error)")
94+
}
95+
}
96+
6897
func testVerifySRP() {
6998
testVerifySRP(configuration: SRPConfiguration<SHA256>(.N1024))
7099
testVerifySRP(configuration: SRPConfiguration<SHA256>(.N1536))
@@ -79,6 +108,16 @@ final class SRPTests: XCTestCase {
79108
testVerifySRP(configuration: SRPConfiguration<SHA384>(N: BigNum(37), g: BigNum(3)))
80109
}
81110

111+
func testVerifyMozillaSRP() {
112+
testMozillaVerifySRP(configuration: SRPConfiguration<SHA256>(.N1024))
113+
testMozillaVerifySRP(configuration: SRPConfiguration<SHA256>(.N1536))
114+
testMozillaVerifySRP(configuration: SRPConfiguration<SHA256>(.N2048))
115+
testMozillaVerifySRP(configuration: SRPConfiguration<SHA256>(.N3072))
116+
testMozillaVerifySRP(configuration: SRPConfiguration<Insecure.SHA1>(.N4096))
117+
testMozillaVerifySRP(configuration: SRPConfiguration<Insecure.SHA1>(.N6144))
118+
testMozillaVerifySRP(configuration: SRPConfiguration<Insecure.SHA1>(.N8192))
119+
}
120+
82121
func testClientSessionProof() {
83122
// Cannot find the source for these test vectors so can't verify correctness
84123
let configuration = SRPConfiguration<Insecure.SHA1>(.N1024)
@@ -173,7 +212,7 @@ final class SRPTests: XCTestCase {
173212

174213
XCTAssertEqual(sharedSecret.hex, "92aaf0f527906aa5e8601f5d707907a03137e1b601e04b5a1deb02a981f4be037b39829a27dba50f1b27545ff2e28729c2b79dcbdd32c9d6b20d340affab91a626a8075806c26fe39df91d0ad979f9b2ee8aad1bc783e7097407b63bfe58d9118b9b0b2a7c5c4cdebaf8e9a460f4bf6247b0da34b760a59fac891757ddedcaf08eed823b090586c63009b2d740cc9f5397be89a2c32cdcfe6d6251ce11e44e6ecbdd9b6d93f30e90896d2527564c7eb9ff70aa91acc0bac1740a11cd184ffb989554ab58117c2196b353d70c356160100ef5f4c28d19f6e59ea2508e8e8aac6001497c27f362edbafb25e0f045bfdf9fb02db9c908f10340a639fe84c31b27")
175214

176-
let clientProof = client.calculateSimpleClientProof(clientPublicKey: SRPKey(A), serverPublicKey: SRPKey(B), sharedSecret: SRPKey(sharedSecret))
215+
let clientProof = client.calculateMozillaClientProof(clientPublicKey: SRPKey(A), serverPublicKey: SRPKey(B), sharedSecret: SRPKey(sharedSecret))
177216

178217
XCTAssertEqual(clientProof.hexdigest(), "27949ec1e0f1625633436865edb037e23eb6bf5cb91873f2a2729373c2039008")
179218
}

0 commit comments

Comments
 (0)