diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml deleted file mode 100644 index 120d1e8..0000000 --- a/.github/workflows/publish-docs.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Publish Docs -on: - push: - branches: - - master -jobs: - Publish: - runs-on: macos-latest - steps: - - uses: actions/checkout@v1 - - name: Publish Jazzy Docs - uses: Steven0351/publish-jazzy-docs@v1.1.2 - with: - config: .jazzy.yaml - personal_access_token: ${{ github.actor }}:${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml deleted file mode 100644 index 49d3162..0000000 --- a/.github/workflows/sonarcloud.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: SonarCloud -on: - push: - branches: [ master ] - pull_request: -jobs: - SonarCloud: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - # Disabling shallow clone is recommended for improving relevancy of reporting - fetch-depth: 0 - - name: SonarCloud Scan - uses: sonarsource/sonarcloud-github-action@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/swiftlint.yml b/.github/workflows/swiftlint.yml deleted file mode 100644 index 1d82697..0000000 --- a/.github/workflows/swiftlint.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: SwiftLint -on: - push: - branches: [ master ] - pull_request: -jobs: - SwiftLint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: SwiftLint - uses: norio-nomura/action-swiftlint@3.1.0 - with: - args: --strict diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index c03cc0c..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Test -on: - push: - branches: [ master ] - pull_request: -jobs: - Ubuntu: - runs-on: ubuntu-18.04 - strategy: - matrix: - swift: ['5.1', '5.2'] - steps: - - uses: actions/checkout@v2 - - name: Setup Swift ${{ matrix.swift }} - run: | - wget https://swift.org/builds/swift-${{ matrix.swift }}-release/ubuntu1804/swift-${{ matrix.swift }}-RELEASE/swift-${{ matrix.swift }}-RELEASE-ubuntu18.04.tar.gz - tar xzf swift-${{ matrix.swift }}-RELEASE-ubuntu18.04.tar.gz - export PATH=`pwd`/swift-${{ matrix.swift }}-RELEASE-ubuntu18.04/usr/bin:"${PATH}" - - name: Run Tests - run: swift test -c release -Xswiftc -enable-testing - macOS: - runs-on: macos-latest - continue-on-error: ${{ matrix.swift == '5.3' }} - strategy: - matrix: - swift: ['5.1', '5.2', '5.3'] - steps: - - uses: actions/checkout@v2 - - name: Setup Swift 5.1 - run: sudo xcode-select -s /Applications/Xcode_11.3.1.app/Contents/Developer - if: matrix.swift == '5.1' - - name: Setup Swift 5.2 - run: sudo xcode-select -s /Applications/Xcode_11.6.app/Contents/Developer - if: matrix.swift == '5.2' - - name: Setup Swift 5.3 - run: sudo xcode-select -s /Applications/Xcode_12_beta.app/Contents/Developer - if: matrix.swift == '5.3' - - name: Run Tests - run: swift test -c release -Xswiftc -enable-testing diff --git a/.jazzy.yaml b/.jazzy.yaml deleted file mode 100644 index b6cd25d..0000000 --- a/.jazzy.yaml +++ /dev/null @@ -1,11 +0,0 @@ -xcodebuild_arguments: - - -scheme - - SRP -module: SRP -author: Bouke Haarsma -author_url: https://twitter.com/BoukeHaarsma -output: ../SRP-Docs/master -theme: fullwidth -clean: true -github_url: https://github.com/Bouke/SRP -dash_url: http://boukehaarsma.nl/SRP/SRP.xml diff --git a/.swiftlint.yml b/.swiftlint.yml deleted file mode 100644 index 1bca665..0000000 --- a/.swiftlint.yml +++ /dev/null @@ -1,37 +0,0 @@ -disabled_rules: - - force_try - - function_body_length - - function_parameter_count - - line_length - - opening_brace -included: - - Sources - - Tests -identifier_name: - excluded: - - a - - A - - b - - B - - g - - H - - HI - - HN_xor_Hg - - M - - k - - K - - K0 - - K1 - - N - - N1024 - - N2048 - - N1536 - - N3072 - - N4096 - - N6144 - - N8192 - - s - - S - - u - - v - - x diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/.swiftpm/xcode/package.xcworkspace/xcuserdata/kbkarimov.xcuserdatad/UserInterfaceState.xcuserstate b/.swiftpm/xcode/package.xcworkspace/xcuserdata/kbkarimov.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..f0b58dc Binary files /dev/null and b/.swiftpm/xcode/package.xcworkspace/xcuserdata/kbkarimov.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/.swiftpm/xcode/xcuserdata/kbkarimov.xcuserdatad/xcschemes/xcschememanagement.plist b/.swiftpm/xcode/xcuserdata/kbkarimov.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..afb9544 --- /dev/null +++ b/.swiftpm/xcode/xcuserdata/kbkarimov.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,30 @@ + + + + + SchemeUserState + + Demo (Playground) 1.xcscheme + + isShown + + orderHint + 1 + + Demo (Playground) 2.xcscheme + + isShown + + orderHint + 2 + + Demo (Playground).xcscheme + + isShown + + orderHint + 0 + + + + diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8559269..0000000 --- a/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -branches: - only: - - master -language: generic -sudo: required -script: swift test -c release -Xswiftc -enable-testing -matrix: - include: - - name: Swift 5.1 on macOS 10.14 - os: osx - osx_image: xcode11.3 - diff --git a/CHANGELOG.md b/CHANGELOG.md index 14051e2..b00bc22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.2.0 - 2021-02-25 +### Changes +- Implemented Nimbus and Thinbus client compatibility + ## 3.1.0 - 2018-10-20 ### Changes - Upgrade BlueCryptor to 1.x for Xcode 10 compatibility diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 23cc400..0000000 --- a/Gemfile +++ /dev/null @@ -1,3 +0,0 @@ -source "https://rubygems.org" - -gem "jazzy" diff --git a/Makefile b/Makefile deleted file mode 100755 index 232dc9d..0000000 --- a/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -test: - swift test -c release -Xswiftc -enable-testing -test-with-python: - PYTHON=`which python` swift test -c release -Xswiftc -enable-testing -debug-test: - PYTHON=`which python` swift test -c release -Xswiftc -enable-testing -Xswiftc -D -Xswiftc DEBUG diff --git a/README.md b/README.md index 9fc03c5..2641067 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,6 @@ Secure Remote Password is a authentication protocol to prove your identity to another party, using a password, but without ever revealing that password to other parties. Not even the party you are proving your identity. See [Secure Remote Password protocol][5] for more information on this protocol. -![CI status](https://github.com/Bouke/SRP/workflows/Test/badge.svg) - ## Example usage ```swift @@ -63,14 +61,8 @@ low failure rates due to the randomness this protocol includes. * Python: ❌ [srp][2] is not compatible; it doesn't correctly calculate `k`. * Python: ✅ [srptools][3] is compatible. - -## Development - -### Testing - -This project includes unit tests. A few compiler flags are required to run the tests swiftly: - - swift test -c release -Xswiftc -enable-testing +* Nimbus: ✅ [nimbus][7] is compatible. +* Thinbus: ✅ [thinbus][8] is compatible. ## References @@ -79,7 +71,7 @@ This project includes unit tests. A few compiler flags are required to run the t ## Credits -This library was written by [Bouke Haarsma][4]. +This library was written originally by [Bouke Haarsma][4] and improved by [Karim Karimov][6]. [0]: https://tools.ietf.org/html/rfc2945 [1]: https://tools.ietf.org/html/rfc5054 @@ -87,3 +79,6 @@ This library was written by [Bouke Haarsma][4]. [3]: https://pypi.python.org/pypi/srptools [4]: https://twitter.com/BoukeHaarsma [5]: https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol +[6]: https://github.com/kerimovscreations +[7]: https://connect2id.com/products/nimbus-srp +[8]: https://github.com/simbo1905/thinbus-srp-npm diff --git a/Sources/Client.swift b/Sources/Client.swift index a31d3ba..23f3c5c 100644 --- a/Sources/Client.swift +++ b/Sources/Client.swift @@ -36,7 +36,7 @@ public class Client { if let privateKey = privateKey { a = BigUInt(privateKey) } else { - a = BigUInt(Data(bytes: try! Random.generate(byteCount: 128))) + a = BigUInt(Data(try! Random.generate(byteCount: max(32, group.getNSize())))) } // A = g^a % N A = group.g.power(a, modulus: group.N) @@ -112,7 +112,7 @@ public class Client { /// - 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) throws -> Data { + public func processChallenge(clientType: ClientType, salt: Data, publicKey serverPublicKey: Data) throws -> Data { let H = Digest.hasher(algorithm) let N = group.N @@ -121,15 +121,29 @@ public class Client { guard B % N != 0 else { throw AuthenticationFailure.invalidPublicKey } - - let u = calculate_u(group: group, algorithm: algorithm, A: publicKey, B: serverPublicKey) + let k = calculate_k(group: group, algorithm: algorithm) - let x = self.precomputedX ?? calculate_x(algorithm: algorithm, salt: salt, username: username, password: password!) - let v = calculate_v(group: group, x: x) + let u: BigUInt + let x: BigUInt + + switch clientType { + case .nimbus: + u = calculate_u(group: group, algorithm: algorithm, A: publicKey, B: serverPublicKey) + x = self.precomputedX ?? calculate_x_nimbus(algorithm: algorithm, salt: salt, password: password!) + case .thinbus: + u = calculate_u_thinbus(group: group, algorithm: algorithm, A: publicKey, B: serverPublicKey) + x = self.precomputedX ?? calculate_x_thinbus(group: group, algorithm: algorithm, salt: salt, username: username, password: password!) + case .srptools: + u = calculate_u(group: group, algorithm: algorithm, A: publicKey, B: serverPublicKey) + x = self.precomputedX ?? calculate_x(algorithm: algorithm, 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 + // 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) @@ -138,7 +152,16 @@ public class Client { K = H(S.serialize()) // client verification - let M = calculate_M(group: group, algorithm: algorithm, username: username, salt: salt, A: publicKey, B: serverPublicKey, K: K!) + let M: Data + + switch clientType { + case .nimbus: + M = calculate_M_nimbus(group: group, algorithm: algorithm, A: publicKey, B: serverPublicKey, S: S.serialize()) + case .thinbus: + M = calculate_M_thinbus(group: group, algorithm: algorithm, A: publicKey, B: serverPublicKey, S: S.serialize()) + case .srptools: + M = calculate_M(group: group, algorithm: algorithm, username: username, salt: salt, A: publicKey, B: serverPublicKey, K: K!) + } // server verification HAMK = calculate_HAMK(algorithm: algorithm, A: publicKey, M: M, K: K!) diff --git a/Sources/ClientType.swift b/Sources/ClientType.swift new file mode 100644 index 0000000..7d4e080 --- /dev/null +++ b/Sources/ClientType.swift @@ -0,0 +1,12 @@ +// +// File.swift +// +// +// Created by Karim Karimov on 25.02.21. +// + +import Foundation + +public enum ClientType { + case nimbus, thinbus, srptools +} diff --git a/Sources/Data+Extensions.swift b/Sources/Data+Extensions.swift index 077145a..5e5bfd2 100644 --- a/Sources/Data+Extensions.swift +++ b/Sources/Data+Extensions.swift @@ -15,3 +15,21 @@ func + (lhs: Data, rhs: Data) -> Data { result.append(rhs) return result } + +extension Data { + public var hexadecimalString : String { + var str = "" + enumerateBytes { buffer, index, stop in + for byte in buffer { + str.append(String(format:"%02x",byte)) + } + } + return str + } +} + +extension NSData { + public var hexadecimalString : String { + return (self as Data).hexadecimalString + } +} diff --git a/Sources/Group.swift b/Sources/Group.swift index 4ba1f1c..156488b 100644 --- a/Sources/Group.swift +++ b/Sources/Group.swift @@ -26,6 +26,9 @@ import BigInt /// Network and Distributed Systems Security, San Diego, CA, /// pp. 97-111. public enum Group { + /// 256-bits group + case N256 + /// 1024-bits group case N1024 @@ -71,6 +74,10 @@ public enum Group { var N: BigUInt { switch self { + case .N256: + return BigUInt( + "115B8B692E0E045692CF280B436735C77A5A9E8A9E7ED56C965F87DB5B2A2ECE3", + radix: 16)! case .N1024: return BigUInt( "EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C" + @@ -216,9 +223,15 @@ public enum Group { return custom.N } } + + public func getNSize() -> Int { + return N.serialize().count + } var g: BigUInt { switch self { + case .N256: + return BigUInt(2) case .N1024: return BigUInt(2) case .N1536: diff --git a/Sources/SRP.swift b/Sources/SRP.swift index 582c536..5b840db 100644 --- a/Sources/SRP.swift +++ b/Sources/SRP.swift @@ -26,8 +26,8 @@ public func createSaltedVerificationKey( algorithm: Digest.Algorithm = .sha1) -> (salt: Data, verificationKey: Data) { - let salt = salt ?? Data(bytes: try! Random.generate(byteCount: 16)) - let x = calculate_x(algorithm: algorithm, salt: salt, username: username, password: password) + let salt = salt ?? Data(try! Random.generate(byteCount: 16)) + let x = calculate_x_thinbus(group: group, algorithm: algorithm, salt: salt, username: username, password: password) return createSaltedVerificationKey(from: x, salt: salt, group: group) } @@ -60,7 +60,7 @@ func createSaltedVerificationKey( group: Group = .N2048) -> (salt: Data, verificationKey: Data) { - let salt = salt ?? Data(bytes: try! Random.generate(byteCount: 16)) + let salt = salt ?? Data(try! Random.generate(byteCount: 16)) let v = calculate_v(group: group, x: x) return (salt, v.serialize()) } @@ -77,6 +77,14 @@ func calculate_u(group: Group, algorithm: Digest.Algorithm, A: Data, B: Data) -> return BigUInt(H(pad(A, to: size) + pad(B, to: size))) } +//u = H(A | B) +func calculate_u_thinbus(group: Group, algorithm: Digest.Algorithm, A: Data, B: Data) -> BigUInt { + let H = Digest.hasher(algorithm) + let Adata = A.hexadecimalString.data(using: .utf8)! + let Bdata = B.hexadecimalString.data(using: .utf8)! + return BigUInt(H(Adata + Bdata)) +} + //M1 = H(H(N) XOR H(g) | H(I) | s | A | B | K) func calculate_M(group: Group, algorithm: Digest.Algorithm, username: String, salt: Data, A: Data, B: Data, K: Data) -> Data { let H = Digest.hasher(algorithm) @@ -85,6 +93,21 @@ func calculate_M(group: Group, algorithm: Digest.Algorithm, username: String, sa return H(HN_xor_Hg + HI + salt + A + B + K) } +//M1 = H(A | B | S) +func calculate_M_nimbus(group: Group, algorithm: Digest.Algorithm, A: Data, B: Data, S: Data) -> Data { + let H = Digest.hasher(algorithm) + return H(A + B + S) +} + +//M1 = H(A | B | S) +func calculate_M_thinbus(group: Group, algorithm: Digest.Algorithm, A: Data, B: Data, S: Data) -> Data { + let H = Digest.hasher(algorithm) + let Adata = A.hexadecimalString.data(using: .utf8)! + let Bdata = B.hexadecimalString.data(using: .utf8)! + let Sdata = S.hexadecimalString.data(using: .utf8)! + return H(Adata + Bdata + Sdata) +} + //HAMK = H(A | M | K) func calculate_HAMK(algorithm: Digest.Algorithm, A: Data, M: Data, K: Data) -> Data { let H = Digest.hasher(algorithm) @@ -104,6 +127,33 @@ func calculate_x(algorithm: Digest.Algorithm, salt: Data, username: String, pass return BigUInt(H(salt + H("\(username):\(password)".data(using: .utf8)!))) } +//x = H(s | H(I | ":" | P)) +func calculate_x_thinbus(group: Group, algorithm: Digest.Algorithm, salt: Data, username: String, password: String) -> BigUInt { + let H = Digest.hasher(algorithm) + + var hash1 = H("\(username):\(password)".data(using: .utf8)!) + + if hash1[0] == 0 { + hash1.remove(at: 0) + } + + let hash1S = hash1.hexadecimalString + + var hash = H("\(salt.hexadecimalString)\(hash1S)".uppercased().data(using: .utf8)!) + + if hash[0] == 0 { + hash.remove(at: 0) + } + + return BigUInt(hash) % group.N +} + +//x = H(s | H(P)) +func calculate_x_nimbus(algorithm: Digest.Algorithm, salt: Data, password: String) -> BigUInt { + let H = Digest.hasher(algorithm) + return BigUInt(H(salt + H(password.data(using: .utf8)!))) +} + // v = g^x % N func calculate_v(group: Group, x: BigUInt) -> BigUInt { return group.g.power(x, modulus: group.N) diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift deleted file mode 100644 index 9310738..0000000 --- a/Tests/LinuxMain.swift +++ /dev/null @@ -1,8 +0,0 @@ -import XCTest -@testable import SRPTests - -XCTMain([ - testCase(SRPTests.allTests), - testCase(PySrptoolsTests.allTests), - testCase(ReadmeTests.allTests) -]) diff --git a/Tests/SRPTests/PySrptoolsTests.swift b/Tests/SRPTests/PySrptoolsTests.swift deleted file mode 100644 index 34540db..0000000 --- a/Tests/SRPTests/PySrptoolsTests.swift +++ /dev/null @@ -1,279 +0,0 @@ -import Cryptor -import Foundation -import XCTest - -@testable import SRP - -class PySrptoolsTests: XCTestCase { - static var allTests: [(String, (PySrptoolsTests) -> () throws -> Void)] { - return [ - ("testClient", testClient), - ("testClientUtf8", testClientUtf8), - ("testServer", testServer), - ("testServerUtf8", testServerUtf8), - ("testLinuxTestSuiteIncludesAllTests", testLinuxTestSuiteIncludesAllTests) - ] - } - - func testClient() { - guard ProcessInfo.processInfo.environment["PYTHON"] != nil else { - return NSLog("Skipped integration test at \(#file):\(#line)") - } - - runClientTest(group: .N1024, algorithm: .sha1, username: "bouke", password: "test") - runClientTest(group: .N2048, algorithm: .sha1, username: "bouke", password: "test") - runClientTest(group: .N3072, algorithm: .sha1, username: "bouke", password: "test") - runClientTest(group: .N4096, algorithm: .sha1, username: "bouke", password: "test") - runClientTest(group: .N6144, algorithm: .sha1, username: "bouke", password: "test") - runClientTest(group: .N8192, algorithm: .sha1, username: "bouke", password: "test") - - runClientTest(group: .N1024, algorithm: .sha256, username: "bouke", password: "test") - runClientTest(group: .N2048, algorithm: .sha256, username: "bouke", password: "test") - runClientTest(group: .N3072, algorithm: .sha256, username: "bouke", password: "test") - runClientTest(group: .N4096, algorithm: .sha256, username: "bouke", password: "test") - runClientTest(group: .N6144, algorithm: .sha256, username: "bouke", password: "test") - runClientTest(group: .N8192, algorithm: .sha256, username: "bouke", password: "test") - } - - func testClientUtf8() { - guard ProcessInfo.processInfo.environment["PYTHON"] != nil else { - return NSLog("Skipped integration test at \(#file):\(#line)") - } - - runClientTest(group: .N1024, algorithm: .sha1, username: "bõūkę", password: "tėšt") - } - - func runClientTest( - group: Group, - algorithm: Digest.Algorithm, - username: String, - password: String, - file: StaticString = #file, - line: UInt = #line) - { - let server: RemoteServer - do { - server = try RemoteServer(username: username, - password: password, - group: group, - algorithm: algorithm) - } catch { - return XCTFail("Could not start remote server: \(error)", file: file, line: line) - } - let client = Client(username: username, - password: password, - group: group, - algorithm: algorithm) - - let debugInfo: () -> String = { - let infos: [String] = [ - "username: \(username)", - "password: \(password)", - "group: \(group)", - "algorithm: \(algorithm)", - "salt: \(server.salt?.hex ?? "N/A")", - "verificationKey: \(server.verificationKey?.hex ?? "N/A")", - "serverPrivateKey: \(server.privateKey?.hex ?? "N/A")", - "serverPublicKey: \(server.publicKey?.hex ?? "N/A")", - "clientPrivateKey: \(client.privateKey.hex)", - "clientPublicKey: \(client.publicKey.hex)", - "expected client M: \(server.expectedM?.hex ?? "N/A")", - "expected server HAMK: \(client.HAMK?.hex ?? "N/A")", - "clientK: \(client.K?.hex ?? "N/A")" - ] - return infos.joined(separator: "\n") - } - - var additionalDebug: String = "" - - // The server generates the challenge: pre-defined salt, public key B - // Server->Client: salt, B - let s: Data - let B: Data - do { - (s, B) = try server.getChallenge(publicKey: client.publicKey) - } catch { - return XCTFail("Server didn't return a challenge: \(error) -- \(debugInfo())", file: file, line: line) - } - - // Using (salt, B), the client generates the proof M - // Client->Server: M - let M: Data - do { - M = try client.processChallenge(salt: s, publicKey: B) - additionalDebug += "\nclientM: \(M.hex)" - } catch { - return XCTFail("Client couldn't process challenge: \(error) -- \(debugInfo())", file: file, line: line) - } - - // Using M, the server verifies the proof and calculates a proof for the client - // Server->Client: H(AMK) - let HAMK: Data - do { - HAMK = try server.verifySession(keyProof: M) - additionalDebug += "\nserverHAMK: \(HAMK.hex)" - } catch { - return XCTFail("Server couldn't verify the session: \(error) -- \(debugInfo())\(additionalDebug)", file: file, line: line) - } - - // Using H(AMK), the client verifies the server's proof - do { - try client.verifySession(keyProof: HAMK) - } catch { - return XCTFail("Client couldn't verify the session: \(error) -- \(debugInfo())\(additionalDebug)", file: file, line: line) - } - - // At this point, the client is authenticated as well - XCTAssert(client.isAuthenticated) - - // They now share a secret session key - let serverSessionKey: Data - do { - serverSessionKey = try server.getSessionKey() - additionalDebug += "\nserverK: \(serverSessionKey.hex)" - } catch { - return XCTFail("Server didn't provide a session key: \(error) -- \(debugInfo())\(additionalDebug)", file: file, line: line) - } - - XCTAssertEqual(serverSessionKey, client.sessionKey, "Session keys not equal -- \(debugInfo())\(additionalDebug)", file: file, line: line) - } - - func testServer() { - guard ProcessInfo.processInfo.environment["PYTHON"] != nil else { - return NSLog("Skipped integration test at \(#file):\(#line)") - } - - runServerTest(group: .N1024, algorithm: .sha1, username: "bouke", password: "test") - runServerTest(group: .N2048, algorithm: .sha1, username: "bouke", password: "test") - runServerTest(group: .N3072, algorithm: .sha1, username: "bouke", password: "test") - runServerTest(group: .N4096, algorithm: .sha1, username: "bouke", password: "test") - runServerTest(group: .N6144, algorithm: .sha1, username: "bouke", password: "test") - runServerTest(group: .N8192, algorithm: .sha1, username: "bouke", password: "test") - - runServerTest(group: .N1024, algorithm: .sha256, username: "bouke", password: "test") - runServerTest(group: .N2048, algorithm: .sha256, username: "bouke", password: "test") - runServerTest(group: .N3072, algorithm: .sha256, username: "bouke", password: "test") - runServerTest(group: .N4096, algorithm: .sha256, username: "bouke", password: "test") - runServerTest(group: .N6144, algorithm: .sha256, username: "bouke", password: "test") - runServerTest(group: .N8192, algorithm: .sha256, username: "bouke", password: "test") - } - - func testServerUtf8() { - guard ProcessInfo.processInfo.environment["PYTHON"] != nil else { - return NSLog("Skipped integration test at \(#file):\(#line)") - } - - runServerTest(group: .N1024, algorithm: .sha1, username: "bõūkę", password: "tėšt") - } - - func runServerTest( - group: Group, - algorithm: Digest.Algorithm, - username: String, - password: String, - file: StaticString = #file, - line: UInt = #line) - { - let (salt, verificationKey) = createSaltedVerificationKey(username: username, - password: password, - group: group, - algorithm: algorithm) - let server = Server(username: username, - salt: salt, - verificationKey: verificationKey, - group: group, - algorithm: algorithm) - - let client: RemoteClient - do { - client = try RemoteClient(username: username, - password: password, - group: group, - algorithm: algorithm) - } catch { - return XCTFail("Could not start remote client: \(error)", file: file, line: line) - } - - let debugInfo: () -> String = { - let infos: [String] = [ - "username: \(username)", - "password: \(password)", - "group: \(group)", - "algorithm: \(algorithm)", - "salt: \(salt.hex)", - "verificationKey: \(verificationKey.hex)", - "serverPrivateKey: \(server.privateKey.hex)", - "serverPublicKey: \(server.publicKey.hex)", - "clientPrivateKey: \(client.privateKey?.hex ?? "N/A")", - "clientPublicKey: \(client.publicKey?.hex ?? "N/A")" - ] - return infos.joined(separator: ", ") - } - - var additionalDebug: String = "" - - let A: Data - do { - (_, A) = try client.startAuthentication() - } catch { - return XCTFail("Client didn't return public key: \(error) -- \(debugInfo())", file: file, line: line) - } - - // The server generates the challenge: pre-defined salt, public key B - // Server->Client: salt, B - let (s, B) = server.getChallenge() - - // Using (salt, B), the client generates the proof M - // Client->Server: M - let M: Data - do { - M = try client.processChallenge(salt: s, publicKey: B) - additionalDebug += "\nclientM: \(M.hex)" - } catch { - return XCTFail("Client couldn't process challenge: \(error) -- \(debugInfo())\(additionalDebug)", file: file, line: line) - } - - // Using M, the server verifies the proof and calculates a proof for the client - // Server->Client: H(AMK) - let HAMK: Data - do { - HAMK = try server.verifySession(publicKey: A, keyProof: M) - additionalDebug += "\nserverHAMK: \(HAMK.hex)" - } catch { - return XCTFail("Server couldn't verify the session: \(error) -- \(debugInfo())\(additionalDebug)", file: file, line: line) - } - - // At this point, the server is authenticated - XCTAssert(server.isAuthenticated) - - // Using H(AMK), the client verifies the server's proof - do { - try client.verifySession(keyProof: HAMK) - } catch { - return XCTFail("Client couldn't verify the session: \(error) -- \(debugInfo())\(additionalDebug)", file: file, line: line) - } - - // They now share a secret session key - let clientSessionKey: Data - do { - clientSessionKey = try client.getSessionKey() - } catch { - return XCTFail("Client didn't provide a session key: \(error) -- \(debugInfo())\(additionalDebug)", file: file, line: line) - } - - XCTAssertEqual(server.sessionKey, clientSessionKey, "Session keys not equal") - } - - // from: https://oleb.net/blog/2017/03/keeping-xctest-in-sync/#appendix-code-generation-with-sourcery - func testLinuxTestSuiteIncludesAllTests() { - #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) - let thisClass = type(of: self) - let linuxCount = thisClass.allTests.count - let darwinCount = Int(thisClass - .defaultTestSuite.testCaseCount) - XCTAssertEqual(linuxCount, - darwinCount, - "\(darwinCount - linuxCount) tests are missing from allTests") - #endif - } -} diff --git a/Tests/SRPTests/ReadmeTests.swift b/Tests/SRPTests/ReadmeTests.swift deleted file mode 100644 index 16365ad..0000000 --- a/Tests/SRPTests/ReadmeTests.swift +++ /dev/null @@ -1,82 +0,0 @@ -import Foundation -import Cryptor -import SRP -import BigInt -import XCTest - -class ReadmeTests: XCTestCase { - static var allTests: [(String, (ReadmeTests) -> () throws -> Void)] { - return [ - ("test", test), - ("testGivenSRPX", testGivenSRPX), - ("testLinuxTestSuiteIncludesAllTests", testLinuxTestSuiteIncludesAllTests) - ] - } - - func test() throws { - // This is a database of users, along with their salted verification keys - let userStore: [String: (salt: Data, verificationKey: Data)] = [ - "alice": createSaltedVerificationKey(username: "alice", password: "password123"), - "bob": createSaltedVerificationKey(username: "bob", password: "qwerty12345") - ] - // Alice wants to authenticate, she sends her username to the server. - let client = Client(username: "alice", password: "password123") - try runCommonTest(client: client, userStore: userStore) - } - - func testGivenSRPX() throws { - // This is a database of users, along with their salted verification keys - let userStore: [String: (salt: Data, verificationKey: Data)] = [ - "alice": createSaltedVerificationKey(from: Data("12345".utf8)), - "bob": createSaltedVerificationKey(from: Data("67890".utf8)) - ] - - // Alice wants to authenticate, she sends her username to the server. - let client = Client(username: "alice", precomputedX: Data("12345".utf8)) - try runCommonTest(client: client, userStore: userStore) - } - - func runCommonTest(client: Client, userStore: [String: (salt: Data, verificationKey: Data)]) throws { - // Alice wants to authenticate - let (username, clientPublicKey) = client.startAuthentication() - - let server = Server( - username: username, - salt: userStore[username]!.salt, - verificationKey: userStore[username]!.verificationKey) - - // The server shares Alice's salt and its public key (the challenge). - let (salt, serverPublicKey) = server.getChallenge() - - // Alice generates a sessionKey and proofs she generated the correct - // session key based on her password and the challenge. - let clientKeyProof = try client.processChallenge(salt: salt, publicKey: serverPublicKey) - - // The server verifies Alices' proof and generates their proof. - let serverKeyProof = try server.verifySession(publicKey: clientPublicKey, keyProof: clientKeyProof) - - // The client verifies the server's proof. - try client.verifySession(keyProof: serverKeyProof) - - // At this point, authentication has completed. - assert(server.isAuthenticated) - assert(client.isAuthenticated) - - // Both now have the same session key. This key can be used to encrypt - // further communication between client and server. - assert(server.sessionKey == client.sessionKey) - } - - // from: https://oleb.net/blog/2017/03/keeping-xctest-in-sync/#appendix-code-generation-with-sourcery - func testLinuxTestSuiteIncludesAllTests() { - #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) - let thisClass = type(of: self) - let linuxCount = thisClass.allTests.count - let darwinCount = Int(thisClass - .defaultTestSuite.testCaseCount) - XCTAssertEqual(linuxCount, - darwinCount, - "\(darwinCount - linuxCount) tests are missing from allTests") - #endif - } -} diff --git a/Tests/SRPTests/SRPTests.swift b/Tests/SRPTests/SRPTests.swift deleted file mode 100644 index cd866a2..0000000 --- a/Tests/SRPTests/SRPTests.swift +++ /dev/null @@ -1,248 +0,0 @@ -import Foundation -import Cryptor -import SRP -import BigInt -import XCTest - -class SRPTests: XCTestCase { - static var allTests: [(String, (SRPTests) -> () throws -> Void)] { - return [ - ("testSHA1", testSHA1), - ("testSHA256", testSHA256), - ("testCustomGroupParameters", testCustomGroupParameters), - ("testUtf8", testUtf8), - ("testClientAborts", testClientAborts), - ("testClientGivenSRPXAborts", testClientGivenSRPXAborts), - ("testServerAborts", testServerGivenSRPXAborts), - ("testServerGivenSRPXAborts", testServerGivenSRPXAborts), - ("testLinuxTestSuiteIncludesAllTests", testLinuxTestSuiteIncludesAllTests) - ] - } - - func testSHA1() { - runTest(group: .N1024, algorithm: .sha1, username: "alice", password: "password123") - runTest(group: .N2048, algorithm: .sha1, username: "alice", password: "password123") - runTest(group: .N3072, algorithm: .sha1, username: "alice", password: "password123") - runTest(group: .N4096, algorithm: .sha1, username: "alice", password: "password123") - runTest(group: .N6144, algorithm: .sha1, username: "alice", password: "password123") - runTest(group: .N8192, algorithm: .sha1, username: "alice", password: "password123") - - // test given precomputed SRP x - runGivenSRPXTest(group: .N1024, algorithm: .sha1, username: "alice") - runGivenSRPXTest(group: .N2048, algorithm: .sha1, username: "alice") - runGivenSRPXTest(group: .N3072, algorithm: .sha1, username: "alice") - runGivenSRPXTest(group: .N4096, algorithm: .sha1, username: "alice") - runGivenSRPXTest(group: .N6144, algorithm: .sha1, username: "alice") - runGivenSRPXTest(group: .N8192, algorithm: .sha1, username: "alice") - } - - func testSHA256() { - runTest(group: .N1024, algorithm: .sha256, username: "alice", password: "password123") - runTest(group: .N2048, algorithm: .sha256, username: "alice", password: "password123") - runTest(group: .N3072, algorithm: .sha256, username: "alice", password: "password123") - runTest(group: .N4096, algorithm: .sha256, username: "alice", password: "password123") - runTest(group: .N6144, algorithm: .sha256, username: "alice", password: "password123") - runTest(group: .N8192, algorithm: .sha256, username: "alice", password: "password123") - - // test given precomputed SRP x - runGivenSRPXTest(group: .N1024, algorithm: .sha256, username: "alice") - runGivenSRPXTest(group: .N2048, algorithm: .sha256, username: "alice") - runGivenSRPXTest(group: .N3072, algorithm: .sha256, username: "alice") - runGivenSRPXTest(group: .N4096, algorithm: .sha256, username: "alice") - runGivenSRPXTest(group: .N6144, algorithm: .sha256, username: "alice") - runGivenSRPXTest(group: .N8192, algorithm: .sha256, username: "alice") - } - - func testCustomGroupParameters() { - let group = Group(prime: "13", generator: "7")! - runTest(group: group, algorithm: .sha1, username: "alice", password: "password123") - runTest(group: group, algorithm: .sha256, username: "alice", password: "password123") - - // test given precomputed SRP x - runGivenSRPXTest(group: group, algorithm: .sha1, username: "alice") - runGivenSRPXTest(group: group, algorithm: .sha256, username: "alice") - } - - func testUtf8() { - runTest(group: .N1024, algorithm: .sha1, username: "bõūkę", password: "tėšt") - runTest(group: .N1024, algorithm: .sha1, username: "bõūkę", password: "😅") - - // test given precomputed SRP x - runGivenSRPXTest(group: .N1024, algorithm: .sha1, username: "bõūkę") - } - - func runTest( - group: Group, - algorithm: Digest.Algorithm, - username: String, - password: String, - file: StaticString = #file, - line: UInt = #line) - { - /* Create a salt+verification key for the user's password. The salt and - * key need to be computed at the time the user's password is set and - * must be stored by the server-side application for use during the - * authentication process. - */ - let (salt, verificationKey) = createSaltedVerificationKey(username: username, password: password, group: group, algorithm: algorithm) - - // Begin authentication process - let client = Client(username: username, password: password, group: group, algorithm: algorithm) - runCommonTest(group: group, algorithm: algorithm, username: username, salt: salt, verificationKey: verificationKey, client: client) - } - - func runGivenSRPXTest( - group: Group, - algorithm: Digest.Algorithm, - username: String, - file: StaticString = #file, - line: UInt = #line) - { - /* Create a salt+verification key for a precomputed SRP x. The salt and - * key must be stored by the server-side application for use during the - * authentication process. - */ - let precomputedX: Data = Data("12345".utf8) - let (salt, verificationKey) = createSaltedVerificationKey(from: precomputedX, group: group) - - // Begin authentication process - let client = Client(username: username, precomputedX: precomputedX, group: group, algorithm: algorithm) - runCommonTest(group: group, algorithm: algorithm, username: username, salt: salt, verificationKey: verificationKey, client: client) - } - - func runCommonTest( - group: Group, - algorithm: Digest.Algorithm, - username: String, - salt: Data, - verificationKey: Data, - client: Client, - file: StaticString = #file, - line: UInt = #line) - { - // Begin authentication process - let (_, A) = client.startAuthentication() - - // Client->Server: I (username) - // Server retrieves salt and verificationKey from permanent storage - let server = Server(username: username, salt: salt, verificationKey: verificationKey, group: group, algorithm: algorithm) - - // The server generates the challenge: pre-defined salt, public key B - // Server->Client: salt, B - let (_, B) = server.getChallenge() - - // Using (salt, B), the client generates the proof M - // Client->Server: M - let M: Data - do { - M = try client.processChallenge(salt: salt, publicKey: B) - } catch { - return XCTFail("Client couldn't process challenge: \(error)", file: file, line: line) - } - - XCTAssertFalse(server.isAuthenticated) - XCTAssertFalse(client.isAuthenticated) - - let HAMK: Data - do { - // Using M, the server verifies the proof and calculates a proof for the client - // Server->Client: H(AMK) - HAMK = try server.verifySession(publicKey: A, keyProof: M) - } catch { - return XCTFail("Client generated invalid M", file: file, line: line) - } - - // At this point, the server is authenticated. - XCTAssert(server.isAuthenticated) - XCTAssertFalse(client.isAuthenticated) - - do { - // Using H(AMK), the client verifies the server's proof - try client.verifySession(keyProof: HAMK) - } catch { - return XCTFail("Server generated invalid H(AMK)", file: file, line: line) - } - - // At this point, the client is authenticated as well - XCTAssert(server.isAuthenticated) - XCTAssert(client.isAuthenticated) - - // They now share a secret session key - guard let K0 = server.sessionKey, let K1 = client.sessionKey else { - return XCTFail("Session keys not set", file: file, line: line) - } - XCTAssertEqual(K0, K1, "Session keys not equal", file: file, line: line) - } - - func testClientAborts() { - let client = Client(username: "alice", password: "password123") - do { - _ = try client.processChallenge( - salt: try! Data(hex: String(repeating: "0", count: 16)), - publicKey: try! Data(hex: String(repeating: "0", count: 512))) - XCTFail("Should not have processed the challenge") - } catch AuthenticationFailure.invalidPublicKey { - // success - } catch { - XCTFail("Incorrect error thrown: \(error)") - } - } - - func testClientGivenSRPXAborts() { - let precomputedX = Data("12345".utf8) - let client = Client(username: "alice", precomputedX: precomputedX) - do { - _ = try client.processChallenge( - salt: try! Data(hex: String(repeating: "0", count: 16)), - publicKey: try! Data(hex: String(repeating: "0", count: 512))) - XCTFail("Should not have processed the challenge") - } catch AuthenticationFailure.invalidPublicKey { - // success - } catch { - XCTFail("Incorrect error thrown: \(error)") - } - } - - func testServerAborts() { - let (salt, verificationKey) = createSaltedVerificationKey(username: "alice", password: "password123") - let server = Server(username: "alice", salt: salt, verificationKey: verificationKey) - do { - _ = try server.verifySession( - publicKey: try! Data(hex: String(repeating: "0", count: 512)), - keyProof: try! Data(hex: String(repeating: "0", count: 512))) - XCTFail("Should not have verified the session") - } catch AuthenticationFailure.invalidPublicKey { - // success - } catch { - XCTFail("Incorrect error thrown: \(error)") - } - } - - func testServerGivenSRPXAborts() { - let (salt, verificationKey) = createSaltedVerificationKey(from: Data("12345".utf8)) - let server = Server(username: "alice", salt: salt, verificationKey: verificationKey) - do { - _ = try server.verifySession( - publicKey: try! Data(hex: String(repeating: "0", count: 512)), - keyProof: try! Data(hex: String(repeating: "0", count: 512))) - XCTFail("Should not have verified the session") - } catch AuthenticationFailure.invalidPublicKey { - // success - } catch { - XCTFail("Incorrect error thrown: \(error)") - } - } - - // from: https://oleb.net/blog/2017/03/keeping-xctest-in-sync/#appendix-code-generation-with-sourcery - func testLinuxTestSuiteIncludesAllTests() { - #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) - let thisClass = type(of: self) - let linuxCount = thisClass.allTests.count - let darwinCount = Int(thisClass - .defaultTestSuite.testCaseCount) - XCTAssertEqual(linuxCount, - darwinCount, - "\(darwinCount - linuxCount) tests are missing from allTests") - #endif - } -} diff --git a/Tests/SRPTests/TestUtils.swift b/Tests/SRPTests/TestUtils.swift deleted file mode 100644 index e78b198..0000000 --- a/Tests/SRPTests/TestUtils.swift +++ /dev/null @@ -1,315 +0,0 @@ -import Cryptor -import Foundation -import SRP - -enum DataDecodingError: Error { - case oddStringLength(Int) -} - -extension Data { - init(hex: String) throws { - if hex.utf8.count % 2 == 1 { - throw DataDecodingError.oddStringLength(hex.utf8.count) - } - let bytes = stride(from: 0, to: hex.utf8.count, by: 2) - .map { hex.utf8.index(hex.utf8.startIndex, offsetBy: $0) } - .map { hex.utf8[$0...hex.utf8.index(after: $0)] } - .map { UInt8(String($0)!, radix: 16)! } - self.init(bytes: bytes) - } - var hex: String { - return map { String(format: "%02hhx", $0) }.joined() - } -} - -let remotepy = URL(fileURLWithPath: #file) - .deletingLastPathComponent() - .deletingLastPathComponent() - .deletingLastPathComponent() - .appendingPathComponent("remote.py") - -indirect enum RemoteError: Error { - case noPython - case unexpectedPrompt(String) - case commandFailure - case commandFailureWithMessage(String) - case valueExpected(RemoteError) - case unexpectedValueLabel(String) - case decodingError - case unexpectedExit(RemoteError) -} - -class Remote { - private let process: Process - - fileprivate let input = Pipe() - fileprivate let output = BufferedPipe() - fileprivate let error = BufferedPipe() - - class BufferedPipe { - let pipe = Pipe() - var buffer = Data() - - var fileHandleForReading: FileHandle { - return pipe.fileHandleForReading - } - } - - fileprivate init(process: Process) { - self.process = process - - process.standardInput = input - process.standardOutput = output.pipe - process.standardError = error.pipe - - process.launch() - } - - fileprivate func write(prompt expectedPrompt: String, line: String) throws { - let prompt = try readprompt(from: output) - guard prompt == "\(expectedPrompt): " else { - throw RemoteError.unexpectedPrompt(prompt) - } - writeline(line) - } - - private func writeline(_ line: String) { - input.fileHandleForWriting.write("\(line)\n".data(using: .ascii)!) - } - - private func readprompt(from pipe: BufferedPipe) throws -> String { - if !process.isRunning { - throw RemoteError.unexpectedExit(readError()) - } - if pipe.buffer.count > 0 { - defer { pipe.buffer = Data() } - return String(data: pipe.buffer, encoding: .ascii)! - } else { - return String(data: pipe.fileHandleForReading.availableData, encoding: .ascii)! - } - } - - fileprivate func read(label: String, from pipe: BufferedPipe) throws -> (String) { - let splitted = try readline(from: pipe).components(separatedBy: ": ") - guard splitted.count == 2 else { - throw RemoteError.valueExpected(readError()) - } - guard label == splitted[0] else { - throw RemoteError.unexpectedValueLabel(splitted[0]) - } - return splitted[1] - } - - fileprivate func readline(from pipe: BufferedPipe) throws -> String { - while true { - if let eol = pipe.buffer.index(of: 10) { - defer { - let lineLength = eol - pipe.buffer.startIndex + 1 - pipe.buffer.removeFirst(lineLength) - } - guard let line = String(data: Data(pipe.buffer[pipe.buffer.startIndex.. RemoteError { - let errorData = error.fileHandleForReading.readDataToEndOfFile() - guard let message = String(data: errorData, encoding: .utf8) else { - return RemoteError.commandFailure - } - return RemoteError.commandFailureWithMessage(message) - } -} - -class RemoteServer: Remote { - var verificationKey: Data? - var privateKey: Data? - var salt: Data? - var publicKey: Data? - var expectedM: Data? - - /// Start remote.py in server-mode. The saltedVerificationKey is - /// generated by the Python script. - /// - /// - Parameters: - /// - username: - /// - password: - /// - group: - /// - algorithm: - /// - privateKey: - /// - salt: - /// - Throws: on I/O Error - init( - username: String, - password: String, - group: Group = .N2048, - algorithm: Digest.Algorithm = .sha1, - privateKey: Data? = nil, - salt: Data? = nil) - throws - { - guard let python = ProcessInfo.processInfo.environment["PYTHON"] else { - throw RemoteError.noPython - } - - let remotepy = URL(fileURLWithPath: #file) - .deletingLastPathComponent() - .deletingLastPathComponent() - .deletingLastPathComponent() - .appendingPathComponent("remote.py") - - let process = Process() - process.launchPath = python - process.arguments = [remotepy.path, - "server", - username.data(using: .utf8)!.hex, - password.data(using: .utf8)!.hex, - "--group", "\(group)", - "--algorithm", "\(algorithm)"] - if let privateKey = privateKey { - process.arguments!.append(contentsOf: ["--private", privateKey.hex]) - } - if let salt = salt { - process.arguments!.append(contentsOf: ["--salt", salt.hex]) - } - super.init(process: process) - - verificationKey = try Data(hex: read(label: "v", from: output)) - } - - /// Get server's challenge - /// - /// - Parameter publicKey: client's public key - /// - Returns: (salt, publicKey) - /// - Throws: on I/O Error - func getChallenge(publicKey A: Data) throws -> (salt: Data, publicKey: Data) { - do { - try write(prompt: "A", line: A.hex) - privateKey = try Data(hex: try read(label: "b", from: output)) - salt = try Data(hex: try read(label: "s", from: output)) - publicKey = try Data(hex: try read(label: "B", from: output)) - return (salt!, publicKey!) - } catch RemoteError.unexpectedExit { - throw readError() - } - } - - /// Verify the client's response - /// - /// - Parameter keyProof: client's key proof (M) - /// - Returns: server's key proof (H(A|M|K)) - /// - Throws: on I/O Error - func verifySession(keyProof M: Data) throws -> Data { - do { - try write(prompt: "M", line: M.hex) - expectedM = try Data(hex: try read(label: "expected M", from: output)) - return try Data(hex: try read(label: "HAMK", from: output)) - } catch RemoteError.unexpectedExit { - throw readError() - } - } - - /// Returns the server's session key - /// - /// - Returns: session key - /// - Throws: on I/O Error - func getSessionKey() throws -> Data { - return try Data(hex: try read(label: "K", from: output)) - } -} - -class RemoteClient: Remote { - let username: String - var privateKey: Data? - var publicKey: Data? - - /// Start remote.py in client-mode. - /// - /// - Parameters: - /// - username: - /// - password: - /// - group: - /// - algorithm: - /// - privateKey: - /// - Throws: on I/O Error - init( - username: String, - password: String, - group: Group = .N2048, - algorithm: Digest.Algorithm = .sha1, - privateKey: Data? = nil) - throws - { - self.username = username - - guard let python = ProcessInfo.processInfo.environment["PYTHON"] else { - throw RemoteError.noPython - } - - let process = Process() - process.launchPath = python - process.arguments = [remotepy.path, - "client", - username.data(using: .utf8)!.hex, - password.data(using: .utf8)!.hex, - "--group", "\(group)", - "--algorithm", "\(algorithm)"] - if let privateKey = privateKey { - process.arguments!.append(contentsOf: ["--private", privateKey.hex]) - } - super.init(process: process) - - self.privateKey = try Data(hex: try read(label: "a", from: output)) - } - - /// Read public key from stdout. - /// - /// - Returns: `username` (I) and `publicKey` (A) - /// - Throws: on I/O Error - func startAuthentication() throws -> (username: String, publicKey: Data) { - publicKey = try Data(hex: try read(label: "A", from: output)) - return (username, publicKey!) - } - - /// Process challenge, get client's response - /// - /// - Parameters: - /// - salt: - /// - publicKey: - /// - Returns: key proof (M) - /// - Throws: on I/O Error - func processChallenge(salt s: Data, publicKey B: Data) throws -> Data { - try write(prompt: "s", line: s.hex) - try write(prompt: "B", line: B.hex) - return try Data(hex: try read(label: "M", from: output)) - } - - /// Verify the server's response. - /// - /// - Parameter keyProof: (M) - /// - Throws: on I/O Error - func verifySession(keyProof: Data) throws { - try write(prompt: "HAMK", line: keyProof.hex) - guard try readline(from: output) == "OK" else { - throw readError() - } - } - - /// Returns the client's session key - /// - /// - Returns: session key (K) - /// - Throws: on I/O Error - func getSessionKey() throws -> Data { - return try Data(hex: try read(label: "K", from: output)) - } -} diff --git a/remote.py b/remote.py deleted file mode 100755 index fe5db7b..0000000 --- a/remote.py +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env python -from __future__ import print_function - -import argparse -from binascii import unhexlify -import sys - -from srptools import SRPClientSession -from srptools import SRPContext, SRPServerSession, constants -from srptools.utils import hex_from, int_from_hex, value_encode - -# Support Python 2 and 3 -try: - input = raw_input -except NameError: - pass - -def hex_encoded_utf8(value): - return unhexlify(str.encode(value)) - -# 8192 bits prime is not a built-in prime in srptools, -# so a custom prime/generator is defined. -PRIME_8192_GEN = hex_from(19) -PRIME_8192 = '''\ -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''' - -groups = { - "N1024": (constants.PRIME_1024, constants.PRIME_1024_GEN), - "N1536": (constants.PRIME_1536, constants.PRIME_1536_GEN), - "N2048": (constants.PRIME_2048, constants.PRIME_2048_GEN), - "N3072": (constants.PRIME_3072, constants.PRIME_3072_GEN), - "N4096": (constants.PRIME_4096, constants.PRIME_4096_GEN), - "N6144": (constants.PRIME_6144, constants.PRIME_6144_GEN), - "N8192": (PRIME_8192, PRIME_8192_GEN), -} - -algorithms = { - "sha1": constants.HASH_SHA_1, - "sha256": constants.HASH_SHA_256, -} - -ensure_hash_sizes = { - "sha1": lambda hex: hex.zfill(40), - "sha256": lambda hex: hex.zfill(64), -} - -parser = argparse.ArgumentParser(description="SRP Server") -parser.add_argument("--group", default="N2048") -parser.add_argument("--algorithm", default="sha1") -parser.add_argument("--salt") -parser.add_argument("--private") - -subparsers = parser.add_subparsers(dest="command") -subparsers.is_required = True -subparsers.add_parser("server") -subparsers.add_parser("client") - -parser.add_argument("username", type=hex_encoded_utf8) -parser.add_argument("password", type=hex_encoded_utf8) - -args = parser.parse_args() - -prime = groups[args.group][0] -generator = groups[args.group][1] -hash_func = algorithms[args.algorithm] -ensure_hash_size = ensure_hash_sizes[args.algorithm] -context = SRPContext(args.username, args.password, prime=prime, generator=generator, hash_func=hash_func) - -if args.command == "server": - if args.salt: - salt = args.salt - password_verifier = value_encode(context.get_common_password_verifier(context.get_common_password_hash(unhexlify(salt)))) - else: - _, password_verifier, salt = context.get_user_data_triplet() - - print("v:", password_verifier) - - # Client => Server: username, A - sys.stdout.write("A: ") - sys.stdout.flush() - A = input() - - # Receive username from client and generate server public. - server_session = SRPServerSession(context, password_verifier, private=args.private) - - print("b:", server_session.private) - - # Server => Client: s, B - print("s:", salt) - print("B:", server_session.public) - - # Client => Server: M - sys.stdout.write("M: ") - sys.stdout.flush() - M = input() - - # Process client public and verify session key proof. - server_session.process(A, salt) - print("expected M:", server_session.key_proof) - - assert server_session.verify_proof(M) - - # Server => Client: HAMK - print("HAMK:", ensure_hash_size(server_session.key_proof_hash)) - - # Always keep the key secret! It is printed to validate the implementation. - print("K:", ensure_hash_size(server_session.key)) - -if args.command == "client": - client_session = SRPClientSession(context, private=args.private) - print("a:", client_session.private) - - # Client => Server: username, A - print("A:", client_session.public) - - # Server => Client: s, B - sys.stdout.write("s: ") - sys.stdout.flush() - s = input() - sys.stdout.write("B: ") - sys.stdout.flush() - B = input() - client_session.process(B, s) - - # Client => Server: M - print("M:", ensure_hash_size(client_session.key_proof)) - - # Server => Client: HAMK - sys.stdout.write("HAMK: ") - sys.stdout.flush() - HAMK = input() - assert client_session.verify_proof(HAMK) - print("OK") - - # Always keep the key secret! It is printed to validate the implementation. - print("K:", ensure_hash_size(client_session.key))