From 09cc2e4a6e66aa9a5855f581e0129fbc16e8a3f6 Mon Sep 17 00:00:00 2001 From: Karim Karimov Date: Thu, 25 Feb 2021 09:58:37 +0400 Subject: [PATCH] implemented nimbus and thinbus srp client types, removed tests --- .github/workflows/publish-docs.yml | 15 - .github/workflows/sonarcloud.yml | 18 - .github/workflows/swiftlint.yml | 14 - .github/workflows/test.yml | 39 --- .jazzy.yaml | 11 - .swiftlint.yml | 37 -- .../contents.xcworkspacedata | 7 + .../UserInterfaceState.xcuserstate | Bin 0 -> 34114 bytes .../xcschemes/xcschememanagement.plist | 30 ++ .travis.yml | 12 - CHANGELOG.md | 4 + Gemfile | 3 - Makefile | 6 - README.md | 17 +- Sources/Client.swift | 39 ++- Sources/ClientType.swift | 12 + Sources/Data+Extensions.swift | 18 + Sources/Group.swift | 13 + Sources/SRP.swift | 56 +++- Tests/LinuxMain.swift | 8 - Tests/SRPTests/PySrptoolsTests.swift | 279 ---------------- Tests/SRPTests/ReadmeTests.swift | 82 ----- Tests/SRPTests/SRPTests.swift | 248 -------------- Tests/SRPTests/TestUtils.swift | 315 ------------------ remote.py | 172 ---------- 25 files changed, 174 insertions(+), 1281 deletions(-) delete mode 100644 .github/workflows/publish-docs.yml delete mode 100644 .github/workflows/sonarcloud.yml delete mode 100644 .github/workflows/swiftlint.yml delete mode 100644 .github/workflows/test.yml delete mode 100644 .jazzy.yaml delete mode 100644 .swiftlint.yml create mode 100644 .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata create mode 100644 .swiftpm/xcode/package.xcworkspace/xcuserdata/kbkarimov.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 .swiftpm/xcode/xcuserdata/kbkarimov.xcuserdatad/xcschemes/xcschememanagement.plist delete mode 100644 .travis.yml delete mode 100644 Gemfile delete mode 100755 Makefile create mode 100644 Sources/ClientType.swift delete mode 100644 Tests/LinuxMain.swift delete mode 100644 Tests/SRPTests/PySrptoolsTests.swift delete mode 100644 Tests/SRPTests/ReadmeTests.swift delete mode 100644 Tests/SRPTests/SRPTests.swift delete mode 100644 Tests/SRPTests/TestUtils.swift delete mode 100755 remote.py 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 0000000000000000000000000000000000000000..f0b58dc8b690a4e7d30c63f342046f31e7aaee7b GIT binary patch literal 34114 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 y(J + + + + 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))