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))