Skip to content

Commit

Permalink
Ensure we have correct padding everywhere
Browse files Browse the repository at this point in the history
  • Loading branch information
adam-fowler committed Oct 29, 2024
1 parent 982c378 commit 989b93b
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 46 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/api-breakage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: API breaking changes

on:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-apibreakage
cancel-in-progress: true

jobs:
linux:
runs-on: ubuntu-latest
timeout-minutes: 15
container:
image: swift:latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
# https://github.com/actions/checkout/issues/766
- name: Mark the workspace as safe
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
- name: API breaking changes
run: |
swift package diagnose-api-breaking-changes origin/${GITHUB_BASE_REF}
16 changes: 8 additions & 8 deletions Sources/SRP/client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public struct SRPClient<H: HashFunction> {
/// - Returns: The client verification code which should be passed to the server
public func calculateSimpleClientProof(clientPublicKey: SRPKey, serverPublicKey: SRPKey, sharedSecret: SRPKey) -> [UInt8] {
// get verification code
return SRP<H>.calculateSimpleClientProof(clientPublicKey: clientPublicKey, serverPublicKey: serverPublicKey, sharedSecret: sharedSecret)
return SRP<H>.calculateSimpleClientProof(clientPublicKey: clientPublicKey, serverPublicKey: serverPublicKey, sharedSecret: sharedSecret, padding: configuration.sizeN)
}

/// 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
Expand All @@ -72,7 +72,7 @@ public struct SRPClient<H: HashFunction> {
/// - Throws: `requiresVerificationKey`, `invalidServerCode`
public func verifySimpleServerProof(serverProof: [UInt8], clientProof: [UInt8], clientKeys: SRPKeyPair, sharedSecret: SRPKey) throws {
// get out version of server proof
let HAMS = SRP<H>.calculateSimpleServerVerification(clientPublicKey: clientKeys.public, clientProof: clientProof, sharedSecret: sharedSecret)
let HAMS = SRP<H>.calculateSimpleServerVerification(clientPublicKey: clientKeys.public, clientProof: clientProof, sharedSecret: sharedSecret, padding: configuration.sizeN)
// is it the same
guard serverProof == HAMS else { throw SRPClientError.invalidServerCode }
}
Expand All @@ -87,10 +87,10 @@ public struct SRPClient<H: HashFunction> {
/// - Returns: The client verification code which should be passed to the server
public func calculateClientProof(username: String, salt: [UInt8], clientPublicKey: SRPKey, serverPublicKey: SRPKey, sharedSecret: SRPKey) -> [UInt8] {

let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes))
let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes(padding: configuration.sizeN)))

// get verification code
return SRP<H>.calculateClientProof(configuration: configuration, username: username, salt: salt, clientPublicKey: clientPublicKey, serverPublicKey: serverPublicKey, hashSharedSecret: hashSharedSecret)
return SRP<H>.calculateClientProof(configuration: configuration, username: username, salt: salt, clientPublicKey: clientPublicKey, serverPublicKey: serverPublicKey, hashSharedSecret: hashSharedSecret, padding: configuration.sizeN)
}

/// If the server returns that the client verification code was valid it will also return a server
Expand All @@ -102,9 +102,9 @@ public struct SRPClient<H: HashFunction> {
/// - clientProof: Client proof
/// - sharedSecret: Shared secret
public func calculateServerProof(clientPublicKey: SRPKey, clientProof: [UInt8], sharedSecret: SRPKey) -> [UInt8] {
let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes))
let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes(padding: configuration.sizeN)))
// get out version of server proof
return SRP<H>.calculateServerVerification(clientPublicKey: clientPublicKey, clientProof: clientProof, sharedSecret: hashSharedSecret)
return SRP<H>.calculateServerVerification(clientPublicKey: clientPublicKey, clientProof: clientProof, sharedSecret: hashSharedSecret, padding: configuration.sizeN)
}

/// If the server returns that the client verification code was valid it will also return a server
Expand Down Expand Up @@ -139,12 +139,12 @@ public struct SRPClient<H: HashFunction> {
}

