diff --git a/Xcodes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Xcodes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 89a660f..e76dc9c 100644 --- a/Xcodes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Xcodes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -11,12 +11,12 @@ } }, { - "package": "big-num", - "repositoryURL": "https://github.com/adam-fowler/big-num", + "package": "BigInt", + "repositoryURL": "https://github.com/attaswift/BigInt", "state": { "branch": null, - "revision": "5c5511ad06aeb2b97d0868f7394e14a624bfb1c7", - "version": "2.0.2" + "revision": "793a7fac0bfc318e85994bf6900652e827aef33e", + "version": "5.4.1" } }, { diff --git a/Xcodes/AppleAPI/Sources/AppleAPI/Client.swift b/Xcodes/AppleAPI/Sources/AppleAPI/Client.swift index 42698ef..09f7c08 100644 --- a/Xcodes/AppleAPI/Sources/AppleAPI/Client.swift +++ b/Xcodes/AppleAPI/Sources/AppleAPI/Client.swift @@ -4,6 +4,7 @@ import SRP import Crypto import CommonCrypto + public class Client { private static let authTypes = ["sa", "hsa", "non-sa", "hsa2"] @@ -14,9 +15,8 @@ public class Client { public func srpLogin(accountName: String, password: String) -> AnyPublisher { var serviceKey: String! - let config = SRPConfiguration(.N2048) - let client = SRPClient(configuration: config) - let clientKeys = client.generateKeys() + let client = SRPClient(username: accountName, password: password) + let a = client.startAuthentication() return Current.network.dataTask(with: URLRequest.itcServiceKey) .map(\.data) @@ -33,13 +33,14 @@ public class Client { } .flatMap { (serviceKey, hashcash) -> AnyPublisher<(String, String, ServerSRPInitResponse), Swift.Error> in - return Current.network.dataTask(with: URLRequest.SRPInit(serviceKey: serviceKey, a: clientKeys.private.hex, accountName: accountName)) + return Current.network.dataTask(with: URLRequest.SRPInit(serviceKey: serviceKey, a: a.base64EncodedString(), accountName: accountName)) .map(\.data) .decode(type: ServerSRPInitResponse.self, decoder: JSONDecoder()) .map { return (serviceKey, hashcash, $0) } .eraseToAnyPublisher() } .flatMap { (serviceKey, hashcash, srpInit) -> AnyPublisher in + print("SRP INIT REsponse: \(srpInit)") guard let decodedB = Data(base64Encoded: srpInit.b) else { return Fail(error: AuthenticationError.srpInvalidPublicKey) @@ -52,46 +53,29 @@ public class Client { } let iterations = srpInit.iteration - let serverPublic = SRPKey([UInt8](decodedB)) - guard let encryptedPassword = self.pbkdf2(password: password, saltData: decodedSalt, keyByteCount: 32, prf: CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256), rounds: iterations) else { - return Fail(error: AuthenticationError.srpInvalidPublicKey) - .eraseToAnyPublisher() - } - - - let encryptedPasswordArray = encryptedPassword.hexEncodedString() - - print("EncryptedPassword: \(encryptedPasswordArray)") - print("EncryptedPassword: \([UInt8](encryptedPassword))") do { - // this calculates "S" - let clientSharedSecret = try client.calculateSharedSecret( - encryptedPassword: encryptedPasswordArray, - salt: [UInt8](decodedSalt), - clientKeys: clientKeys, - serverPublicKey: serverPublic - ) - print("SharedSecret: \(clientSharedSecret)") + guard let encryptedPassword = self.pbkdf2(password: password, saltData: decodedSalt, keyByteCount: 32, prf: CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256), rounds: iterations) else { + return Fail(error: AuthenticationError.srpInvalidPublicKey) + .eraseToAnyPublisher() + } - let m1 = client.calculateClientProof( - username: accountName, - salt: [UInt8](decodedSalt), - clientPublicKey: clientKeys.public, - serverPublicKey: serverPublic, - sharedSecret: clientSharedSecret - ) - - let m2 = client.serverProof(clientProof: m1, clientKeys: clientKeys, sharedSecret: clientSharedSecret) - +// let m1 = try client.processChallenge(salt: decodedSalt, publicKey: decodedB, isEncryptedPassword: true, encryptedPassword: encryptedPassword.hexEncodedString()) + let encryptedPasswordString = String(data: encryptedPassword, encoding: .utf8) + let m1 = try client.processChallenge(salt: decodedSalt, publicKey: decodedB, isEncryptedPassword: true, encryptedPassword: encryptedPasswordString) - print("M1: \(Data(m1).base64EncodedString())") - print("M2: \(Data(m2).base64EncodedString())") + guard let m2 = client.HAMK else { + return Fail(error: AuthenticationError.srpInvalidPublicKey) + .eraseToAnyPublisher() + } - return Current.network.dataTask(with: URLRequest.SRPComplete(serviceKey: serviceKey, hashcash: hashcash, accountName: accountName, c: srpInit.c, m1: Data(m1).base64EncodedString(), m2: Data(m2).base64EncodedString())) - .mapError { $0 as Swift.Error } - .eraseToAnyPublisher() + print("m1: \(m1.base64EncodedString())") + print("m2: \(m2.base64EncodedString())") + + return Current.network.dataTask(with: URLRequest.SRPComplete(serviceKey: serviceKey, hashcash: hashcash, accountName: accountName, c: srpInit.c, m1: m1.base64EncodedString(), m2: m2.base64EncodedString())) + .mapError { $0 as Swift.Error } + .eraseToAnyPublisher() } catch { print("Error: calculateSharedSecret \(error)") return Fail(error: AuthenticationError.srpInvalidPublicKey) diff --git a/xcodes-srp/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/xcodes-srp/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/xcodes-srp/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/xcodes-srp/Package.resolved b/xcodes-srp/Package.resolved new file mode 100644 index 0000000..8c65785 --- /dev/null +++ b/xcodes-srp/Package.resolved @@ -0,0 +1,34 @@ +{ + "object": { + "pins": [ + { + "package": "big-num", + "repositoryURL": "https://github.com/adam-fowler/big-num", + "state": { + "branch": null, + "revision": "5c5511ad06aeb2b97d0868f7394e14a624bfb1c7", + "version": "2.0.2" + } + }, + { + "package": "BigInt", + "repositoryURL": "https://github.com/attaswift/BigInt.git", + "state": { + "branch": null, + "revision": "793a7fac0bfc318e85994bf6900652e827aef33e", + "version": "5.4.1" + } + }, + { + "package": "swift-crypto", + "repositoryURL": "https://github.com/apple/swift-crypto", + "state": { + "branch": null, + "revision": "ddb07e896a2a8af79512543b1c7eb9797f8898a5", + "version": "1.1.7" + } + } + ] + }, + "version": 1 +} diff --git a/xcodes-srp/Package.swift b/xcodes-srp/Package.swift index a427028..890d696 100644 --- a/xcodes-srp/Package.swift +++ b/xcodes-srp/Package.swift @@ -16,10 +16,10 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/apple/swift-crypto", from: "1.0.0"), - .package(url: "https://github.com/adam-fowler/big-num", from: "2.0.0"), + .package(url: "https://github.com/attaswift/BigInt.git", from: "5.0.0") ], targets: [ - .target(name: "SRP", dependencies: ["BigNum", "Crypto"]), + .target(name: "SRP", dependencies: ["Crypto", "BigInt"]), .testTarget( name: "SRPTests", dependencies: ["SRP"]), ] diff --git a/xcodes-srp/Sources/SRP/client.swift b/xcodes-srp/Sources/SRP/client.swift index 98e0190..3870252 100644 --- a/xcodes-srp/Sources/SRP/client.swift +++ b/xcodes-srp/Sources/SRP/client.swift @@ -1,182 +1,224 @@ -import BigNum + +import Foundation +import BigInt import Crypto -/// Manages the client side of Secure Remote Password -/// -/// Secure Remote Password (SRP) provides username and password authentication without needing to provide your password to the server. The server -/// has a cryptographic verifier that is derived from the password and a salt that was used to generate this verifier. Both client and server -/// generate a shared secret then the client sends a proof they have the secret and if it is correct the server will do the same to verify the -/// server as well. -/// -/// This version is compliant with SRP version 6a and RFC 5054. -/// -/// Reference reading -/// - https://tools.ietf.org/html/rfc2945 -/// - https://tools.ietf.org/html/rfc5054 -/// -public struct SRPClient { - /// configuration. This needs to be the same as the server configuration - public let configuration: SRPConfiguration - - /// Initialise a SRPClient object - /// - Parameter configuration: configuration to use - public init(configuration: SRPConfiguration) { - self.configuration = configuration - } - - /// 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() - repeat { - a = BigNum(bytes: SymmetricKey(size: .bits256)) - A = configuration.g.power(a, modulus: configuration.N) - } while A % configuration.N == BigNum(0) +/// SRP Client; the party that initializes the authentication and +/// must proof possession of the correct password. +public class SRPClient { + let a: BigUInt + let A: BigUInt - return SRPKeyPair(public: SRPKey(A), private: SRPKey(a)) - } - - /// return shared secret given the username, password, B value and salt from the server - /// - Parameters: - /// - username: user identifier - /// - password: password - /// - salt: salt - /// - clientKeys: client public/private keys - /// - serverPublicKey: server public key - /// - Throws: `nullServerKey` - /// - Returns: shared secret - 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) + let group: Group + typealias impl = Implementation // swiftlint:disable:this type_name + + let username: String + var password: String? + var precomputedX: BigUInt? + + public var HAMK: Data? + var K: Data? + + /// Whether the session is authenticated, i.e. the password + /// was verified by the server and proof of a valid session + /// key was provided by the server. If `true`, `sessionKey` + /// is also available. + public private(set) var isAuthenticated = false + + private init( + username: String, + group: Group = .N2048, + privateKey: Data? = nil) + { + self.username = username + self.group = group + if let privateKey = privateKey { + a = BigUInt(privateKey) + } else { + a = BigUInt(Curve25519.KeyAgreement.PrivateKey().rawRepresentation) + } + // A = g^a % N + A = group.g.power(a, modulus: group.N) } - /// return shared secret given the username, password, B value and salt from the server - /// - Parameters: - /// - username: user identifier - /// - password: password - /// - salt: salt - /// - clientKeys: client public/private keys - /// - serverPublicKey: server public key - /// - Throws: `nullServerKey` - /// - Returns: shared secret - public func calculateSharedSecret(encryptedPassword: String, salt: [UInt8], clientKeys: SRPKeyPair, serverPublicKey: SRPKey) throws -> SRPKey { - let message = [UInt8](":\(encryptedPassword)".utf8) - let sharedSecret = try calculateSharedSecret(message: message, salt: salt, clientKeys: clientKeys, serverPublicKey: serverPublicKey) - return SRPKey(sharedSecret) - } - - - /// calculate proof of shared secret to send to server - /// - Parameters: - /// - 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 calculateSimpleClientProof(clientPublicKey: SRPKey, serverPublicKey: SRPKey, sharedSecret: SRPKey) -> [UInt8] { - // get verification code - return SRP.calculateSimpleClientProof(clientPublicKey: clientPublicKey, serverPublicKey: serverPublicKey, sharedSecret: sharedSecret) - } - - /// 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 + /// Initialize the Client SRP party with a password. /// /// - 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 } + /// - username: user's username. + /// - password: user's password. + /// - group: which `Group` to use, must be the same for the + /// server as well as the pre-stored verificationKey. + /// - privateKey: (optional) custom private key (a); if providing + /// the private key of the `Client`, make sure to provide a + /// good random key of at least 32 bytes. Default is to + /// generate a private key of 128 bytes. You MUST not re-use + /// the private key between sessions. + public convenience init( + username: String, + password: String, + group: Group = .N2048, + privateKey: Data? = nil) + { + self.init(username: username, group: group, privateKey: privateKey) + self.password = password } - /// calculate proof of shared secret to send to server - /// - Parameters: - /// - username: username - /// - salt: The salt value associated with the user returned by the server - /// - 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] { - - 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) - } - - /// 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 + /// Initialize the Client SRP party with a precomputed x. /// /// - Parameters: - /// - code: Verification code returned by server - /// - state: Authentication state - /// - Throws: `requiresVerificationKey`, `invalidServerCode` - public func verifyServerProof(serverProof: [UInt8], clientProof: [UInt8], clientKeys: SRPKeyPair, sharedSecret: SRPKey) throws { - let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes)) - // get out version of server proof - let HAMK = SRP.calculateServerVerification(clientPublicKey: clientKeys.public, clientProof: clientProof, sharedSecret: hashSharedSecret) - // is it the same - guard serverProof == HAMK else { throw SRPClientError.invalidServerCode } + /// - username: user's username. + /// - precomputedX: precomputed SRP x. + /// - group: which `Group` to use, must be the same for the + /// server as well as the pre-stored verificationKey. + /// - privateKey: (optional) custom private key (a); if providing + /// the private key of the `Client`, make sure to provide a + /// good random key of at least 32 bytes. Default is to + /// generate a private key of 128 bytes. You MUST not re-use + /// the private key between sessions. + public convenience init( + username: String, + precomputedX: Data, + group: Group = .N2048, + privateKey: Data? = nil) + { + self.init(username: username, group: group, privateKey: privateKey) + self.precomputedX = BigUInt(precomputedX) } - - /// 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 + /// Starts authentication. This method is a no-op. + /// + /// - Returns: `publicKey` (A) + public func startAuthentication() -> Data { + return publicKey + } + + /// Process the challenge provided by the server. This sets the `sessionKey` + /// and generates proof that it generated the correct key from the password + /// and the challenge. After the server has also proven the validity of their + /// key, the `sessionKey` can be used. /// /// - Parameters: - /// - code: Verification code returned by server - /// - state: Authentication state - /// - Throws: `requiresVerificationKey`, `invalidServerCode` - public func serverProof(clientProof: [UInt8], clientKeys: SRPKeyPair, sharedSecret: SRPKey) -> [UInt8] { - let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes)) + /// - salt: user-specific salt (s) + /// - publicKey: server's public key (B) + /// - Returns: key proof (M) + /// - Throws: `AuthenticationFailure.invalidPublicKey` if the server's + /// public key is invalid (i.e. B % N is zero). + public func processChallenge(salt: Data, publicKey serverPublicKey: Data, isEncryptedPassword: Bool, encryptedPassword: String? = nil) throws -> Data { + let H = impl.H + let N = group.N + + let B = BigUInt(serverPublicKey) + + guard B % N != 0 else { + throw AuthenticationFailure.invalidPublicKey + } + + let u = impl.calculate_u(group: group, A: publicKey, B: serverPublicKey) + let k = impl.calculate_k(group: group) - return SRP.calculateServerVerification(clientPublicKey: clientKeys.public, clientProof: clientProof, sharedSecret: hashSharedSecret) + let x = self.precomputedX ?? (isEncryptedPassword ? impl.calculate_x(salt: salt, username: "", password: encryptedPassword!) : impl.calculate_x(salt: salt, username: username, password: password!)) + let v = calculate_v(group: group, x: x) + + // shared secret + // S = (B - kg^x) ^ (a + ux) + // Note that v = g^x, and that B - kg^x might become negative, which + // cannot be stored in BigUInt. So we'll add N to B_ and make sure kv + // isn't greater than N. + let S = (B + N - k * v % N).power(a + u * x, modulus: N) + + // session key + K = H(S.serialize()) + + // client verification + let M = impl.calculate_M(group: group, username: username, salt: salt, A: publicKey, B: serverPublicKey, K: K!) + + // server verification + HAMK = impl.calculate_HAMK(A: publicKey, M: M, K: K!) + return M } - - /// Generate salt and password verifier from username and password. When creating your user instead of passing your password to the server, you - /// pass the salt and password verifier values. In this way the server never knows your password so can never leak it. + + /// After the server has verified that the password is correct, + /// it will send proof of the derived session key. This is verified + /// on our end and finalizes the authentication session. After this + /// step, the `sessionKey` is available. /// - /// - Parameters: - /// - username: username - /// - password: user password - /// - Returns: tuple containing salt and password verifier - 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)) + /// - Parameter HAMK: proof of the server that it derived the same + /// session key. + /// - Throws: + /// - `AuthenticationFailure.missingChallenge` if this method + /// is called before calling `processChallenge`. + /// - `AuthenticationFailure.keyProofMismatch` if the proof + /// doesn't match our own. + public func verifySession(keyProof serverKeyProof: Data) throws { + guard let HAMK = HAMK else { + throw AuthenticationFailure.missingChallenge + } + guard HAMK == serverKeyProof else { + throw AuthenticationFailure.keyProofMismatch + } + isAuthenticated = true + } + + /// The client's public key (A). For every authentication + /// session a new public key is generated. + public var publicKey: Data { + return A.serialize() + } + + /// The client's private key (a). For every authentication + /// session a new random private key is generated. + public var privateKey: Data { + return a.serialize() + } + + /// The session key (K) that is exchanged during authentication. + /// This key can be used to encrypt further communication + /// between client and server. + public var sessionKey: Data? { + guard isAuthenticated else { + return nil + } + return K } } -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 { - guard serverPublicKey.number % configuration.N != BigNum(0) else { throw SRPClientError.nullServerKey } +/// Possible authentication failure modes. +public enum AuthenticationFailure: Error { + /// Security breach: the provided public key is empty (i.e. PK % N is zero). + case invalidPublicKey - // calculate u = H(clientPublicKey | serverPublicKey) - let u = SRP.calculateU(clientPublicKey: clientKeys.public.bytes, serverPublicKey: serverPublicKey.bytes, pad: configuration.sizeN) + /// Invalid client state: call `processChallenge` before `verifySession`. + case missingChallenge - guard u != 0 else { throw SRPClientError.nullServerKey } - - let x = BigNum(bytes: [UInt8](H.hash(data: salt + H.hash(data: message)))) - - // 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 - } - - /// generate password verifier - public func generatePasswordVerifier(username: String, password: String, salt: [UInt8]) -> BigNum { - let message = "\(username):\(password)" - return generatePasswordVerifier(message: [UInt8](message.utf8), salt: salt) - } - - /// generate password verifier - public func generatePasswordVerifier(message: [UInt8], salt: [UInt8]) -> BigNum { - let x = BigNum(bytes: [UInt8](H.hash(data: salt + H.hash(data: message)))) - let verifier = configuration.g.power(x, modulus: configuration.N) - return verifier + /// Failed authentication: the key proof didn't match our own. + case keyProofMismatch +} + +extension AuthenticationFailure: CustomStringConvertible { + /// A textual representation of this instance. + /// + /// Instead of accessing this property directly, convert an instance of any + /// type to a string by using the `String(describing:)` initializer. + public var description: String { + switch self { + case .invalidPublicKey: return "security breach - the provided public key is invalid" + case .missingChallenge: return "invalid client state - call `processChallenge` before `verifySession`" + case .keyProofMismatch: return "failed authentication - the key proof didn't match our own" + } } } +func ^ (lhs: Data, rhs: Data) -> Data? { + guard lhs.count == rhs.count else { return nil } + var result = Data(count: lhs.count) + for index in lhs.indices { + result[index] = lhs[index] ^ rhs[index] + } + return result +} + +// Removed in Xcode 8 beta 3 +func + (lhs: Data, rhs: Data) -> Data { + var result = lhs + result.append(rhs) + return result +} diff --git a/xcodes-srp/Sources/SRP/configuration.swift b/xcodes-srp/Sources/SRP/configuration.swift deleted file mode 100644 index a1fe01a..0000000 --- a/xcodes-srp/Sources/SRP/configuration.swift +++ /dev/null @@ -1,201 +0,0 @@ -import BigNum -import Crypto - -/// SRP Configuration. The same configuration hast to be used by both client and server. Contains a large safe prime N ie a prime where the value (N-1)/2 is also a prime, g the multiplicative group generator and the value k = Hash(N | g) -public struct SRPConfiguration { - /// large safe prime - public let N: BigNum - /// multiplicative group generator - public let g: BigNum - /// derived value from N and g. k = H( N | g ) - public let k: BigNum - /// size in bytes of N - public let sizeN: Int - - /// Initialise SRPConfiguration with known safe prime - /// - Parameter prime: enum indicating size of prime - public init(_ prime: Prime) { - 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)))) - } - - /// Initialise SRPConfiguration with your own prime and multiplicative group generator - /// - Parameters: - /// - N: Large prime - /// - g: multiplicative group generator (usually 2) - public init(N: BigNum, g: BigNum) { - 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)))) - } - - public enum Prime { - case N1024 - case N1536 - case N2048 - case N3072 - case N4096 - case N6144 - case N8192 - - /// prime numbers and generators taken from RC 5054 https://tools.ietf.org/html/rfc5054 - var group: BigNum { - switch self { - case .N1024: - return BigNum(hex: - "EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C" + - "9C256576D674DF7496EA81D3383B4813D692C6E0E0D5D8E250B98BE4" + - "8E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD69B15D4982559B29" + - "7BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA9A" + - "FD5138FE8376435B9FC61D2FC0EB06E3")! - - case .N1536: - return BigNum(hex: - "9DEF3CAFB939277AB1F12A8617A47BBBDBA51DF499AC4C80BEEEA961" + - "4B19CC4D5F4F5F556E27CBDE51C6A94BE4607A291558903BA0D0F843" + - "80B655BB9A22E8DCDF028A7CEC67F0D08134B1C8B97989149B609E0B" + - "E3BAB63D47548381DBC5B1FC764E3F4B53DD9DA1158BFD3E2B9C8CF5" + - "6EDF019539349627DB2FD53D24B7C48665772E437D6C7F8CE442734A" + - "F7CCB7AE837C264AE3A9BEB87F8A2FE9B8B5292E5A021FFF5E91479E" + - "8CE7A28C2442C6F315180F93499A234DCF76E3FED135F9BB")! - - case .N2048: - return BigNum(hex: - "AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC319294" + - "3DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310D" + - "CD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FB" + - "D5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF74" + - "7359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A" + - "436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D" + - "5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6AF874E73" + - "03CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB6" + - "94B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F" + - "9E4AFF73")! - - case .N3072: - return BigNum(hex: - "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + - "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + - "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + - "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + - "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + - "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + - "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + - "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + - "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + - "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + - "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + - "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + - "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + - "E0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF")! - - case .N4096: - return BigNum(hex: - "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + - "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + - "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + - "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + - "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + - "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + - "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + - "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + - "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + - "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + - "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + - "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + - "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + - "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" + - "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" + - "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" + - "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" + - "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" + - "FFFFFFFFFFFFFFFF")! - - case .N6144: - return BigNum(hex: - "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + - "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + - "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + - "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + - "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + - "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + - "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + - "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + - "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + - "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + - "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + - "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + - "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + - "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" + - "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" + - "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" + - "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" + - "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" + - "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406" + - "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918" + - "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151" + - "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03" + - "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F" + - "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + - "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B" + - "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632" + - "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E" + - "6DCC4024FFFFFFFFFFFFFFFF")! - - case .N8192: - return BigNum(hex: - "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + - "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + - "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + - "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + - "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + - "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + - "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + - "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + - "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + - "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + - "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + - "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + - "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + - "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" + - "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" + - "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" + - "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" + - "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" + - "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406" + - "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918" + - "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151" + - "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03" + - "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F" + - "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + - "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B" + - "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632" + - "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E" + - "6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA" + - "3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C" + - "5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" + - "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC886" + - "2F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6" + - "6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC5" + - "0846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268" + - "359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6" + - "FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" + - "60C980DD98EDD3DFFFFFFFFFFFFFFFFF")! - } - } - - var generator: BigNum { - switch self { - case .N1024, .N1536, .N2048: - return BigNum(2) - case .N3072, .N4096, .N6144: - return BigNum(5) - case .N8192: - return BigNum(19) - } - } - } -} diff --git a/xcodes-srp/Sources/SRP/group.swift b/xcodes-srp/Sources/SRP/group.swift new file mode 100644 index 0000000..9712acb --- /dev/null +++ b/xcodes-srp/Sources/SRP/group.swift @@ -0,0 +1,248 @@ +// +// must.swift +// swift-srp +// +// Created by Matt Kiazyk on 2024-10-24. +// + + +import BigInt + +/// SRP Group Parameters +/// +/// The 1024-, 1536-, and 2048-bit groups are taken from software +/// developed by Tom Wu and Eugene Jhong for the Stanford SRP +/// distribution, and subsequently proven to be prime. The larger primes +/// are taken from [MODP], but generators have been calculated that are +/// primitive roots of N, unlike the generators in [MODP]. +/// +/// The values of N and g used in this protocol must be agreed upon by +/// the two parties in question. They can be set in advance, or the host +/// can supply them to the client. In the latter case, the host should +/// send the parameters in the first message along with the salt. For +/// maximum security, N should be a safe prime (i.e. a number of the form +/// N = 2q + 1, where q is also prime). Also, g should be a generator +/// modulo N (see [SRP] for details), which means that for any X where 0 +/// < X < N, there exists a value x for which g^x % N == X. +/// +/// [MODP] Kivinen, T. and M. Kojo, "More Modular Exponentiation +/// (MODP) Diffie-Hellman groups for Internet Key Exchange +/// (IKE)", RFC 3526, May 2003. +/// +/// [SRP] T. Wu, "The Secure Remote Password Protocol", In +/// Proceedings of the 1998 Internet Society Symposium on +/// Network and Distributed Systems Security, San Diego, CA, +/// pp. 97-111. +public enum Group { + /// 1024-bits group + case N1024 + + /// 2048-bits group + case N2048 + + /// 1536-bits group + case N1536 + + /// 3072-bits group + case N3072 + + /// 4096-bits group + case N4096 + + /// 6144-bits group + case N6144 + + /// 8192-bits group + case N8192 + + /// Custom group parameters. See `init(prime:generator:)` for more information. + public struct CustomGroup { + let N: BigUInt + let g: BigUInt + } + + /// Custom group parameters. See `init(prime:generator:)` for more information. + case custom(CustomGroup) + + /// Create custom group parameters. See the enum's documentation for + /// considerations on good parameters. + /// - Parameters: + /// - prime: hex-encoded prime + /// - generator: hex-encoded generator + /// - Returns: nil if one of the parameters chould not be decoded + public init?(prime: String, generator: String) { + guard let N = BigUInt(prime, radix: 16), let g = BigUInt(generator, radix: 16) else { + return nil + } + self = .custom(CustomGroup(N: N, g: g)) + } + + var N: BigUInt { + switch self { + case .N1024: + return BigUInt( + "EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C" + + "9C256576D674DF7496EA81D3383B4813D692C6E0E0D5D8E250B98BE4" + + "8E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD69B15D4982559B29" + + "7BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA9A" + + "FD5138FE8376435B9FC61D2FC0EB06E3", + radix: 16)! + case .N1536: + return BigUInt( + "9DEF3CAFB939277AB1F12A8617A47BBBDBA51DF499AC4C80BEEEA961" + + "4B19CC4D5F4F5F556E27CBDE51C6A94BE4607A291558903BA0D0F843" + + "80B655BB9A22E8DCDF028A7CEC67F0D08134B1C8B97989149B609E0B" + + "E3BAB63D47548381DBC5B1FC764E3F4B53DD9DA1158BFD3E2B9C8CF5" + + "6EDF019539349627DB2FD53D24B7C48665772E437D6C7F8CE442734A" + + "F7CCB7AE837C264AE3A9BEB87F8A2FE9B8B5292E5A021FFF5E91479E" + + "8CE7A28C2442C6F315180F93499A234DCF76E3FED135F9BB", + radix: 16)! + case .N2048: + return BigUInt( + "AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC319294" + + "3DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310D" + + "CD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FB" + + "D5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF74" + + "7359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A" + + "436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D" + + "5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6AF874E73" + + "03CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB6" + + "94B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F" + + "9E4AFF73", + radix: 16)! + case .N3072: + return BigUInt( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + + "E0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF", + radix: 16)! + case .N4096: + return BigUInt( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + + "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" + + "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" + + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" + + "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" + + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" + + "FFFFFFFFFFFFFFFF", + radix: 16)! + case .N6144: + return BigUInt( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + + "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" + + "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" + + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" + + "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" + + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" + + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406" + + "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918" + + "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151" + + "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03" + + "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F" + + "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B" + + "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632" + + "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E" + + "6DCC4024FFFFFFFFFFFFFFFF", + radix: 16)! + case .N8192: + return BigUInt( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + + "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" + + "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" + + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" + + "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" + + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" + + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406" + + "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918" + + "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151" + + "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03" + + "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F" + + "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B" + + "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632" + + "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E" + + "6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA" + + "3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C" + + "5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" + + "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC886" + + "2F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6" + + "6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC5" + + "0846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268" + + "359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6" + + "FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" + + "60C980DD98EDD3DFFFFFFFFFFFFFFFFF", + radix: 16)! + case .custom(let custom): + return custom.N + } + } + + var g: BigUInt { + switch self { + case .N1024: + return BigUInt(2) + case .N1536: + return BigUInt(2) + case .N2048: + return BigUInt(2) + case .N3072: + return BigUInt(5) + case .N4096: + return BigUInt(5) + case .N6144: + return BigUInt(5) + case .N8192: + return BigUInt(19) + case .custom(let custom): + return custom.g + } + } +} \ No newline at end of file diff --git a/xcodes-srp/Sources/SRP/keys.swift b/xcodes-srp/Sources/SRP/keys.swift deleted file mode 100644 index c7d08a0..0000000 --- a/xcodes-srp/Sources/SRP/keys.swift +++ /dev/null @@ -1,40 +0,0 @@ -import BigNum - -/// Wrapper for keys used by SRP -public struct SRPKey { - public let number: BigNum - public var bytes: [UInt8] { number.bytes } - public var hex: String { number.hex } - - public init(_ bytes: [UInt8]) { - self.number = BigNum(bytes: bytes) - } - - public init(_ number: BigNum) { - self.number = number - } - - public init?(hex: String) { - guard let number = BigNum(hex: hex) else { return nil } - self.number = number - } -} - -extension SRPKey: Equatable { } - -/// Contains a private and a public key -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) { - self.private = `private` - self.public = `public` - } -} - diff --git a/xcodes-srp/Sources/SRP/server.swift b/xcodes-srp/Sources/SRP/server.swift deleted file mode 100644 index c823c06..0000000 --- a/xcodes-srp/Sources/SRP/server.swift +++ /dev/null @@ -1,108 +0,0 @@ -import BigNum -import Crypto - -/// Manages the server side of Secure Remote Password. -/// -/// Secure Remote Password (SRP) provides username and password authentication without needing to provide your password to the server. The server -/// has a cryptographic verifier that is derived from the password and a salt that was used to generate this verifier. Both client and server -/// generate a shared secret then the client sends a proof they have the secret and if it is correct the server will do the same to verify the -/// server as well. -/// -/// This version is compliant with SRP version 6a and RFC 5054. -/// -/// Reference reading -/// - https://tools.ietf.org/html/rfc2945 -/// - 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 - - /// Initialise SRPServer - /// - Parameter configuration: configuration to use - public init(configuration: SRPConfiguration) { - self.configuration = configuration - } - - /// generate public and private keys to be used in srp authentication - /// - Parameter verifier: password verifier used to generate key pair - /// - Returns: return public/private key pair - public func generateKeys(verifier: SRPKey) -> SRPKeyPair { - var b: BigNum - var B: BigNum - repeat { - b = BigNum(bytes: SymmetricKey(size: .bits256)) - 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)) - } - - /// calculate the shared secret - /// - Parameters: - /// - 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 { - 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) - - // 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: - /// - code: verification code sent by user - /// - username: username - /// - salt: salt stored with user - /// - state: authentication state. - /// - 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) - } - - /// 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 - /// - state: authentication state. - /// - Throws: invalidClientCode - /// - Returns: The server verification code - public func verifyClientProof(proof: [UInt8], username: String, salt: [UInt8], clientPublicKey: SRPKey, serverPublicKey: SRPKey, sharedSecret: SRPKey) throws -> [UInt8] { - let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes)) - - let clientProof = SRP.calculateClientProof( - configuration: configuration, - username: username, - salt: salt, - clientPublicKey: clientPublicKey, - serverPublicKey: serverPublicKey, - hashSharedSecret: hashSharedSecret - ) - guard clientProof == proof else { throw SRPServerError.invalidClientProof } - return SRP.calculateServerVerification(clientPublicKey: clientPublicKey, clientProof: clientProof, sharedSecret: hashSharedSecret) - } -} diff --git a/xcodes-srp/Sources/SRP/srp.swift b/xcodes-srp/Sources/SRP/srp.swift index 52bcd80..9ffdf5d 100644 --- a/xcodes-srp/Sources/SRP/srp.swift +++ b/xcodes-srp/Sources/SRP/srp.swift @@ -1,64 +1,125 @@ -import BigNum +// +// Implementation.swift +// swift-srp +// +// Created by Matt Kiazyk on 2024-10-24. +// + + +import Foundation +import BigInt import Crypto -/// Contains common code used by both client and server SRP code -public struct SRP { +/// Creates the salted verification key based on a user's username and +/// password. Only the salt and verification key need to be stored on the +/// server, there's no need to keep the plain-text password. +/// +/// Keep the verification key private, as it can be used to brute-force +/// the password from. +/// +/// - Parameters: +/// - using: hash function to use +/// - username: user's username +/// - password: user's password +/// - salt: (optional) custom salt value; if providing a salt, make sure to +/// provide a good random salt of at least 16 bytes. Default is to +/// generate a salt of 16 bytes. +/// - group: `Group` parameters; default is 2048-bits group. +/// - algorithm: which `Digest.Algorithm` to use; default is SHA1. +/// - Returns: salt (s) and verification key (v) +public func createSaltedVerificationKey( + using hashFunction: H.Type, + group: Group = .N2048, + username: String, + password: String, + salt: Data? = nil) + -> (salt: Data, verificationKey: Data) +{ + let salt = salt ?? randomBytes(16) + let x = Implementation.calculate_x(salt: salt, username: username, password: password) + return createSaltedVerificationKey(from: x, salt: salt, group: group) +} - /// 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)))) - } - - /// 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) +/// Creates the salted verification key based on a precomputed SRP x value. +/// Only the salt and verification key need to be stored on the +/// server, there's no need to keep the plain-text password. +/// +/// Keep the verification key private, as it can be used to brute-force +/// the password from. +/// +/// - Parameters: +/// - x: precomputed SRP x +/// - salt: (optional) custom salt value; if providing a salt, make sure to +/// provide a good random salt of at least 16 bytes. Default is to +/// generate a salt of 16 bytes. +/// - group: `Group` parameters; default is 2048-bits group. +/// - Returns: salt (s) and verification key (v) +public func createSaltedVerificationKey( + from x: Data, + salt: Data? = nil, + group: Group = .N2048) + -> (salt: Data, verificationKey: Data) +{ + return createSaltedVerificationKey(from: BigUInt(x), salt: salt, group: group) +} + +func createSaltedVerificationKey( + from x: BigUInt, + salt: Data? = nil, + group: Group = .N2048) + -> (salt: Data, verificationKey: Data) +{ + let salt = salt ?? randomBytes(16) + let v = calculate_v(group: group, x: x) + return (salt, v.serialize()) +} + +func pad(_ data: Data, to size: Int) -> Data { + precondition(size >= data.count, "Negative padding not possible") + return Data(count: size - data.count) + data +} + +enum Implementation { + // swiftlint:disable:next identifier_name + static func H(_ data: Data) -> Data { + return Data(HF.hash(data: data)) } - /// Calculate client verification code H(H(N)^ H(g)) | H(username) | salt | A | B | H(S)) - static func calculateClientProof( - configuration: SRPConfiguration, - username: String, - salt: [UInt8], - clientPublicKey: SRPKey, - serverPublicKey: SRPKey, - 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 hashUser = H.hash(data: [UInt8](username.utf8)) - let M1 = [UInt8](N_xor_g) + hashUser + salt - let M2 = clientPublicKey.bytes + serverPublicKey.bytes + hashSharedSecret - let M = H.hash(data: M1 + M2) - return [UInt8](M) + //u = H(PAD(A) | PAD(B)) + static func calculate_u(group: Group, A: Data, B: Data) -> BigUInt { + let size = group.N.serialize().count + return BigUInt(H(pad(A, to: size) + pad(B, to: size))) } - /// 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) - return [UInt8](HAMK) + //M1 = H(H(N) XOR H(g) | H(I) | s | A | B | K) + static func calculate_M(group: Group, username: String, salt: Data, A: Data, B: Data, K: Data) -> Data { + let HN_xor_Hg = (H(group.N.serialize()) ^ H(group.g.serialize()))! + let HI = H(username.data(using: .utf8)!) + return H(HN_xor_Hg + HI + salt + A + B + K) + } + + //HAMK = H(A | M | K) + static func calculate_HAMK(A: Data, M: Data, K: Data) -> Data { + return H(A + M + K) + } + + //k = H(N | PAD(g)) + static func calculate_k(group: Group) -> BigUInt { + let size = group.N.serialize().count + return BigUInt(H(group.N.serialize() + pad(group.g.serialize(), to: size))) + } + + //x = H(s | H(I | ":" | P)) + static func calculate_x(salt: Data, username: String, password: String) -> BigUInt { + return BigUInt(H(salt + H("\(username):\(password)".data(using: .utf8)!))) } } + +// v = g^x % N +func calculate_v(group: Group, x: BigUInt) -> BigUInt { + return group.g.power(x, modulus: group.N) +} + +func randomBytes(_ count: Int) -> Data { + return Data((0..