From bd76e88ba98dec83508687ad7ecbda58ba9a708e Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Wed, 30 Oct 2024 17:52:29 +0000 Subject: [PATCH] Ensure we have correct padding everywhere (#14) --- .github/workflows/api-breakage.yml | 25 ++++++ Package.swift | 2 +- Sources/SRP/Array.swift | 10 +++ Sources/SRP/client.swift | 128 +++++++++++++++++++---------- Sources/SRP/configuration.swift | 4 +- Sources/SRP/keys.swift | 39 +++++++-- Sources/SRP/server.swift | 71 ++++++++-------- Sources/SRP/srp.swift | 43 ++-------- Tests/SRPTests/SRPTests.swift | 52 +++++++++--- 9 files changed, 234 insertions(+), 140 deletions(-) create mode 100644 .github/workflows/api-breakage.yml diff --git a/.github/workflows/api-breakage.yml b/.github/workflows/api-breakage.yml new file mode 100644 index 0000000..87eedac --- /dev/null +++ b/.github/workflows/api-breakage.yml @@ -0,0 +1,25 @@ +name: API breaking changes + +on: + pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-apibreakage + cancel-in-progress: true + +jobs: + linux: + runs-on: ubuntu-latest + timeout-minutes: 15 + container: + image: swift:latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + # https://github.com/actions/checkout/issues/766 + - name: Mark the workspace as safe + run: git config --global --add safe.directory ${GITHUB_WORKSPACE} + - name: API breaking changes + run: | + swift package diagnose-api-breaking-changes origin/${GITHUB_BASE_REF} diff --git a/Package.swift b/Package.swift index a427028..76b5877 100644 --- a/Package.swift +++ b/Package.swift @@ -15,7 +15,7 @@ let package = Package( .library(name: "SRP", targets: ["SRP"]), ], dependencies: [ - .package(url: "https://github.com/apple/swift-crypto", from: "1.0.0"), + .package(url: "https://github.com/apple/swift-crypto", "1.0.0"..<"5.0.0"), .package(url: "https://github.com/adam-fowler/big-num", from: "2.0.0"), ], targets: [ diff --git a/Sources/SRP/Array.swift b/Sources/SRP/Array.swift index 8f2a181..0061ac5 100644 --- a/Sources/SRP/Array.swift +++ b/Sources/SRP/Array.swift @@ -18,6 +18,16 @@ extension Array where Element: FixedWidthInteger { } } +extension Array where Element == UInt8 { + func pad(to size: Int) -> [UInt8] { + let padSize = size - self.count + guard padSize > 0 else { return self } + // create prefix and return prefix + data + let prefix: [UInt8] = (1...padSize).reduce([]) { result,_ in return result + [0] } + return prefix + self + } +} + /// xor together the contents of two byte arrays func ^ (lhs: [UInt8], rhs: [UInt8]) -> [UInt8] { precondition(lhs.count == rhs.count, "Arrays are required to be the same size") diff --git a/Sources/SRP/client.swift b/Sources/SRP/client.swift index 4764dfe..d1ad3b4 100644 --- a/Sources/SRP/client.swift +++ b/Sources/SRP/client.swift @@ -1,5 +1,6 @@ import BigNum import Crypto +import Foundation /// Manages the client side of Secure Remote Password /// @@ -27,14 +28,14 @@ public struct SRPClient { /// Initiate the authentication process /// - Returns: An authentication state. The A value from this state should be sent to the server public func generateKeys() -> SRPKeyPair { - var a = BigNum() - var A = BigNum() + var a: BigNum + var A: BigNum repeat { a = BigNum(bytes: SymmetricKey(size: .bits256)) A = configuration.g.power(a, modulus: configuration.N) } while A % configuration.N == BigNum(0) - return SRPKeyPair(public: SRPKey(A), private: SRPKey(a)) + return SRPKeyPair(public: SRPKey(A, padding: self.configuration.sizeN), private: SRPKey(a)) } /// return shared secret given the username, password, B value and salt from the server @@ -46,51 +47,63 @@ public struct SRPClient { /// - serverPublicKey: server public key /// - Throws: `nullServerKey` /// - Returns: shared secret - public func calculateSharedSecret(username: String, password: String, salt: [UInt8], clientKeys: SRPKeyPair, serverPublicKey: SRPKey) throws -> SRPKey { + public func calculateSharedSecret( + username: String, + password: String, + salt: [UInt8], + clientKeys: SRPKeyPair, + serverPublicKey: SRPKey + ) throws -> SRPKey { let message = [UInt8]("\(username):\(password)".utf8) - let sharedSecret = try calculateSharedSecret(message: message, salt: salt, clientKeys: clientKeys, serverPublicKey: serverPublicKey) - return SRPKey(sharedSecret) + return try calculateSharedSecret(message: message, salt: salt, clientKeys: clientKeys, serverPublicKey: serverPublicKey) } - - /// calculate proof of shared secret to send to server + /// return shared secret given a binary password, B value and salt from the server /// - Parameters: - /// - clientPublicKey: client public key + /// - password: password + /// - salt: salt + /// - clientKeys: client public/private keys /// - serverPublicKey: server public key - /// - sharedSecret: shared secret - /// - Returns: The client verification code which should be passed to the server - public func calculateSimpleClientProof(clientPublicKey: SRPKey, serverPublicKey: SRPKey, sharedSecret: SRPKey) -> [UInt8] { - // get verification code - return SRP.calculateSimpleClientProof(clientPublicKey: clientPublicKey, serverPublicKey: serverPublicKey, sharedSecret: sharedSecret) + /// - Throws: `nullServerKey` + /// - Returns: shared secret + public func calculateSharedSecret( + password: [UInt8], + salt: [UInt8], + clientKeys: SRPKeyPair, + serverPublicKey: SRPKey + ) throws -> SRPKey { + let message = [0x3a] + password + return try calculateSharedSecret(message: message, salt: salt, clientKeys: clientKeys, serverPublicKey: serverPublicKey) } - /// 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 - /// - /// - Parameters: - /// - code: Verification code returned by server - /// - state: Authentication state - /// - Throws: `requiresVerificationKey`, `invalidServerCode` - public func verifySimpleServerProof(serverProof: [UInt8], clientProof: [UInt8], clientKeys: SRPKeyPair, sharedSecret: SRPKey) throws { - // get out version of server proof - let HAMS = SRP.calculateSimpleServerVerification(clientPublicKey: clientKeys.public, clientProof: clientProof, sharedSecret: sharedSecret) - // is it the same - guard serverProof == HAMS else { throw SRPClientError.invalidServerCode } - } - /// calculate proof of shared secret to send to server /// - Parameters: - /// - username: username + /// - username: Username /// - salt: The salt value associated with the user returned by the server - /// - clientPublicKey: client public key + /// - clientPublicKey: Client public key /// - serverPublicKey: server public key /// - sharedSecret: shared secret /// - Returns: The client verification code which should be passed to the server - public func calculateClientProof(username: String, salt: [UInt8], clientPublicKey: SRPKey, serverPublicKey: SRPKey, sharedSecret: SRPKey) -> [UInt8] { - + public func calculateClientProof( + username: String, + salt: [UInt8], + clientPublicKey: SRPKey, + serverPublicKey: SRPKey, + sharedSecret: SRPKey + ) -> [UInt8] { + let clientPublicKey = clientPublicKey.with(padding: self.configuration.sizeN) + let serverPublicKey = serverPublicKey.with(padding: self.configuration.sizeN) + let sharedSecret = sharedSecret.with(padding: self.configuration.sizeN) let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes)) - // get verification code - return SRP.calculateClientProof(configuration: configuration, username: username, salt: salt, clientPublicKey: clientPublicKey, serverPublicKey: serverPublicKey, hashSharedSecret: hashSharedSecret) + return SRP.calculateClientProof( + configuration: configuration, + username: username, + salt: salt, + clientPublicKey: clientPublicKey, + serverPublicKey: serverPublicKey, + hashSharedSecret: hashSharedSecret + ) } /// If the server returns that the client verification code was valid it will also return a server @@ -101,24 +114,39 @@ public struct SRPClient { /// - clientPublicKey: Client public key /// - clientProof: Client proof /// - sharedSecret: Shared secret - public func calculateServerProof(clientPublicKey: SRPKey, clientProof: [UInt8], sharedSecret: SRPKey) -> [UInt8] { + public func calculateServerProof( + clientPublicKey: SRPKey, + clientProof: [UInt8], + sharedSecret: SRPKey + ) -> [UInt8] { + let clientPublicKey = clientPublicKey.with(padding: self.configuration.sizeN) + let sharedSecret = sharedSecret.with(padding: self.configuration.sizeN) let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes)) // get out version of server proof - return SRP.calculateServerVerification(clientPublicKey: clientPublicKey, clientProof: clientProof, sharedSecret: hashSharedSecret) + return SRP.calculateServerVerification( + clientPublicKey: clientPublicKey, + clientProof: clientProof, + hashSharedSecret: hashSharedSecret + ) } /// If the server returns that the client verification code was valid it will also return a server /// verification code that the client can use to verify the server is correct /// /// - Parameters: - /// - clientProof: Server proof + /// - serverProof: Server proof /// - clientProof: Client proof - /// - clientKeys: Client keys + /// - clientPublicKey: Client public key /// - sharedSecret: Shared secret /// - Throws: `requiresVerificationKey`, `invalidServerCode` - public func verifyServerProof(serverProof: [UInt8], clientProof: [UInt8], clientKeys: SRPKeyPair, sharedSecret: SRPKey) throws { + public func verifyServerProof( + serverProof: [UInt8], + clientProof: [UInt8], + clientPublicKey: SRPKey, + sharedSecret: SRPKey + ) throws { // get our version of server proof - let HAMK = calculateServerProof(clientPublicKey: clientKeys.public, clientProof: clientProof, sharedSecret: sharedSecret) + let HAMK = calculateServerProof(clientPublicKey: clientPublicKey, clientProof: clientProof, sharedSecret: sharedSecret) // is it the same guard serverProof == HAMK else { throw SRPClientError.invalidServerCode } } @@ -134,17 +162,29 @@ public struct SRPClient { public func generateSaltAndVerifier(username: String, password: String) -> (salt: [UInt8], verifier: SRPKey) { let salt = [UInt8].random(count: 16) let verifier = generatePasswordVerifier(username: username, password: password, salt: salt) - return (salt: salt, verifier: SRPKey(verifier)) + return (salt: salt, verifier: SRPKey(verifier, padding: configuration.sizeN)) + } + + /// Hash data using same hash function that SRP uses + /// - Parameter data: Data to be hashed + /// - Returns: Hashed data + @inlinable public func hash(data: D) -> H.Digest where D : DataProtocol { + H.hash(data: data) } } extension SRPClient { - /// return shared secret given the username, password, B value and salt from the server - func calculateSharedSecret(message: [UInt8], salt: [UInt8], clientKeys: SRPKeyPair, serverPublicKey: SRPKey) throws -> BigNum { + /// return shared secret given the message (username:password), salt from server, client keys, and B value + func calculateSharedSecret( + message: [UInt8], + salt: [UInt8], + clientKeys: SRPKeyPair, + serverPublicKey: SRPKey + ) throws -> SRPKey { guard serverPublicKey.number % configuration.N != BigNum(0) else { throw SRPClientError.nullServerKey } // calculate u = H(clientPublicKey | serverPublicKey) - let u = SRP.calculateU(clientPublicKey: clientKeys.public.bytes, serverPublicKey: serverPublicKey.bytes, pad: configuration.sizeN) + let u = SRP.calculateU(clientPublicKey: clientKeys.public.bytes, serverPublicKey: serverPublicKey.bytes) guard u != 0 else { throw SRPClientError.nullServerKey } @@ -153,7 +193,7 @@ extension SRPClient { // calculate S = (B - k*g^x)^(a+u*x) let S = (serverPublicKey.number - configuration.k * configuration.g.power(x, modulus: configuration.N)).power(clientKeys.private.number + u * x, modulus: configuration.N) - return S + return .init(S, padding: self.configuration.sizeN) } /// generate password verifier diff --git a/Sources/SRP/configuration.swift b/Sources/SRP/configuration.swift index a1fe01a..0d51b84 100644 --- a/Sources/SRP/configuration.swift +++ b/Sources/SRP/configuration.swift @@ -18,7 +18,7 @@ public struct SRPConfiguration { self.N = prime.group self.sizeN = Int(self.N.numBits() + 7) / 8 self.g = prime.generator - self.k = BigNum(bytes: [UInt8](H.hash(data: self.N.bytes + SRP.pad(self.g.bytes, to: sizeN)))) + self.k = BigNum(bytes: [UInt8](H.hash(data: self.N.bytes + self.g.bytes.pad(to: sizeN)))) } /// Initialise SRPConfiguration with your own prime and multiplicative group generator @@ -29,7 +29,7 @@ public struct SRPConfiguration { self.N = N self.sizeN = Int(self.N.numBits() + 7) / 8 self.g = g - self.k = BigNum(bytes: [UInt8](H.hash(data: self.N.bytes + SRP.pad(self.g.bytes, to: sizeN)))) + self.k = BigNum(bytes: [UInt8](H.hash(data: self.N.bytes + self.g.bytes.pad(to: sizeN)))) } public enum Prime { diff --git a/Sources/SRP/keys.swift b/Sources/SRP/keys.swift index c7d08a0..e1bbe67 100644 --- a/Sources/SRP/keys.swift +++ b/Sources/SRP/keys.swift @@ -1,22 +1,46 @@ import BigNum +import Crypto +import Foundation /// Wrapper for keys used by SRP public struct SRPKey { + /// SRPKey internal storage public let number: BigNum - public var bytes: [UInt8] { number.bytes } - public var hex: String { number.hex } + /// padding + public let padding: Int + /// Representation as a byte array + public var bytes: [UInt8] { number.bytes.pad(to: self.padding) } + /// Representation as a hex string + public var hex: String { number.bytes.pad(to: self.padding).hexdigest() } - public init(_ bytes: [UInt8]) { + /// Initialize with an array of bytes + @inlinable public init(_ bytes: C, padding: Int? = nil) { self.number = BigNum(bytes: bytes) + self.padding = padding ?? bytes.count } - public init(_ number: BigNum) { - self.number = number + /// Initialize with a crypto digest + @inlinable public init(_ digest: D, padding: Int? = nil) { + self.number = BigNum(bytes: digest) + self.padding = padding ?? D.byteCount } - public init?(hex: String) { + /// Initialize with a hex string + @inlinable public init?(hex: String, padding: Int = 0) { guard let number = BigNum(hex: hex) else { return nil } self.number = number + self.padding = padding + } + + /// Initialize with a BigNum + @usableFromInline init(_ number: BigNum, padding: Int = 0) { + self.number = number + self.padding = padding + } + + /// Return SRPKey with padding + func with(padding: Int) -> SRPKey { + .init(self.number, padding: padding) } } @@ -27,12 +51,11 @@ public struct SRPKeyPair { public let `public`: SRPKey public let `private`: SRPKey - /// Initialise a SRPKeyPair object /// - Parameters: /// - public: The public key of the key pair /// - private: The private key of the key pair - public init(`public`: SRPKey, `private`: SRPKey) { + init(`public`: SRPKey, `private`: SRPKey) { self.private = `private` self.public = `public` } diff --git a/Sources/SRP/server.swift b/Sources/SRP/server.swift index 1e1931c..a059498 100644 --- a/Sources/SRP/server.swift +++ b/Sources/SRP/server.swift @@ -1,5 +1,6 @@ import BigNum import Crypto +import Foundation /// Manages the server side of Secure Remote Password. /// @@ -15,13 +16,6 @@ import Crypto /// - https://tools.ietf.org/html/rfc5054 /// public struct SRPServer { - /// Authentication state. Stores A,B and shared secret - public struct AuthenticationState { - let clientPublicKey: SRPKey - let serverPublicKey: SRPKey - var serverPrivateKey: SRPKey - } - /// configuration has to be the same as the client configuration public let configuration: SRPConfiguration @@ -42,58 +36,54 @@ public struct SRPServer { B = (configuration.k * verifier.number + configuration.g.power(b, modulus: configuration.N)) % configuration.N } while B % configuration.N == BigNum(0) - return SRPKeyPair(public: SRPKey(B), private: SRPKey(b)) + return SRPKeyPair(public: SRPKey(B, padding: self.configuration.sizeN), private: SRPKey(b)) } /// calculate the shared secret /// - Parameters: - /// - clientPublicKey: public key received from client - /// - serverKeys: server key pair - /// - verifier: password verifier + /// - clientPublicKey: Public key received from client + /// - serverKeys: Server key pair + /// - verifier: Password verifier /// - Returns: shared secret - public func calculateSharedSecret(clientPublicKey: SRPKey, serverKeys: SRPKeyPair, verifier: SRPKey) throws -> SRPKey { + public func calculateSharedSecret( + clientPublicKey: SRPKey, + serverKeys: SRPKeyPair, + verifier: SRPKey + ) throws -> SRPKey { + let clientPublicKey = clientPublicKey.with(padding: self.configuration.sizeN) guard clientPublicKey.number % configuration.N != BigNum(0) else { throw SRPServerError.nullClientKey } // calculate u = H(clientPublicKey | serverPublicKey) - let u = SRP.calculateU(clientPublicKey: clientPublicKey.bytes, serverPublicKey: serverKeys.public.bytes, pad: configuration.sizeN) + let u = SRP.calculateU(clientPublicKey: clientPublicKey.bytes, serverPublicKey: serverKeys.public.bytes) // calculate S let S = ((clientPublicKey.number * verifier.number.power(u, modulus: configuration.N)).power(serverKeys.private.number, modulus: configuration.N)) - return SRPKey(S) - } - - /// verify proof that client has shared secret and return a server verification proof. If verification fails a `invalidClientCode` error is thrown - /// - /// - Parameters: - /// - proof: Client proof - /// - clientPublicKey: Client public key - /// - serverPublicKey: Server public key - /// - sharedSecret: Shared secret - /// - Throws: invalidClientCode - /// - Returns: The server verification code - public func verifySimpleClientProof(proof: [UInt8], clientPublicKey: SRPKey, serverPublicKey: SRPKey, sharedSecret: SRPKey) throws -> [UInt8] { - let clientProof = SRP.calculateSimpleClientProof( - clientPublicKey: clientPublicKey, - serverPublicKey: serverPublicKey, - sharedSecret: sharedSecret - ) - guard clientProof == proof else { throw SRPServerError.invalidClientProof } - return SRP.calculateSimpleServerVerification(clientPublicKey: clientPublicKey, clientProof: clientProof, sharedSecret: sharedSecret) + return SRPKey(S, padding: self.configuration.sizeN) } /// verify proof that client has shared secret and return a server verification proof. If verification fails a `invalidClientCode` error is thrown /// /// - Parameters: /// - code: verification code sent by user - /// - username: username - /// - salt: salt stored with user + /// - username: Username + /// - salt: Salt stored with user /// - clientPublicKey: Client public key /// - serverPublicKey: Server public key /// - sharedSecret: Shared secret /// - Throws: invalidClientCode /// - Returns: The server verification code - public func verifyClientProof(proof: [UInt8], username: String, salt: [UInt8], clientPublicKey: SRPKey, serverPublicKey: SRPKey, sharedSecret: SRPKey) throws -> [UInt8] { + public func verifyClientProof( + proof: [UInt8], + username: String, + salt: [UInt8], + clientPublicKey: SRPKey, + serverPublicKey: SRPKey, + sharedSecret: SRPKey + ) throws -> [UInt8] { + let clientPublicKey = clientPublicKey.with(padding: self.configuration.sizeN) + let serverPublicKey = serverPublicKey.with(padding: self.configuration.sizeN) + let sharedSecret = sharedSecret.with(padding: self.configuration.sizeN) let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes)) let clientProof = SRP.calculateClientProof( @@ -105,6 +95,13 @@ public struct SRPServer { hashSharedSecret: hashSharedSecret ) guard clientProof == proof else { throw SRPServerError.invalidClientProof } - return SRP.calculateServerVerification(clientPublicKey: clientPublicKey, clientProof: clientProof, sharedSecret: hashSharedSecret) + return SRP.calculateServerVerification(clientPublicKey: clientPublicKey, clientProof: clientProof, hashSharedSecret: hashSharedSecret) + } + + /// Hash data using same hash function that SRP uses + /// - Parameter data: Data to be hashed + /// - Returns: Hashed data + @inlinable public func hash(data: D) -> H.Digest where D : DataProtocol { + H.hash(data: data) } } diff --git a/Sources/SRP/srp.swift b/Sources/SRP/srp.swift index 52bcd80..e6d7999 100644 --- a/Sources/SRP/srp.swift +++ b/Sources/SRP/srp.swift @@ -4,40 +4,11 @@ import Crypto /// Contains common code used by both client and server SRP code public struct SRP { - /// pad to a certain size by prefixing with zeros - static func pad(_ data: [UInt8], to size: Int) -> [UInt8] { - let padSize = size - data.count - guard padSize > 0 else { return data } - // create prefix and return prefix + data - let prefix: [UInt8] = (1...padSize).reduce([]) { result,_ in return result + [0] } - return prefix + data - } - /// calculate u = H(clientPublicKey | serverPublicKey) - public static func calculateU(clientPublicKey: [UInt8], serverPublicKey: [UInt8], pad: Int) -> BigNum { - BigNum(bytes: [UInt8].init(H.hash(data: SRP.pad(clientPublicKey, to: pad) + SRP.pad(serverPublicKey, to: pad)))) + static func calculateU(clientPublicKey: [UInt8], serverPublicKey: [UInt8]) -> BigNum { + BigNum(bytes: [UInt8].init(H.hash(data: clientPublicKey + serverPublicKey))) } - /// Calculate a simpler client verification code H(A | B | S) - static func calculateSimpleClientProof( - clientPublicKey: SRPKey, - serverPublicKey: SRPKey, - sharedSecret: SRPKey) -> [UInt8] - { - let HABK = H.hash(data: clientPublicKey.bytes + serverPublicKey.bytes + sharedSecret.bytes) - return [UInt8](HABK) - } - - /// Calculate a simpler client verification code H(A | M1 | S) - static func calculateSimpleServerVerification( - clientPublicKey: SRPKey, - clientProof: [UInt8], - sharedSecret: SRPKey) -> [UInt8] - { - let HABK = H.hash(data: clientPublicKey.bytes + clientProof + sharedSecret.bytes) - return [UInt8](HABK) - } - /// Calculate client verification code H(H(N)^ H(g)) | H(username) | salt | A | B | H(S)) static func calculateClientProof( configuration: SRPConfiguration, @@ -45,10 +16,10 @@ public struct SRP { salt: [UInt8], clientPublicKey: SRPKey, serverPublicKey: SRPKey, - hashSharedSecret: [UInt8]) -> [UInt8] - { + hashSharedSecret: [UInt8] + ) -> [UInt8] { // M = H(H(N)^ H(g)) | H(username) | salt | client key | server key | H(shared secret)) - let N_xor_g = [UInt8](H.hash(data: configuration.N.bytes)) ^ [UInt8](H.hash(data: configuration.g.bytes)) + let N_xor_g = [UInt8](H.hash(data: configuration.N.bytes)) ^ [UInt8](H.hash(data: configuration.g.bytes.pad(to: configuration.sizeN))) let hashUser = H.hash(data: [UInt8](username.utf8)) let M1 = [UInt8](N_xor_g) + hashUser + salt let M2 = clientPublicKey.bytes + serverPublicKey.bytes + hashSharedSecret @@ -57,8 +28,8 @@ public struct SRP { } /// Calculate server verification code H(A | M1 | K) - static func calculateServerVerification(clientPublicKey: SRPKey, clientProof: [UInt8], sharedSecret: [UInt8]) -> [UInt8] { - let HAMK = H.hash(data: clientPublicKey.bytes + clientProof + sharedSecret) + static func calculateServerVerification(clientPublicKey: SRPKey, clientProof: [UInt8], hashSharedSecret: [UInt8]) -> [UInt8] { + let HAMK = H.hash(data: clientPublicKey.bytes + clientProof + hashSharedSecret) return [UInt8](HAMK) } } diff --git a/Tests/SRPTests/SRPTests.swift b/Tests/SRPTests/SRPTests.swift index ab15098..eaca279 100644 --- a/Tests/SRPTests/SRPTests.swift +++ b/Tests/SRPTests/SRPTests.swift @@ -59,7 +59,7 @@ final class SRPTests: XCTestCase { let serverSharedSecret = try server.calculateSharedSecret(clientPublicKey: clientKeys.public, serverKeys: serverKeys, verifier: verifier) let serverProof = try server.verifyClientProof(proof: clientProof, username: username, salt: salt, clientPublicKey: clientKeys.public, serverPublicKey: serverKeys.public, sharedSecret: serverSharedSecret) // client verifies server validation key - try client.verifyServerProof(serverProof: serverProof, clientProof: clientProof, clientKeys: clientKeys, sharedSecret: clientSharedSecret) + try client.verifyServerProof(serverProof: serverProof, clientProof: clientProof, clientPublicKey: clientKeys.public, sharedSecret: clientSharedSecret) } catch { XCTFail("\(error)") } @@ -80,23 +80,36 @@ final class SRPTests: XCTestCase { } func testClientSessionProof() { + // Cannot find the source for these test vectors so can't verify correctness let configuration = SRPConfiguration(.N1024) let username = "alice" let salt = "bafa3be2813c9326".bytes(using: .hexadecimal)! let A = BigNum(hex: "b525e8fe2eac8f5da6b3220e66a0ab6f833a59d5f079fe9ddcdf111a22eaec95850374d9d7597f45497eb429bcde5057a450948de7d48edc034264916a01e6c0690e14b0a527f107d3207fd2214653c9162f5745e7cbeb19a550a072d4600ce8f4ef778f6d6899ba718adf0a462e7d981ed689de93ea1bda773333f23ebb4a9b")! let B = BigNum(hex: "2bfc8559a022497f1254af3c76786b95cb0dfb449af15501aa51eefe78947d7ef06df4fcc07a899bcaae0e552ca72c7a1f3016f3ec357a86a1428dad9f98cb8a69d405404e57e9aaf01e51a46a73b3fc7bc1d212569e4a882ae6d878599e098c89033838ec069fe368a49461f531e5b4662700d56d8c252d0aea9da6abe9b014")! let secret = "b6288955afd690a13686d65886b5f82018515df3".bytes(using: .hexadecimal)! - let clientProof = SRP.calculateClientProof(configuration: configuration, username: username, salt: salt, clientPublicKey: SRPKey(A), serverPublicKey: SRPKey(B), hashSharedSecret: secret) - - XCTAssertEqual(clientProof.hexdigest(), "e4c5c2e145ea2de18d0cc1ac9dc2a0d0988706d6") + let clientProof = SRP.calculateClientProof( + configuration: configuration, + username: username, + salt: salt, + clientPublicKey: SRPKey(A, padding: configuration.sizeN), + serverPublicKey: SRPKey(B, padding: configuration.sizeN), + hashSharedSecret: secret + ) + + XCTAssertEqual(clientProof.hexdigest(), "2dbc18fd8bbb6574fd318e96fbc92c4c8dc8a5e8") } func testServerSessionProof() { + // Cannot find the source for these test vectors so can't verify correctness let A = BigNum(hex: "eade4992a46182e9ffe2e69f3e2639ca5f8c29b2868083c45d0972b72bb6003911b64a7ea6738061d705d368ddbe2bdb251bec63184db09b8990d8a7415dc449fbab720626fc25d6bd33c32234973c1e41c25b18d1824590c807c491221be5493878bd27a5ca507fd3963c849b07a9ec413e13253c6c61e7f3219b247cfa574a")! let secret = "d89740e18a9fb597aef8f2ecc0e66f4b31c2ae08".bytes(using: .hexadecimal)! let clientProof = "e1a8629a723039a61be91a173ab6260fc582192f".bytes(using: .hexadecimal)! - let serverProof = SRP.calculateServerVerification(clientPublicKey: SRPKey(A), clientProof: clientProof, sharedSecret: secret) + let serverProof = SRP.calculateServerVerification( + clientPublicKey: SRPKey(A), + clientProof: clientProof, + hashSharedSecret: secret + ) XCTAssertEqual(serverProof.hexdigest(), "8342bd06bdf4d263de2df9a56da8e581fb38c769") } @@ -127,11 +140,17 @@ final class SRPTests: XCTestCase { XCTAssertEqual(B.hex, "BD0C61512C692C0CB6D041FA01BB152D4916A1E77AF46AE105393011BAF38964DC46A0670DD125B95A981652236F99D9B681CBF87837EC996C6DA04453728610D0C6DDB58B318885D7D82C7F8DEB75CE7BD4FBAA37089E6F9C6059F388838E7A00030B331EB76840910440B1B27AAEAEEB4012B7D7665238A8E3FB004B117B58".lowercased()) - let u = SRP.calculateU(clientPublicKey: A.bytes, serverPublicKey: B.bytes, pad: configuration.sizeN) + let u = SRP.calculateU(clientPublicKey: A.bytes.pad(to: configuration.sizeN), serverPublicKey: B.bytes.pad(to: configuration.sizeN)) XCTAssertEqual(u.hex, "CE38B9593487DA98554ED47D70A7AE5F462EF019".lowercased()) - let sharedSecret = try client.calculateSharedSecret(username: username, password: password, salt: salt, clientKeys: SRPKeyPair(public: SRPKey(A), private: SRPKey(a)), serverPublicKey: SRPKey(B)) + let sharedSecret = try client.calculateSharedSecret( + username: username, + password: password, + salt: salt, + clientKeys: SRPKeyPair(public: SRPKey(A, padding: configuration.sizeN), private: SRPKey(a)), + serverPublicKey: SRPKey(B, padding: configuration.sizeN) + ) XCTAssertEqual(sharedSecret.number.hex, "B0DC82BABCF30674AE450C0287745E7990A3381F63B387AAF271A10D233861E359B48220F7C4693C9AE12B0A6F67809F0876E2D013800D6C41BB59B6D5979B5C00A172B4A2A5903A0BDCAF8A709585EB2AFAFA8F3499B200210DCC1F10EB33943CD67FC88A2F39A4BE5BEC4EC0A3212DC346D7E474B29EDE8A469FFECA686E5A".lowercased()) } @@ -155,21 +174,30 @@ final class SRPTests: XCTestCase { // copied from server.swift let B = (configuration.k * verifier + configuration.g.power(b, modulus: configuration.N)) % configuration.N - XCTAssertEqual(B.hex, "22ce5a7b9d81277172caa20b0f1efb4643b3becc53566473959b07b790d3c3f08650d5531c19ad30ebb67bdb481d1d9cf61bf272f8439848fdda58a4e6abc5abb2ac496da5098d5cbf90e29b4b110e4e2c033c70af73925fa37457ee13ea3e8fde4ab516dff1c2ae8e57a6b264fb9db637eeeae9b5e43dfaba9b329d3b8770ce89888709e026270e474eef822436e6397562f284778673a1a7bc12b6883d1c21fbc27ffb3dbeb85efda279a69a19414969113f10451603065f0a012666645651dde44a52f4d8de113e2131321df1bf4369d2585364f9e536c39a4dce33221be57d50ddccb4384e3612bbfd03a268a36e4f7e01de651401e108cc247db50392") + XCTAssertEqual(SRPKey(B, padding: configuration.sizeN).hex, "0022ce5a7b9d81277172caa20b0f1efb4643b3becc53566473959b07b790d3c3f08650d5531c19ad30ebb67bdb481d1d9cf61bf272f8439848fdda58a4e6abc5abb2ac496da5098d5cbf90e29b4b110e4e2c033c70af73925fa37457ee13ea3e8fde4ab516dff1c2ae8e57a6b264fb9db637eeeae9b5e43dfaba9b329d3b8770ce89888709e026270e474eef822436e6397562f284778673a1a7bc12b6883d1c21fbc27ffb3dbeb85efda279a69a19414969113f10451603065f0a012666645651dde44a52f4d8de113e2131321df1bf4369d2585364f9e536c39a4dce33221be57d50ddccb4384e3612bbfd03a268a36e4f7e01de651401e108cc247db50392") let a = BigNum(hex: "00f2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d3d7")! // copied from client.swift let A = configuration.g.power(a, modulus: configuration.N) - XCTAssertEqual(A.hex, "7da76cb7e77af5ab61f334dbd5a958513afcdf0f47ab99271fc5f7860fe2132e5802ca79d2e5c064bb80a38ee08771c98a937696698d878d78571568c98a1c40cc6e7cb101988a2f9ba3d65679027d4d9068cb8aad6ebff0101bab6d52b5fdfa81d2ed48bba119d4ecdb7f3f478bd236d5749f2275e9484f2d0a9259d05e49d78a23dd26c60bfba04fd346e5146469a8c3f010a627be81c58ded1caaef2363635a45f97ca0d895cc92ace1d09a99d6beb6b0dc0829535c857a419e834db12864cd6ee8a843563b0240520ff0195735cd9d316842d5d3f8ef7209a0bb4b54ad7374d73e79be2c3975632de562c596470bb27bad79c3e2fcddf194e1666cb9fc") + XCTAssertEqual(SRPKey(A, padding: configuration.sizeN).hex, "007da76cb7e77af5ab61f334dbd5a958513afcdf0f47ab99271fc5f7860fe2132e5802ca79d2e5c064bb80a38ee08771c98a937696698d878d78571568c98a1c40cc6e7cb101988a2f9ba3d65679027d4d9068cb8aad6ebff0101bab6d52b5fdfa81d2ed48bba119d4ecdb7f3f478bd236d5749f2275e9484f2d0a9259d05e49d78a23dd26c60bfba04fd346e5146469a8c3f010a627be81c58ded1caaef2363635a45f97ca0d895cc92ace1d09a99d6beb6b0dc0829535c857a419e834db12864cd6ee8a843563b0240520ff0195735cd9d316842d5d3f8ef7209a0bb4b54ad7374d73e79be2c3975632de562c596470bb27bad79c3e2fcddf194e1666cb9fc") - let u = SRP.calculateU(clientPublicKey: A.bytes, serverPublicKey: B.bytes, pad: configuration.sizeN) + let u = SRP.calculateU(clientPublicKey: A.bytes.pad(to: configuration.sizeN), serverPublicKey: B.bytes.pad(to: configuration.sizeN)) XCTAssertEqual(u.hex, "b284aa1064e8775150da6b5e2147b47ca7df505bed94a6f4bb2ad873332ad732") - let sharedSecret = try client.calculateSharedSecret(message: message, salt: salt, clientKeys: SRPKeyPair(public: SRPKey(A), private: SRPKey(a)), serverPublicKey: SRPKey(B)) + let sharedSecret = try client.calculateSharedSecret( + message: message, + salt: salt, + clientKeys: SRPKeyPair(public: SRPKey(A, padding: configuration.sizeN), private: SRPKey(a)), + serverPublicKey: SRPKey(B, padding: configuration.sizeN) + ) + + XCTAssertEqual(sharedSecret.hex, "0092aaf0f527906aa5e8601f5d707907a03137e1b601e04b5a1deb02a981f4be037b39829a27dba50f1b27545ff2e28729c2b79dcbdd32c9d6b20d340affab91a626a8075806c26fe39df91d0ad979f9b2ee8aad1bc783e7097407b63bfe58d9118b9b0b2a7c5c4cdebaf8e9a460f4bf6247b0da34b760a59fac891757ddedcaf08eed823b090586c63009b2d740cc9f5397be89a2c32cdcfe6d6251ce11e44e6ecbdd9b6d93f30e90896d2527564c7eb9ff70aa91acc0bac1740a11cd184ffb989554ab58117c2196b353d70c356160100ef5f4c28d19f6e59ea2508e8e8aac6001497c27f362edbafb25e0f045bfdf9fb02db9c908f10340a639fe84c31b27") + + let clientProof = SHA256.hash(data: SRPKey(A, padding: configuration.sizeN).bytes + SRPKey(B, padding: configuration.sizeN).bytes + sharedSecret.bytes) - XCTAssertEqual(sharedSecret.hex, "92aaf0f527906aa5e8601f5d707907a03137e1b601e04b5a1deb02a981f4be037b39829a27dba50f1b27545ff2e28729c2b79dcbdd32c9d6b20d340affab91a626a8075806c26fe39df91d0ad979f9b2ee8aad1bc783e7097407b63bfe58d9118b9b0b2a7c5c4cdebaf8e9a460f4bf6247b0da34b760a59fac891757ddedcaf08eed823b090586c63009b2d740cc9f5397be89a2c32cdcfe6d6251ce11e44e6ecbdd9b6d93f30e90896d2527564c7eb9ff70aa91acc0bac1740a11cd184ffb989554ab58117c2196b353d70c356160100ef5f4c28d19f6e59ea2508e8e8aac6001497c27f362edbafb25e0f045bfdf9fb02db9c908f10340a639fe84c31b27") + XCTAssertEqual([UInt8](clientProof).hexdigest(), "27949ec1e0f1625633436865edb037e23eb6bf5cb91873f2a2729373c2039008") } static var allTests = [