extension SRPClient {
/// return shared secret given the username, password, B value and salt from the server
/// return shared secret given the username, password, salt from server, client keys, and B value
func calculateSharedSecret(message: [UInt8], salt: [UInt8], clientKeys: SRPKeyPair, serverPublicKey: SRPKey) throws -> BigNum {
guard serverPublicKey.number % configuration.N != BigNum(0) else { throw SRPClientError.nullServerKey }

// calculate u = H(clientPublicKey | serverPublicKey)
let u = SRP<H>.calculateU(clientPublicKey: clientKeys.public.bytes, serverPublicKey: serverPublicKey.bytes, pad: configuration.sizeN)
let u = SRP<H>.calculateU(clientPublicKey: clientKeys.public.bytes(padding: configuration.sizeN), serverPublicKey: serverPublicKey.bytes(padding: configuration.sizeN))

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

Expand Down
4 changes: 2 additions & 2 deletions Sources/SRP/configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public struct SRPConfiguration<H: HashFunction> {
self.N = prime.group
self.sizeN = Int(self.N.numBits() + 7) / 8
self.g = prime.generator
self.k = BigNum(bytes: [UInt8](H.hash(data: self.N.bytes + SRP<H>.pad(self.g.bytes, to: sizeN))))
self.k = BigNum(bytes: [UInt8](H.hash(data: self.N.bytes + self.g.bytes.pad(to: sizeN))))
}

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

public enum Prime {
Expand Down
10 changes: 10 additions & 0 deletions Sources/SRP/keys.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,28 @@ import BigNum

/// Wrapper for keys used by SRP
public struct SRPKey {
/// SRPKey internal storage
public let number: BigNum
/// Representation as a byte array
public var bytes: [UInt8] { number.bytes }
/// Representation as a hex string
public var hex: String { number.hex }
/// Representation as a byte array with padding
public func bytes(padding: Int) -> [UInt8] { number.bytes.pad(to: padding) }
/// Representation as a hex string with padding
public func hex(padding: Int) -> String { number.bytes.pad(to: padding).hexdigest() }

/// Initialize with an array of bytes
public init(_ bytes: [UInt8]) {
self.number = BigNum(bytes: bytes)
}

/// Initialize with a BigNum
public init(_ number: BigNum) {
self.number = number
}

/// Initialize with a hex string
public init?(hex: String) {
guard let number = BigNum(hex: hex) else { return nil }
self.number = number
Expand Down
14 changes: 8 additions & 6 deletions Sources/SRP/server.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public struct SRPServer<H: HashFunction> {
guard clientPublicKey.number % configuration.N != BigNum(0) else { throw SRPServerError.nullClientKey }

// calculate u = H(clientPublicKey | serverPublicKey)
let u = SRP<H>.calculateU(clientPublicKey: clientPublicKey.bytes, serverPublicKey: serverKeys.public.bytes, pad: configuration.sizeN)
let u = SRP<H>.calculateU(clientPublicKey: clientPublicKey.bytes(padding: configuration.sizeN), serverPublicKey: serverKeys.public.bytes(padding: configuration.sizeN))

// calculate S
let S = ((clientPublicKey.number * verifier.number.power(u, modulus: configuration.N)).power(serverKeys.private.number, modulus: configuration.N))
Expand All @@ -76,10 +76,11 @@ public struct SRPServer<H: HashFunction> {
let clientProof = SRP<H>.calculateSimpleClientProof(
clientPublicKey: clientPublicKey,
serverPublicKey: serverPublicKey,
sharedSecret: sharedSecret
sharedSecret: sharedSecret,
padding: configuration.sizeN
)
guard clientProof == proof else { throw SRPServerError.invalidClientProof }
return SRP<H>.calculateSimpleServerVerification(clientPublicKey: clientPublicKey, clientProof: clientProof, sharedSecret: sharedSecret)
return SRP<H>.calculateSimpleServerVerification(clientPublicKey: clientPublicKey, clientProof: clientProof, sharedSecret: sharedSecret, padding: configuration.sizeN)
}

/// verify proof that client has shared secret and return a server verification proof. If verification fails a `invalidClientCode` error is thrown
Expand All @@ -94,17 +95,18 @@ public struct SRPServer<H: HashFunction> {
/// - Throws: invalidClientCode
/// - Returns: The server verification code
public func verifyClientProof(proof: [UInt8], username: String, salt: [UInt8], clientPublicKey: SRPKey, serverPublicKey: SRPKey, sharedSecret: SRPKey) throws -> [UInt8] {
let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes))
let hashSharedSecret = [UInt8](H.hash(data: sharedSecret.bytes(padding: configuration.sizeN)))

let clientProof = SRP<H>.calculateClientProof(
configuration: configuration,
username: username,
salt: salt,
clientPublicKey: clientPublicKey,
serverPublicKey: serverPublicKey,
hashSharedSecret: hashSharedSecret
hashSharedSecret: hashSharedSecret,
padding: configuration.sizeN
)
guard clientProof == proof else { throw SRPServerError.invalidClientProof }
return SRP<H>.calculateServerVerification(clientPublicKey: clientPublicKey, clientProof: clientProof, sharedSecret: hashSharedSecret)
return SRP<H>.calculateServerVerification(clientPublicKey: clientPublicKey, clientProof: clientProof, sharedSecret: hashSharedSecret, padding: configuration.sizeN)
}
}
50 changes: 27 additions & 23 deletions Sources/SRP/srp.swift
Original file line number Diff line number Diff line change
@@ -1,40 +1,43 @@
import BigNum
import Crypto

/// Contains common code used by both client and server SRP code
public struct SRP<H: HashFunction> {

/// pad to a certain size by prefixing with zeros
static func pad(_ data: [UInt8], to size: Int) -> [UInt8] {
let padSize = size - data.count
guard padSize > 0 else { return data }
extension Array where Element == UInt8 {
func pad(to size: Int) -> [UInt8] {
let padSize = size - self.count
guard padSize > 0 else { return self }
// create prefix and return prefix + data
let prefix: [UInt8] = (1...padSize).reduce([]) { result,_ in return result + [0] }
return prefix + data
return prefix + self
}

}

/// Contains common code used by both client and server SRP code
public struct SRP<H: HashFunction> {

/// calculate u = H(clientPublicKey | serverPublicKey)
public static func calculateU(clientPublicKey: [UInt8], serverPublicKey: [UInt8], pad: Int) -> BigNum {
BigNum(bytes: [UInt8].init(H.hash(data: SRP<H>.pad(clientPublicKey, to: pad) + SRP<H>.pad(serverPublicKey, to: pad))))
public static func calculateU(clientPublicKey: [UInt8], serverPublicKey: [UInt8]) -> BigNum {
BigNum(bytes: [UInt8].init(H.hash(data: clientPublicKey + serverPublicKey)))
}

/// Calculate a simpler client verification code H(A | B | S)
static func calculateSimpleClientProof(
clientPublicKey: SRPKey,
serverPublicKey: SRPKey,
sharedSecret: SRPKey) -> [UInt8]
{
let HABK = H.hash(data: clientPublicKey.bytes + serverPublicKey.bytes + sharedSecret.bytes)
sharedSecret: SRPKey,
padding: Int
) -> [UInt8] {
let HABK = H.hash(data: clientPublicKey.bytes(padding: padding) + serverPublicKey.bytes(padding: padding) + sharedSecret.bytes(padding: padding))
return [UInt8](HABK)
}

/// Calculate a simpler client verification code H(A | M1 | S)
static func calculateSimpleServerVerification(
clientPublicKey: SRPKey,
clientProof: [UInt8],
sharedSecret: SRPKey) -> [UInt8]
{
let HABK = H.hash(data: clientPublicKey.bytes + clientProof + sharedSecret.bytes)
sharedSecret: SRPKey,
padding: Int
) -> [UInt8] {
let HABK = H.hash(data: clientPublicKey.bytes(padding: padding) + clientProof + sharedSecret.bytes(padding: padding))
return [UInt8](HABK)
}

Expand All @@ -45,20 +48,21 @@ public struct SRP<H: HashFunction> {
salt: [UInt8],
clientPublicKey: SRPKey,
serverPublicKey: SRPKey,
hashSharedSecret: [UInt8]) -> [UInt8]
{
hashSharedSecret: [UInt8],
padding: Int
) -> [UInt8] {
// M = H(H(N)^ H(g)) | H(username) | salt | client key | server key | H(shared secret))
let N_xor_g = [UInt8](H.hash(data: configuration.N.bytes)) ^ [UInt8](H.hash(data: configuration.g.bytes))
let N_xor_g = [UInt8](H.hash(data: configuration.N.bytes.pad(to: padding))) ^ [UInt8](H.hash(data: configuration.g.bytes.pad(to: padding)))
let hashUser = H.hash(data: [UInt8](username.utf8))
let M1 = [UInt8](N_xor_g) + hashUser + salt
let M2 = clientPublicKey.bytes + serverPublicKey.bytes + hashSharedSecret
let M2 = clientPublicKey.bytes(padding: padding) + serverPublicKey.bytes(padding: padding) + hashSharedSecret
let M = H.hash(data: M1 + M2)
return [UInt8](M)
}

/// Calculate server verification code H(A | M1 | K)
static func calculateServerVerification(clientPublicKey: SRPKey, clientProof: [UInt8], sharedSecret: [UInt8]) -> [UInt8] {
let HAMK = H.hash(data: clientPublicKey.bytes + clientProof + sharedSecret)
static func calculateServerVerification(clientPublicKey: SRPKey, clientProof: [UInt8], sharedSecret: [UInt8], padding: Int) -> [UInt8] {
let HAMK = H.hash(data: clientPublicKey.bytes(padding: padding) + clientProof + sharedSecret)
return [UInt8](HAMK)
}
}
Loading

0 comments on commit 989b93b

Please sign in to comment.