Skip to content

Commit bd76e88

Browse files
authored
Ensure we have correct padding everywhere (#14)
1 parent 982c378 commit bd76e88

File tree

9 files changed

+234
-140
lines changed

9 files changed

+234
-140
lines changed

Diff for: .github/workflows/api-breakage.yml

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: API breaking changes
2+
3+
on:
4+
pull_request:
5+
concurrency:
6+
group: ${{ github.workflow }}-${{ github.ref }}-apibreakage
7+
cancel-in-progress: true
8+
9+
jobs:
10+
linux:
11+
runs-on: ubuntu-latest
12+
timeout-minutes: 15
13+
container:
14+
image: swift:latest
15+
steps:
16+
- name: Checkout
17+
uses: actions/checkout@v4
18+
with:
19+
fetch-depth: 0
20+
# https://github.com/actions/checkout/issues/766
21+
- name: Mark the workspace as safe
22+
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
23+
- name: API breaking changes
24+
run: |
25+
swift package diagnose-api-breaking-changes origin/${GITHUB_BASE_REF}

Diff for: Package.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ let package = Package(
1515
.library(name: "SRP", targets: ["SRP"]),
1616
],
1717
dependencies: [
18-
.package(url: "https://github.com/apple/swift-crypto", from: "1.0.0"),
18+
.package(url: "https://github.com/apple/swift-crypto", "1.0.0"..<"5.0.0"),
1919
.package(url: "https://github.com/adam-fowler/big-num", from: "2.0.0"),
2020
],
2121
targets: [

Diff for: Sources/SRP/Array.swift

+10
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@ extension Array where Element: FixedWidthInteger {
1818
}
1919
}
2020

21+
extension Array where Element == UInt8 {
22+
func pad(to size: Int) -> [UInt8] {
23+
let padSize = size - self.count
24+
guard padSize > 0 else { return self }
25+
// create prefix and return prefix + data
26+
let prefix: [UInt8] = (1...padSize).reduce([]) { result,_ in return result + [0] }
27+
return prefix + self
28+
}
29+
}
30+
2131
/// xor together the contents of two byte arrays
2232
func ^ (lhs: [UInt8], rhs: [UInt8]) -> [UInt8] {
2333
precondition(lhs.count == rhs.count, "Arrays are required to be the same size")

Diff for: Sources/SRP/client.swift

+84-44
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import BigNum
22
import Crypto
3+
import Foundation
34

45
/// Manages the client side of Secure Remote Password
56
///
@@ -27,14 +28,14 @@ public struct SRPClient<H: HashFunction> {
2728
/// Initiate the authentication process
2829
/// - Returns: An authentication state. The A value from this state should be sent to the server
2930
public func generateKeys() -> SRPKeyPair {
30-
var a = BigNum()
31-
var A = BigNum()
31+
var a: BigNum
32+
var A: BigNum
3233
repeat {
3334
a = BigNum(bytes: SymmetricKey(size: .bits256))
3435
A = configuration.g.power(a, modulus: configuration.N)
3536
} while A % configuration.N == BigNum(0)
3637

37-
return SRPKeyPair(public: SRPKey(A), private: SRPKey(a))
38+
return SRPKeyPair(public: SRPKey(A, padding: self.configuration.sizeN), private: SRPKey(a))
3839
}
3940

4041
/// return shared secret given the username, password, B value and salt from the server
@@ -46,51 +47,63 @@ public struct SRPClient<H: HashFunction> {
4647
/// - serverPublicKey: server public key
4748
/// - Throws: `nullServerKey`
4849
/// - Returns: shared secret
49-
public func calculateSharedSecret(username: String, password: String, salt: [UInt8], clientKeys: SRPKeyPair, serverPublicKey: SRPKey) throws -> SRPKey {
50+
public func calculateSharedSecret(
51+
username: String,
52+
password: String,
53+
salt: [UInt8],
54+
clientKeys: SRPKeyPair,
55+
serverPublicKey: SRPKey
56+
) throws -> SRPKey {
5057
let message = [UInt8]("\(username):\(password)".utf8)
51-
let sharedSecret = try calculateSharedSecret(message: message, salt: salt, clientKeys: clientKeys, serverPublicKey: serverPublicKey)
52-
return SRPKey(sharedSecret)
58+
return try calculateSharedSecret(message: message, salt: salt, clientKeys: clientKeys, serverPublicKey: serverPublicKey)
5359
}
5460

55-
56-
/// calculate proof of shared secret to send to server
61+
/// return shared secret given a binary password, B value and salt from the server
5762
/// - Parameters:
58-
/// - clientPublicKey: client public key
63+
/// - password: password
64+
/// - salt: salt
65+
/// - clientKeys: client public/private keys
5966
/// - serverPublicKey: server public key
60-
/// - sharedSecret: shared secret
61-
/// - Returns: The client verification code which should be passed to the server
62-
public func calculateSimpleClientProof(clientPublicKey: SRPKey, serverPublicKey: SRPKey, sharedSecret: SRPKey) -> [UInt8] {
63-
// get verification code
64-
return SRP<H>.calculateSimpleClientProof(clientPublicKey: clientPublicKey, serverPublicKey: serverPublicKey, sharedSecret: sharedSecret)
67+
/// - Throws: `nullServerKey`
68+
/// - Returns: shared secret
69+
public func calculateSharedSecret(
70+
password: [UInt8],
71+
salt: [UInt8],
72+
clientKeys: SRPKeyPair,
73+
serverPublicKey: SRPKey
74+
) throws -> SRPKey {
75+
let message = [0x3a] + password
76+
return try calculateSharedSecret(message: message, salt: salt, clientKeys: clientKeys, serverPublicKey: serverPublicKey)
6577
}
6678

67-
/// If the server returns that the client verification code was valiid it will also return a server verification code that the client can use to verify the server is correct
68-
///
69-
/// - Parameters:
70-
/// - code: Verification code returned by server
71-
/// - state: Authentication state
72-
/// - Throws: `requiresVerificationKey`, `invalidServerCode`
73-
public func verifySimpleServerProof(serverProof: [UInt8], clientProof: [UInt8], clientKeys: SRPKeyPair, sharedSecret: SRPKey) throws {
74-
// get out version of server proof
75-
let HAMS = SRP<H>.calculateSimpleServerVerification(clientPublicKey: clientKeys.public, clientProof: clientProof, sharedSecret: sharedSecret)
76-
// is it the same
77-
guard serverProof == HAMS else { throw SRPClientError.invalidServerCode }
78-
}
79-
8079
/// calculate proof of shared secret to send to server
8180
/// - Parameters:
82-
/// - username: username
81+
/// - username: Username
8382
/// - salt: The salt value associated with the user returned by the server
84-
/// - clientPublicKey: client public key
83+
/// - clientPublicKey: Client public key
8584
/// - serverPublicKey: server public key
8685
/// - sharedSecret: shared secret
8786
/// - Returns: The client verification code which should be passed to the server
88-
public func calculateClientProof(username: String, salt: [UInt8], clientPublicKey: SRPKey, serverPublicKey: SRPKey, sharedSecret: SRPKey) -> [UInt8] {
89-
87+
public func calculateClientProof(
88+
username: String,
89+
salt: [UInt8],
90+
clientPublicKey: SRPKey,
91+
serverPublicKey: SRPKey,
92+
sharedSecret: SRPKey
93+
) -> [UInt8] {
94+
let clientPublicKey = clientPublicKey.with(padding: self.configuration.sizeN)
95+
let serverPublicKey = serverPublicKey.with(padding: self.configuration.sizeN)
96+
let sharedSecret = sharedSecret.with(padding: self.configuration.sizeN)
9097
let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes))
91-
9298
// get verification code
93-
return SRP<H>.calculateClientProof(configuration: configuration, username: username, salt: salt, clientPublicKey: clientPublicKey, serverPublicKey: serverPublicKey, hashSharedSecret: hashSharedSecret)
99+
return SRP<H>.calculateClientProof(
100+
configuration: configuration,
101+
username: username,
102+
salt: salt,
103+
clientPublicKey: clientPublicKey,
104+
serverPublicKey: serverPublicKey,
105+
hashSharedSecret: hashSharedSecret
106+
)
94107
}
95108

96109
/// If the server returns that the client verification code was valid it will also return a server
@@ -101,24 +114,39 @@ public struct SRPClient<H: HashFunction> {
101114
/// - clientPublicKey: Client public key
102115
/// - clientProof: Client proof
103116
/// - sharedSecret: Shared secret
104-
public func calculateServerProof(clientPublicKey: SRPKey, clientProof: [UInt8], sharedSecret: SRPKey) -> [UInt8] {
117+
public func calculateServerProof(
118+
clientPublicKey: SRPKey,
119+
clientProof: [UInt8],
120+
sharedSecret: SRPKey
121+
) -> [UInt8] {
122+
let clientPublicKey = clientPublicKey.with(padding: self.configuration.sizeN)
123+
let sharedSecret = sharedSecret.with(padding: self.configuration.sizeN)
105124
let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes))
106125
// get out version of server proof
107-
return SRP<H>.calculateServerVerification(clientPublicKey: clientPublicKey, clientProof: clientProof, sharedSecret: hashSharedSecret)
126+
return SRP<H>.calculateServerVerification(
127+
clientPublicKey: clientPublicKey,
128+
clientProof: clientProof,
129+
hashSharedSecret: hashSharedSecret
130+
)
108131
}
109132

110133
/// If the server returns that the client verification code was valid it will also return a server
111134
/// verification code that the client can use to verify the server is correct
112135
///
113136
/// - Parameters:
114-
/// - clientProof: Server proof
137+
/// - serverProof: Server proof
115138
/// - clientProof: Client proof
116-
/// - clientKeys: Client keys
139+
/// - clientPublicKey: Client public key
117140
/// - sharedSecret: Shared secret
118141
/// - Throws: `requiresVerificationKey`, `invalidServerCode`
119-
public func verifyServerProof(serverProof: [UInt8], clientProof: [UInt8], clientKeys: SRPKeyPair, sharedSecret: SRPKey) throws {
142+
public func verifyServerProof(
143+
serverProof: [UInt8],
144+
clientProof: [UInt8],
145+
clientPublicKey: SRPKey,
146+
sharedSecret: SRPKey
147+
) throws {
120148
// get our version of server proof
121-
let HAMK = calculateServerProof(clientPublicKey: clientKeys.public, clientProof: clientProof, sharedSecret: sharedSecret)
149+
let HAMK = calculateServerProof(clientPublicKey: clientPublicKey, clientProof: clientProof, sharedSecret: sharedSecret)
122150
// is it the same
123151
guard serverProof == HAMK else { throw SRPClientError.invalidServerCode }
124152
}
@@ -134,17 +162,29 @@ public struct SRPClient<H: HashFunction> {
134162
public func generateSaltAndVerifier(username: String, password: String) -> (salt: [UInt8], verifier: SRPKey) {
135163
let salt = [UInt8].random(count: 16)
136164
let verifier = generatePasswordVerifier(username: username, password: password, salt: salt)
137-
return (salt: salt, verifier: SRPKey(verifier))
165+
return (salt: salt, verifier: SRPKey(verifier, padding: configuration.sizeN))
166+
}
167+
168+
/// Hash data using same hash function that SRP uses
169+
/// - Parameter data: Data to be hashed
170+
/// - Returns: Hashed data
171+
@inlinable public func hash<D>(data: D) -> H.Digest where D : DataProtocol {
172+
H.hash(data: data)
138173
}
139174
}
140175

141176
extension SRPClient {
142-
/// return shared secret given the username, password, B value and salt from the server
143-
func calculateSharedSecret(message: [UInt8], salt: [UInt8], clientKeys: SRPKeyPair, serverPublicKey: SRPKey) throws -> BigNum {
177+
/// return shared secret given the message (username:password), salt from server, client keys, and B value
178+
func calculateSharedSecret(
179+
message: [UInt8],
180+
salt: [UInt8],
181+
clientKeys: SRPKeyPair,
182+
serverPublicKey: SRPKey
183+
) throws -> SRPKey {
144184
guard serverPublicKey.number % configuration.N != BigNum(0) else { throw SRPClientError.nullServerKey }
145185

146186
// calculate u = H(clientPublicKey | serverPublicKey)
147-
let u = SRP<H>.calculateU(clientPublicKey: clientKeys.public.bytes, serverPublicKey: serverPublicKey.bytes, pad: configuration.sizeN)
187+
let u = SRP<H>.calculateU(clientPublicKey: clientKeys.public.bytes, serverPublicKey: serverPublicKey.bytes)
148188

149189
guard u != 0 else { throw SRPClientError.nullServerKey }
150190

@@ -153,7 +193,7 @@ extension SRPClient {
153193
// calculate S = (B - k*g^x)^(a+u*x)
154194
let S = (serverPublicKey.number - configuration.k * configuration.g.power(x, modulus: configuration.N)).power(clientKeys.private.number + u * x, modulus: configuration.N)
155195

156-
return S
196+
return .init(S, padding: self.configuration.sizeN)
157197
}
158198

159199
/// generate password verifier

Diff for: Sources/SRP/configuration.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public struct SRPConfiguration<H: HashFunction> {
1818
self.N = prime.group
1919
self.sizeN = Int(self.N.numBits() + 7) / 8
2020
self.g = prime.generator
21-
self.k = BigNum(bytes: [UInt8](H.hash(data: self.N.bytes + SRP<H>.pad(self.g.bytes, to: sizeN))))
21+
self.k = BigNum(bytes: [UInt8](H.hash(data: self.N.bytes + self.g.bytes.pad(to: sizeN))))
2222
}
2323

2424
/// Initialise SRPConfiguration with your own prime and multiplicative group generator
@@ -29,7 +29,7 @@ public struct SRPConfiguration<H: HashFunction> {
2929
self.N = N
3030
self.sizeN = Int(self.N.numBits() + 7) / 8
3131
self.g = g
32-
self.k = BigNum(bytes: [UInt8](H.hash(data: self.N.bytes + SRP<H>.pad(self.g.bytes, to: sizeN))))
32+
self.k = BigNum(bytes: [UInt8](H.hash(data: self.N.bytes + self.g.bytes.pad(to: sizeN))))
3333
}
3434

3535
public enum Prime {

Diff for: Sources/SRP/keys.swift

+31-8
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,46 @@
11
import BigNum
2+
import Crypto
3+
import Foundation
24

35
/// Wrapper for keys used by SRP
46
public struct SRPKey {
7+
/// SRPKey internal storage
58
public let number: BigNum
6-
public var bytes: [UInt8] { number.bytes }
7-
public var hex: String { number.hex }
9+
/// padding
10+
public let padding: Int
11+
/// Representation as a byte array
12+
public var bytes: [UInt8] { number.bytes.pad(to: self.padding) }
13+
/// Representation as a hex string
14+
public var hex: String { number.bytes.pad(to: self.padding).hexdigest() }
815

9-
public init(_ bytes: [UInt8]) {
16+
/// Initialize with an array of bytes
17+
@inlinable public init<C: Collection & ContiguousBytes>(_ bytes: C, padding: Int? = nil) {
1018
self.number = BigNum(bytes: bytes)
19+
self.padding = padding ?? bytes.count
1120
}
1221

13-
public init(_ number: BigNum) {
14-
self.number = number
22+
/// Initialize with a crypto digest
23+
@inlinable public init<D: Digest>(_ digest: D, padding: Int? = nil) {
24+
self.number = BigNum(bytes: digest)
25+
self.padding = padding ?? D.byteCount
1526
}
1627

17-
public init?(hex: String) {
28+
/// Initialize with a hex string
29+
@inlinable public init?(hex: String, padding: Int = 0) {
1830
guard let number = BigNum(hex: hex) else { return nil }
1931
self.number = number
32+
self.padding = padding
33+
}
34+
35+
/// Initialize with a BigNum
36+
@usableFromInline init(_ number: BigNum, padding: Int = 0) {
37+
self.number = number
38+
self.padding = padding
39+
}
40+
41+
/// Return SRPKey with padding
42+
func with(padding: Int) -> SRPKey {
43+
.init(self.number, padding: padding)
2044
}
2145
}
2246

@@ -27,12 +51,11 @@ public struct SRPKeyPair {
2751
public let `public`: SRPKey
2852
public let `private`: SRPKey
2953

30-
3154
/// Initialise a SRPKeyPair object
3255
/// - Parameters:
3356
/// - public: The public key of the key pair
3457
/// - private: The private key of the key pair
35-
public init(`public`: SRPKey, `private`: SRPKey) {
58+
init(`public`: SRPKey, `private`: SRPKey) {
3659
self.private = `private`
3760
self.public = `public`
3861
}

0 commit comments

Comments
 (0)