From 9b107ec98ce64cc9772206e41210f589700c073a Mon Sep 17 00:00:00 2001 From: Anand Biligiri Date: Mon, 28 Oct 2024 13:25:17 -0700 Subject: [PATCH] SRP Login works now - Switch to use https://github.com/adam-fowler/swift-srp with some modifications that are local - Pad g value to equal size of N while calculating clientProof - Use SHA256(plain-text-password) while computing key using PBKDF2 - Added a unit test with some sample values --- Xcodes.xcodeproj/project.pbxproj | 10 +- .../xcshareddata/swiftpm/Package.resolved | 8 +- Xcodes/AppleAPI/Package.swift | 6 +- Xcodes/AppleAPI/Sources/AppleAPI/Client.swift | 65 +++-- XcodesTests/AppStateUpdateTests.swift | 81 ++++++ swift-srp-main/.github/FUNDING.yml | 1 + swift-srp-main/.github/workflows/swift.yml | 39 +++ swift-srp-main/.gitignore | 7 + {xcodes-srp => swift-srp-main}/LICENSE | 0 {xcodes-srp => swift-srp-main}/Package.swift | 4 +- {xcodes-srp => swift-srp-main}/README.md | 0 .../Sources/SRP/Array.swift | 0 swift-srp-main/Sources/SRP/client.swift | 178 +++++++++++++ .../Sources/SRP/configuration.swift | 201 ++++++++++++++ .../Sources/SRP/error.swift | 0 swift-srp-main/Sources/SRP/keys.swift | 40 +++ swift-srp-main/Sources/SRP/server.swift | 110 ++++++++ swift-srp-main/Sources/SRP/srp.swift | 66 +++++ .../Tests/LinuxMain.swift | 0 .../Tests/SRPTests/SRPTests.swift | 0 .../Tests/SRPTests/XCTestManifests.swift | 0 .../contents.xcworkspacedata | 7 - xcodes-srp/Package.resolved | 34 --- xcodes-srp/Sources/SRP/client.swift | 224 ---------------- xcodes-srp/Sources/SRP/group.swift | 248 ------------------ xcodes-srp/Sources/SRP/srp.swift | 125 --------- 26 files changed, 778 insertions(+), 676 deletions(-) create mode 100644 swift-srp-main/.github/FUNDING.yml create mode 100644 swift-srp-main/.github/workflows/swift.yml create mode 100644 swift-srp-main/.gitignore rename {xcodes-srp => swift-srp-main}/LICENSE (100%) rename {xcodes-srp => swift-srp-main}/Package.swift (80%) rename {xcodes-srp => swift-srp-main}/README.md (100%) rename {xcodes-srp => swift-srp-main}/Sources/SRP/Array.swift (100%) create mode 100644 swift-srp-main/Sources/SRP/client.swift create mode 100644 swift-srp-main/Sources/SRP/configuration.swift rename {xcodes-srp => swift-srp-main}/Sources/SRP/error.swift (100%) create mode 100644 swift-srp-main/Sources/SRP/keys.swift create mode 100644 swift-srp-main/Sources/SRP/server.swift create mode 100644 swift-srp-main/Sources/SRP/srp.swift rename {xcodes-srp => swift-srp-main}/Tests/LinuxMain.swift (100%) rename {xcodes-srp => swift-srp-main}/Tests/SRPTests/SRPTests.swift (100%) rename {xcodes-srp => swift-srp-main}/Tests/SRPTests/XCTestManifests.swift (100%) delete mode 100644 xcodes-srp/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata delete mode 100644 xcodes-srp/Package.resolved delete mode 100644 xcodes-srp/Sources/SRP/client.swift delete mode 100644 xcodes-srp/Sources/SRP/group.swift delete mode 100644 xcodes-srp/Sources/SRP/srp.swift diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index 5e9ee95..b177caf 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 15F5B8902CCF09B900705E2F /* CryptoKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 15F5B88F2CCF09B900705E2F /* CryptoKit.framework */; }; 33027E342CA8C18800CB387C /* LibFido2Swift in Frameworks */ = {isa = PBXBuildFile; productRef = 334A932B2CA885A400A5E079 /* LibFido2Swift */; }; 3328073F2CA5E2C80036F691 /* SignInSecurityKeyPinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3328073E2CA5E2C80036F691 /* SignInSecurityKeyPinView.swift */; }; 332807412CA5EA820036F691 /* SignInSecurityKeyTouchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 332807402CA5EA820036F691 /* SignInSecurityKeyTouchView.swift */; }; @@ -196,6 +197,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 15F5B88F2CCF09B900705E2F /* CryptoKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CryptoKit.framework; path = System/Library/Frameworks/CryptoKit.framework; sourceTree = SDKROOT; }; 3328073E2CA5E2C80036F691 /* SignInSecurityKeyPinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInSecurityKeyPinView.swift; sourceTree = ""; }; 332807402CA5EA820036F691 /* SignInSecurityKeyTouchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInSecurityKeyTouchView.swift; sourceTree = ""; }; 36741BFC291E4FDB00A85AAE /* DownloadPreferencePane.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloadPreferencePane.swift; sourceTree = ""; }; @@ -352,6 +354,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 15F5B8902CCF09B900705E2F /* CryptoKit.framework in Frameworks */, 33027E342CA8C18800CB387C /* LibFido2Swift in Frameworks */, CABFA9E42592F08E00380FEE /* Version in Frameworks */, CABFA9FD2592F13300380FEE /* LegibleError in Frameworks */, @@ -416,6 +419,7 @@ CA538A12255A4F7C00E64DD7 /* Frameworks */ = { isa = PBXGroup; children = ( + 15F5B88F2CCF09B900705E2F /* CryptoKit.framework */, ); name = Frameworks; sourceTree = ""; @@ -815,7 +819,7 @@ E84E4F552B335094003F3959 /* XCRemoteSwiftPackageReference "swift-collections" */, E83FDC422CBB649100679C6B /* XCRemoteSwiftPackageReference "Sparkle" */, 33027E282CA8BB5800CB387C /* XCRemoteSwiftPackageReference "LibFido2Swift" */, - E862D4392CC8B26F00BAA376 /* XCLocalSwiftPackageReference "xcodes-srp" */, + 15F5B8912CCF44B700705E2F /* XCLocalSwiftPackageReference "swift-srp-main" */, ); productRefGroup = CAD2E79F2449574E00113D76 /* Products */; projectDirPath = ""; @@ -1485,9 +1489,9 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - E862D4392CC8B26F00BAA376 /* XCLocalSwiftPackageReference "xcodes-srp" */ = { + 15F5B8912CCF44B700705E2F /* XCLocalSwiftPackageReference "swift-srp-main" */ = { isa = XCLocalSwiftPackageReference; - relativePath = "xcodes-srp"; + relativePath = "swift-srp-main"; }; /* End XCLocalSwiftPackageReference section */ diff --git a/Xcodes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Xcodes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e76dc9c..89a660f 100644 --- a/Xcodes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Xcodes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -11,12 +11,12 @@ } }, { - "package": "BigInt", - "repositoryURL": "https://github.com/attaswift/BigInt", + "package": "big-num", + "repositoryURL": "https://github.com/adam-fowler/big-num", "state": { "branch": null, - "revision": "793a7fac0bfc318e85994bf6900652e827aef33e", - "version": "5.4.1" + "revision": "5c5511ad06aeb2b97d0868f7394e14a624bfb1c7", + "version": "2.0.2" } }, { diff --git a/Xcodes/AppleAPI/Package.swift b/Xcodes/AppleAPI/Package.swift index 500b9d1..02f3b5c 100644 --- a/Xcodes/AppleAPI/Package.swift +++ b/Xcodes/AppleAPI/Package.swift @@ -12,13 +12,15 @@ let package = Package( name: "AppleAPI", targets: ["AppleAPI"]), ], - dependencies: [], + dependencies: [ + .package(name: "swift-srp", path: "swift-srp-main") + ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "AppleAPI", - dependencies: []), + dependencies: [.product(name: "SRP", package: "swift-srp")]), .testTarget( name: "AppleAPITests", dependencies: ["AppleAPI"]), diff --git a/Xcodes/AppleAPI/Sources/AppleAPI/Client.swift b/Xcodes/AppleAPI/Sources/AppleAPI/Client.swift index 09f7c08..78c80ee 100644 --- a/Xcodes/AppleAPI/Sources/AppleAPI/Client.swift +++ b/Xcodes/AppleAPI/Sources/AppleAPI/Client.swift @@ -15,9 +15,10 @@ public class Client { public func srpLogin(accountName: String, password: String) -> AnyPublisher { var serviceKey: String! - let client = SRPClient(username: accountName, password: password) - let a = client.startAuthentication() - + let client = SRPClient(configuration: SRPConfiguration(.N2048)) + let clientKeys = client.generateKeys() + let a = clientKeys.public + return Current.network.dataTask(with: URLRequest.itcServiceKey) .map(\.data) .decode(type: ServiceKeyResponse.self, decoder: JSONDecoder()) @@ -33,7 +34,7 @@ public class Client { } .flatMap { (serviceKey, hashcash) -> AnyPublisher<(String, String, ServerSRPInitResponse), Swift.Error> in - return Current.network.dataTask(with: URLRequest.SRPInit(serviceKey: serviceKey, a: a.base64EncodedString(), accountName: accountName)) + return Current.network.dataTask(with: URLRequest.SRPInit(serviceKey: serviceKey, a: Data(a.bytes).base64EncodedString(), accountName: accountName)) .map(\.data) .decode(type: ServerSRPInitResponse.self, decoder: JSONDecoder()) .map { return (serviceKey, hashcash, $0) } @@ -62,18 +63,16 @@ public class Client { } // 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) + let encryptedPasswordString = encryptedPassword.base64EncodedString() + let sharedSecret = try client.calculateSharedSecret(password: encryptedPassword, salt: [UInt8](decodedSalt), clientKeys: clientKeys, serverPublicKey: .init([UInt8](decodedB))) +// let m1 = try client.processChallenge(salt: decodedSalt, publicKey: decodedB, encryptedPassword: encryptedPasswordString) - guard let m2 = client.HAMK else { - return Fail(error: AuthenticationError.srpInvalidPublicKey) - .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())) + let m1 = client.calculateClientProof(username: accountName, salt: [UInt8](decodedSalt), clientPublicKey: a, serverPublicKey: .init([UInt8](decodedB)), sharedSecret: .init(sharedSecret.bytes)) + let m2 = client.calculateServerProof(clientPublicKey: a, clientProof: m1, sharedSecret: .init([UInt8](sharedSecret.bytes))) + print("m1: \(Data(m1).base64EncodedString())") + print("m2: \(Data(m2).base64EncodedString())") + + 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() } catch { @@ -382,10 +381,19 @@ public class Client { .mapError { $0 as Error } .eraseToAnyPublisher() } - + + func sha256(data : Data) -> Data { + var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) + data.withUnsafeBytes { + _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash) + } + return Data(hash) + } + private func pbkdf2(password: String, saltData: Data, keyByteCount: Int, prf: CCPseudoRandomAlgorithm, rounds: Int) -> Data? { guard let passwordData = password.data(using: .utf8) else { return nil } - + let hashedPasswordData = sha256(data: passwordData) + var derivedKeyData = Data(repeating: 0, count: keyByteCount) let derivedCount = derivedKeyData.count let derivationStatus: Int32 = derivedKeyData.withUnsafeMutableBytes { derivedKeyBytes in @@ -393,16 +401,19 @@ public class Client { derivedKeyBytes.baseAddress!.assumingMemoryBound(to: UInt8.self) return saltData.withUnsafeBytes { saltBytes -> Int32 in let saltBuffer: UnsafePointer = saltBytes.baseAddress!.assumingMemoryBound(to: UInt8.self) - return CCKeyDerivationPBKDF( - CCPBKDFAlgorithm(kCCPBKDF2), - password, - passwordData.count, - saltBuffer, - saltData.count, - prf, - UInt32(rounds), - keyBuffer, - derivedCount) + return hashedPasswordData.withUnsafeBytes { hashedPasswordBytes -> Int32 in + let passwordBuffer: UnsafePointer = hashedPasswordBytes.baseAddress!.assumingMemoryBound(to: UInt8.self) + return CCKeyDerivationPBKDF( + CCPBKDFAlgorithm(kCCPBKDF2), + passwordBuffer, + hashedPasswordData.count, + saltBuffer, + saltData.count, + prf, + UInt32(rounds), + keyBuffer, + derivedCount) + } } } return derivationStatus == kCCSuccess ? derivedKeyData : nil diff --git a/XcodesTests/AppStateUpdateTests.swift b/XcodesTests/AppStateUpdateTests.swift index 39d0d21..b510652 100644 --- a/XcodesTests/AppStateUpdateTests.swift +++ b/XcodesTests/AppStateUpdateTests.swift @@ -1,7 +1,11 @@ import Path +import CryptoKit import Version @testable import Xcodes import XCTest +import CommonCrypto +import BigNum +import SRP class AppStateUpdateTests: XCTestCase { var subject: AppState! @@ -258,4 +262,81 @@ class AppStateUpdateTests: XCTestCase { XCTAssertEqual(subject.allXcodes.map(\.version), [Version("12.4.0")!, Version("12.3.0-RC")!]) XCTAssertEqual(subject.allXcodes.map(\.identicalBuilds), [[], []]) } + + func sha256(data : Data) -> Data { + var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) + data.withUnsafeBytes { + _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash) + } + return Data(hash) + } + + func testSIRP() throws { + /* + Obtaind by running fastlane spaceauth --verbose -u anand.appleseed@example.com with some custom logging + + Starting SIRP Apple ID login + a: {"a":"VLEKLa+n2cyeYNWbECm45CuS4kCdCxodlTDGlW1FKaUyOrv/RbtN2sM0pVE12zI7k3VkocPC3rN5DZBIkahR6I8JHj/J97dtTvzsR+aNRWTYCT2HGP1PBI0QArp3eitAbFqTWI4+Zw+oOnV8+AYdH/wjbq7gOK4C4dvIHE+FzRwIlmguPb5qu2r47R9W3y1msVdoUGlFBOMOMb7Gyq7F0MaEIFH63lNzGomwq74mfss/cFqurd6fxU+Y7tdVTPZw1GWyBEPiXWpk8sNm2zE+S6zWo5tOsICprU75IC9galh1igfzN7VNe0SUFLNFTbFK+Bb1SFAOrAbBZOmyOG5uSQ==","accountName":"anand.appleseed@example.com","protocols":["s2k","s2k_fo"]} + Received SIRP signin init response: {"iteration"=>20309, "salt"=>"fIjNflgqSJXACWwwvhDU+w==", "protocol"=>"s2k", "b"=>"PMbU75wwG6rDTySXn2ASWyfQuPoW5ham15SzIscpInwOPE2uk7sePsW4ra0dHcLDUMFQn/LgBggIKOo7YZ9hf1VReiAzXwSKSHdJHjHUURTC2eNpANGUPO1qzuXYgc/MP3MR+GipKHsz+KTLT+8wLjNaiCIHsL/7evJBMw9QqiwhyXlAIm5mGZfhdTVbGpLz2/QzrFmI6pUTrHpio6m1Q74DH3FBxxIeiIcuEdGdeVt9iUweowBRyf2woasTvSV1fbMQbl+lsWPwzt/a73+J30eOGFdSubqSVYh2pV0OLqRz7zPzJars12teCWUV+0WUIaxb14Mp7tlmqcTPuqZe9w==", "c"=>"d-533-eccbc4e9-9564-11ef-84a6-018111c8cc60:PRN"} + encrypted_password: 40532b4de9353fc537dc62ee84eacebd7ecb5ec26efca98bd01b0380e302100f 32 + bb: 7672345903537871991962715758896796468138571329014139041563495295907370682045347022183702983061785424983278857706335295545994877883818377653653442007828499221881058994644619578239367613808278802931379172730746665773282250642455690715139985911303055104847971308813151718669484181874342088801251592138154023949370621963319928723678385968989085032385411532317797659749008135679504901238396934480214258070495365760319076978872181485178648397361564241555189629889320567561713407566532187413091018319494367244540399242523126294027225387432028960726767445027313453858210115810946641002311734776929442587065438110307439763191 + x: 726436461883978586175291668001486484510457416477927591386889224605776454162 + u: 49415306980415573732801389514223606278850554555635359953422678270536095422201 + private 23161374166158551996079451276150657702422963034121842124445818241826569345033578345120284496449280736328513302994568402583647660370960353252836732307301957364261384324957527103960720408713825427474127658415917826326829664923997096839970397226662116904369925262192683131695683487505523842260218490007066160096482662715246662018133837725691586205535995663334471723776536640973591229093933458552240634178864509015968350855952558520147559154646484379002445961375384929682566525908284011230815908584648931495968206840416022306138033496705677078512266958633477047047323620540878121579549170392075029336954975132431417099801 + S: 4f75b6ea99c2d7d121357cce80c75c8e1bf74a65e8f66f75f8f66a481301afb8bebf0e54a3fac4f8bfdd60c77d6e670c87968b341f62175e25eb1d4f496e4e6596e1a387f2840688a35002419b70115b7902a46544cc7b31eb4c909c0acaeb752835d1562a687c431421510ebc7535c007a2bd12a4f7696c8c96a75a491b1eb9189ade2bef23dd5b0bb962b4f03e7fba7f6ba6fe67ba34cc18647daf3e474876f85dac5a15eb51c99d1ed78783579ffd6c8b6911f72564d87dc8f76519c8fc1535b83743ed5f7d6b8461d7154ce2a874cbb45bf63018352b9b997fbafbd6b15eac2a544a801c0152470796f3b69a84a4a653e5186b30efeeb148ff3c32ab8e08 + K: c5207f707186a52f1adee41bf0a7bc41e51e6dffc25cdaeca8acb7de2710b20a + hN: 65908899099613711898698321155848703477601840791750658211391687862255842366922 + hG: 23094592799618609623465742609366149076596436609130823198107630312273622653270 + hxor 73599884097654065452785151481733181870375477364472235597514429707329935690908 + response: {"accountName":"anand.appleseed@example.com","c":"d-533-eccbc4e9-9564-11ef-84a6-018111c8cc60:PRN","m1":"f/Bkq8gBTYxl7SyiRd4SXTyE/jM/g6E0mVyZIQDIsPg=","m2":"R2rgqC9cMAtWiXUImOrvs4oF+ccibf8KaFsZQ22WokM=","rememberMe":false} + */ + + let publicKey = Data(base64Encoded: "VLEKLa+n2cyeYNWbECm45CuS4kCdCxodlTDGlW1FKaUyOrv/RbtN2sM0pVE12zI7k3VkocPC3rN5DZBIkahR6I8JHj/J97dtTvzsR+aNRWTYCT2HGP1PBI0QArp3eitAbFqTWI4+Zw+oOnV8+AYdH/wjbq7gOK4C4dvIHE+FzRwIlmguPb5qu2r47R9W3y1msVdoUGlFBOMOMb7Gyq7F0MaEIFH63lNzGomwq74mfss/cFqurd6fxU+Y7tdVTPZw1GWyBEPiXWpk8sNm2zE+S6zWo5tOsICprU75IC9galh1igfzN7VNe0SUFLNFTbFK+Bb1SFAOrAbBZOmyOG5uSQ==".data(using: .utf8)!) + + let clientKeys = SRPKeyPair(public: .init([UInt8](publicKey!)), + private: .init(BigNum("23161374166158551996079451276150657702422963034121842124445818241826569345033578345120284496449280736328513302994568402583647660370960353252836732307301957364261384324957527103960720408713825427474127658415917826326829664923997096839970397226662116904369925262192683131695683487505523842260218490007066160096482662715246662018133837725691586205535995663334471723776536640973591229093933458552240634178864509015968350855952558520147559154646484379002445961375384929682566525908284011230815908584648931495968206840416022306138033496705677078512266958633477047047323620540878121579549170392075029336954975132431417099801")!)) + + let password = sha256(data: "example".data(using: .utf8)!) + let salt = Data(base64Encoded: "fIjNflgqSJXACWwwvhDU+w==".data(using: .utf8)!)! + let iterations: Int = 20309 + let derivedKeyLength: Int = 32 + var keyArray = Array(repeating: 0, count: derivedKeyLength) + + let result:Int32 = keyArray.withUnsafeMutableBytes { keyBytes -> Int32 in + let keyBuffer = UnsafeMutablePointer(keyBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)) + return password.withUnsafeBytes { passwordDigestBytes -> Int32 in + let passwordBuffer = UnsafePointer(passwordDigestBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)) + return salt.withUnsafeBytes { saltBytes -> Int32 in + let saltBuffer = UnsafePointer(saltBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)) + return CCKeyDerivationPBKDF( + CCPBKDFAlgorithm(kCCPBKDF2), + passwordBuffer, + password.count, + saltBuffer, + salt.count, + CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256), + UInt32(iterations), + keyBuffer, + derivedKeyLength) + + } + } + } + + let expectedKey: [UInt8] = [0x40, 0x53, 0x2b, 0x4d, 0xe9, 0x35, 0x3f, 0xc5, 0x37, 0xdc, 0x62, 0xee, 0x84, 0xea, 0xce, 0xbd, 0x7e, 0xcb, 0x5e, 0xc2, 0x6e, 0xfc, 0xa9, 0x8b, 0xd0, 0x1b, 0x03, 0x80, 0xe3, 0x02, 0x10, 0x0f] + + XCTAssertEqual(expectedKey, keyArray) + + let decodedB = Data(base64Encoded: "PMbU75wwG6rDTySXn2ASWyfQuPoW5ham15SzIscpInwOPE2uk7sePsW4ra0dHcLDUMFQn/LgBggIKOo7YZ9hf1VReiAzXwSKSHdJHjHUURTC2eNpANGUPO1qzuXYgc/MP3MR+GipKHsz+KTLT+8wLjNaiCIHsL/7evJBMw9QqiwhyXlAIm5mGZfhdTVbGpLz2/QzrFmI6pUTrHpio6m1Q74DH3FBxxIeiIcuEdGdeVt9iUweowBRyf2woasTvSV1fbMQbl+lsWPwzt/a73+J30eOGFdSubqSVYh2pV0OLqRz7zPzJars12teCWUV+0WUIaxb14Mp7tlmqcTPuqZe9w==".data(using: .utf8)!)! + + let client = SRPClient(configuration: SRPConfiguration(.N2048)) + let sharedSecret = try client.calculateSharedSecret(password: Data(keyArray), salt: [UInt8](salt), clientKeys: clientKeys, serverPublicKey: .init([UInt8](decodedB))) + + let accountName = "anand.appleseed@example.com" + let m1 = client.calculateClientProof(username: accountName, salt: [UInt8](salt), clientPublicKey: clientKeys.public, serverPublicKey: .init([UInt8](decodedB)), sharedSecret: .init(sharedSecret.bytes)) + let m2 = client.calculateServerProof(clientPublicKey: clientKeys.public, clientProof: m1, sharedSecret: .init([UInt8](sharedSecret.bytes))) + + XCTAssertEqual(Data(m1).base64EncodedString(), "f/Bkq8gBTYxl7SyiRd4SXTyE/jM/g6E0mVyZIQDIsPg=") + XCTAssertEqual(Data(m2).base64EncodedString(), "R2rgqC9cMAtWiXUImOrvs4oF+ccibf8KaFsZQ22WokM=") + } } diff --git a/swift-srp-main/.github/FUNDING.yml b/swift-srp-main/.github/FUNDING.yml new file mode 100644 index 0000000..7fc910b --- /dev/null +++ b/swift-srp-main/.github/FUNDING.yml @@ -0,0 +1 @@ +github: adam-fowler diff --git a/swift-srp-main/.github/workflows/swift.yml b/swift-srp-main/.github/workflows/swift.yml new file mode 100644 index 0000000..9a734f6 --- /dev/null +++ b/swift-srp-main/.github/workflows/swift.yml @@ -0,0 +1,39 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + release: + types: [published] + workflow_dispatch: + +jobs: + macOS: + runs-on: macOS-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + - name: Build + run: swift build + - name: Run tests + run: swift test + + linux: + runs-on: ubuntu-latest + strategy: + matrix: + image: ['swift:5.9', 'swift:5.10', 'swift:6.0'] + container: + image: ${{ matrix.image }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1 + - name: Test + run: swift test --parallel --enable-code-coverage diff --git a/swift-srp-main/.gitignore b/swift-srp-main/.gitignore new file mode 100644 index 0000000..4c741c0 --- /dev/null +++ b/swift-srp-main/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +/.build +/.swiftpm +/Packages +/*.xcodeproj +xcuserdata/ +Package.resolved \ No newline at end of file diff --git a/xcodes-srp/LICENSE b/swift-srp-main/LICENSE similarity index 100% rename from xcodes-srp/LICENSE rename to swift-srp-main/LICENSE diff --git a/xcodes-srp/Package.swift b/swift-srp-main/Package.swift similarity index 80% rename from xcodes-srp/Package.swift rename to swift-srp-main/Package.swift index 890d696..a427028 100644 --- a/xcodes-srp/Package.swift +++ b/swift-srp-main/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/attaswift/BigInt.git", from: "5.0.0") + .package(url: "https://github.com/adam-fowler/big-num", from: "2.0.0"), ], targets: [ - .target(name: "SRP", dependencies: ["Crypto", "BigInt"]), + .target(name: "SRP", dependencies: ["BigNum", "Crypto"]), .testTarget( name: "SRPTests", dependencies: ["SRP"]), ] diff --git a/xcodes-srp/README.md b/swift-srp-main/README.md similarity index 100% rename from xcodes-srp/README.md rename to swift-srp-main/README.md diff --git a/xcodes-srp/Sources/SRP/Array.swift b/swift-srp-main/Sources/SRP/Array.swift similarity index 100% rename from xcodes-srp/Sources/SRP/Array.swift rename to swift-srp-main/Sources/SRP/Array.swift diff --git a/swift-srp-main/Sources/SRP/client.swift b/swift-srp-main/Sources/SRP/client.swift new file mode 100644 index 0000000..d4c34b4 --- /dev/null +++ b/swift-srp-main/Sources/SRP/client.swift @@ -0,0 +1,178 @@ +import BigNum +import Crypto +import Foundation + +/// 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) + + 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) + } + + public func calculateSharedSecret(password: Data, salt: [UInt8], clientKeys: SRPKeyPair, serverPublicKey: SRPKey) throws -> SRPKey { + let message = [UInt8](Data([0x3a]) + password) + 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 + /// + /// - 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 + /// - 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 valid it will also return a server + /// verification code that the client can use to verify the server is correct. This is the calculation + /// to verify it is correct + /// + /// - Parameters: + /// - clientPublicKey: Client public key + /// - clientProof: Client proof + /// - sharedSecret: Shared secret + public func calculateServerProof(clientPublicKey: SRPKey, clientProof: [UInt8], sharedSecret: SRPKey) -> [UInt8] { + let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes)) + // get out version of server proof + return SRP.calculateServerVerification(clientPublicKey: clientPublicKey, clientProof: clientProof, sharedSecret: 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 + /// - clientProof: Client proof + /// - clientKeys: Client keys + /// - sharedSecret: Shared secret + /// - Throws: `requiresVerificationKey`, `invalidServerCode` + public func verifyServerProof(serverProof: [UInt8], clientProof: [UInt8], clientKeys: SRPKeyPair, sharedSecret: SRPKey) throws { + // get our version of server proof + let HAMK = calculateServerProof(clientPublicKey: clientKeys.public, clientProof: clientProof, sharedSecret: sharedSecret) + // is it the same + guard serverProof == HAMK else { throw SRPClientError.invalidServerCode } + } + + /// 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. + /// + /// - 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)) + } +} + +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 } + + // calculate u = H(clientPublicKey | serverPublicKey) + let u = SRP.calculateU(clientPublicKey: clientKeys.public.bytes, serverPublicKey: serverPublicKey.bytes, pad: configuration.sizeN) + + 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 + } +} diff --git a/swift-srp-main/Sources/SRP/configuration.swift b/swift-srp-main/Sources/SRP/configuration.swift new file mode 100644 index 0000000..a1fe01a --- /dev/null +++ b/swift-srp-main/Sources/SRP/configuration.swift @@ -0,0 +1,201 @@ +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/error.swift b/swift-srp-main/Sources/SRP/error.swift similarity index 100% rename from xcodes-srp/Sources/SRP/error.swift rename to swift-srp-main/Sources/SRP/error.swift diff --git a/swift-srp-main/Sources/SRP/keys.swift b/swift-srp-main/Sources/SRP/keys.swift new file mode 100644 index 0000000..c7d08a0 --- /dev/null +++ b/swift-srp-main/Sources/SRP/keys.swift @@ -0,0 +1,40 @@ +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/swift-srp-main/Sources/SRP/server.swift b/swift-srp-main/Sources/SRP/server.swift new file mode 100644 index 0000000..1e1931c --- /dev/null +++ b/swift-srp-main/Sources/SRP/server.swift @@ -0,0 +1,110 @@ +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: + /// - 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) + } + + /// 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 + /// - 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] { + 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/swift-srp-main/Sources/SRP/srp.swift b/swift-srp-main/Sources/SRP/srp.swift new file mode 100644 index 0000000..6e192c3 --- /dev/null +++ b/swift-srp-main/Sources/SRP/srp.swift @@ -0,0 +1,66 @@ +import BigNum +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)))) + } + + /// 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, + 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 hashN = BigNum(bytes: H.hash(data: configuration.N.bytes)) + let hashG = BigNum(bytes: H.hash(data: SRP.pad(configuration.g.bytes, to: configuration.sizeN))) + let N_xor_g = hashN.bytes ^ hashG.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) + } + + /// 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) + } +} diff --git a/xcodes-srp/Tests/LinuxMain.swift b/swift-srp-main/Tests/LinuxMain.swift similarity index 100% rename from xcodes-srp/Tests/LinuxMain.swift rename to swift-srp-main/Tests/LinuxMain.swift diff --git a/xcodes-srp/Tests/SRPTests/SRPTests.swift b/swift-srp-main/Tests/SRPTests/SRPTests.swift similarity index 100% rename from xcodes-srp/Tests/SRPTests/SRPTests.swift rename to swift-srp-main/Tests/SRPTests/SRPTests.swift diff --git a/xcodes-srp/Tests/SRPTests/XCTestManifests.swift b/swift-srp-main/Tests/SRPTests/XCTestManifests.swift similarity index 100% rename from xcodes-srp/Tests/SRPTests/XCTestManifests.swift rename to swift-srp-main/Tests/SRPTests/XCTestManifests.swift diff --git a/xcodes-srp/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/xcodes-srp/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/xcodes-srp/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/xcodes-srp/Package.resolved b/xcodes-srp/Package.resolved deleted file mode 100644 index 8c65785..0000000 --- a/xcodes-srp/Package.resolved +++ /dev/null @@ -1,34 +0,0 @@ -{ - "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/Sources/SRP/client.swift b/xcodes-srp/Sources/SRP/client.swift deleted file mode 100644 index 3870252..0000000 --- a/xcodes-srp/Sources/SRP/client.swift +++ /dev/null @@ -1,224 +0,0 @@ - -import Foundation -import BigInt -import Crypto - -/// 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 - - 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) - } - - /// Initialize the Client SRP party with a password. - /// - /// - Parameters: - /// - 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 - } - - /// Initialize the Client SRP party with a precomputed x. - /// - /// - Parameters: - /// - 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) - } - - /// 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: - /// - 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) - - 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 - } - - /// 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. - /// - /// - 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 - } -} - -/// Possible authentication failure modes. -public enum AuthenticationFailure: Error { - /// Security breach: the provided public key is empty (i.e. PK % N is zero). - case invalidPublicKey - - /// Invalid client state: call `processChallenge` before `verifySession`. - case missingChallenge - - /// 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/group.swift b/xcodes-srp/Sources/SRP/group.swift deleted file mode 100644 index 9712acb..0000000 --- a/xcodes-srp/Sources/SRP/group.swift +++ /dev/null @@ -1,248 +0,0 @@ -// -// 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/srp.swift b/xcodes-srp/Sources/SRP/srp.swift deleted file mode 100644 index 9ffdf5d..0000000 --- a/xcodes-srp/Sources/SRP/srp.swift +++ /dev/null @@ -1,125 +0,0 @@ -// -// Implementation.swift -// swift-srp -// -// Created by Matt Kiazyk on 2024-10-24. -// - - -import Foundation -import BigInt -import Crypto - -/// 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) -} - -/// 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)) - } - - //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))) - } - - //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..