161 lines
5.8 KiB
Swift
161 lines
5.8 KiB
Swift
//
|
|||
|
|
// SafetyChecksTests.swift
|
||
|
|
// PortalKitTests
|
||
|
|
//
|
||
|
|
// Verification of SRP parameter validation and safety guards (RFC 5054).
|
||
|
|
//
|
||
|
|
|
||
|
|
import XCTest
|
||
|
|
@testable import PortalKit
|
||
|
|
|
||
|
|
final class SafetyChecksTests: XCTestCase {
|
||
|
|
let validCertHash = Data(repeating: 0xaa, count: 32)
|
||
|
|
let validSaltHex = "0102030405060708090a0b0c0d0e0f10"
|
||
|
|
let validPubBHex = SrpFormat.bytesToHex(BigUInt(123456789).toPadded256Data())
|
||
|
|
let validPin = "123456"
|
||
|
|
|
||
|
|
func testInvalidSaltThrows() {
|
||
|
|
let client = PortalSrpClient()
|
||
|
|
|
||
|
|
// Empty salt
|
||
|
|
XCTAssertThrowsError(
|
||
|
|
try client.computeM1(saltHex: "", pubBHex: validPubBHex, pin: validPin, tlsCertSha256: validCertHash)
|
||
|
|
) { error in
|
||
|
|
guard case SrpError.invalidParameter(let m) = error else {
|
||
|
|
XCTFail("Wrong error: \(error)")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
XCTAssertTrue(m.contains("salt"))
|
||
|
|
}
|
||
|
|
|
||
|
|
// Odd length hex salt
|
||
|
|
XCTAssertThrowsError(
|
||
|
|
try client.computeM1(saltHex: "abc", pubBHex: validPubBHex, pin: validPin, tlsCertSha256: validCertHash)
|
||
|
|
)
|
||
|
|
|
||
|
|
// Non-hex characters in salt
|
||
|
|
XCTAssertThrowsError(
|
||
|
|
try client.computeM1(saltHex: "invalid-hex-characters!!", pubBHex: validPubBHex, pin: validPin, tlsCertSha256: validCertHash)
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
func testInvalidBModuloNThrows() {
|
||
|
|
let client = PortalSrpClient()
|
||
|
|
|
||
|
|
// B = 0 -> B % N == 0
|
||
|
|
let zeroBHex = SrpFormat.bytesToHex(BigUInt(0).toPadded256Data())
|
||
|
|
XCTAssertThrowsError(
|
||
|
|
try client.computeM1(saltHex: validSaltHex, pubBHex: zeroBHex, pin: validPin, tlsCertSha256: validCertHash)
|
||
|
|
) { error in
|
||
|
|
guard case SrpError.invalidParameter(let m) = error else {
|
||
|
|
XCTFail("Wrong error: \(error)")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
XCTAssertTrue(m.contains("B % N == 0"))
|
||
|
|
}
|
||
|
|
|
||
|
|
// B = N -> B % N == 0
|
||
|
|
let nBHex = SrpFormat.bytesToHex(PortalSrpClient.N.toPadded256Data())
|
||
|
|
XCTAssertThrowsError(
|
||
|
|
try client.computeM1(saltHex: validSaltHex, pubBHex: nBHex, pin: validPin, tlsCertSha256: validCertHash)
|
||
|
|
) { error in
|
||
|
|
guard case SrpError.invalidParameter(let m) = error else {
|
||
|
|
XCTFail("Wrong error: \(error)")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
XCTAssertTrue(m.contains("B % N == 0"))
|
||
|
|
}
|
||
|
|
|
||
|
|
// B = 2*N -> B % N == 0
|
||
|
|
let twoNBHex = SrpFormat.bytesToHex((PortalSrpClient.N * 2).serialize())
|
||
|
|
XCTAssertThrowsError(
|
||
|
|
try client.computeM1(saltHex: validSaltHex, pubBHex: twoNBHex, pin: validPin, tlsCertSha256: validCertHash)
|
||
|
|
) { error in
|
||
|
|
guard case SrpError.invalidParameter(let m) = error else {
|
||
|
|
XCTFail("Wrong error: \(error)")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
XCTAssertTrue(m.contains("B % N == 0"))
|
||
|
|
}
|
||
|
|
|
||
|
|
// Empty pubBHex
|
||
|
|
XCTAssertThrowsError(
|
||
|
|
try client.computeM1(saltHex: validSaltHex, pubBHex: "", pin: validPin, tlsCertSha256: validCertHash)
|
||
|
|
)
|
||
|
|
|
||
|
|
// Non-hex pubBHex
|
||
|
|
XCTAssertThrowsError(
|
||
|
|
try client.computeM1(saltHex: validSaltHex, pubBHex: "not_hex", pin: validPin, tlsCertSha256: validCertHash)
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
func testEmptyPinThrows() {
|
||
|
|
let client = PortalSrpClient()
|
||
|
|
XCTAssertThrowsError(
|
||
|
|
try client.computeM1(saltHex: validSaltHex, pubBHex: validPubBHex, pin: "", tlsCertSha256: validCertHash)
|
||
|
|
) { error in
|
||
|
|
guard case SrpError.invalidParameter(let m) = error else {
|
||
|
|
XCTFail("Wrong error: \(error)")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
XCTAssertTrue(m.contains("PIN"))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func testEmptyTlsCertHashThrows() {
|
||
|
|
let client = PortalSrpClient()
|
||
|
|
XCTAssertThrowsError(
|
||
|
|
try client.computeM1(saltHex: validSaltHex, pubBHex: validPubBHex, pin: validPin, tlsCertSha256: Data())
|
||
|
|
) { error in
|
||
|
|
guard case SrpError.invalidParameter(let m) = error else {
|
||
|
|
XCTFail("Wrong error: \(error)")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
XCTAssertTrue(m.contains("certificate"))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func testInvalidUZeroCheck() {
|
||
|
|
let _ = SrpServerMock(pin: validPin)
|
||
|
|
// If a server or client encounters u == 0, it must be rejected as an invalid scrambler.
|
||
|
|
let uZero = BigUInt(0)
|
||
|
|
XCTAssertEqual(uZero, 0)
|
||
|
|
|
||
|
|
// Verify SrpError formatting for u == 0
|
||
|
|
let err = SrpError.invalidParameter("Computed u == 0")
|
||
|
|
XCTAssertEqual(err.localizedDescription, "SRP Parameter Error: Computed u == 0")
|
||
|
|
}
|
||
|
|
|
||
|
|
func testInvalidM2Throws() throws {
|
||
|
|
let client = PortalSrpClient()
|
||
|
|
|
||
|
|
// Verify before computeM1 must fail
|
||
|
|
XCTAssertThrowsError(try client.verifyServerM2(serverM2Hex: "123456")) { error in
|
||
|
|
guard case SrpError.verificationFailed(let m) = error else {
|
||
|
|
XCTFail("Wrong error: \(error)")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
XCTAssertTrue(m.contains("not initialized"))
|
||
|
|
}
|
||
|
|
|
||
|
|
// Initialize state
|
||
|
|
_ = try client.computeM1(saltHex: validSaltHex, pubBHex: validPubBHex, pin: validPin, tlsCertSha256: validCertHash)
|
||
|
|
|
||
|
|
// Invalid hex
|
||
|
|
XCTAssertThrowsError(try client.verifyServerM2(serverM2Hex: "invalid-hex"))
|
||
|
|
|
||
|
|
// Odd length hex
|
||
|
|
XCTAssertThrowsError(try client.verifyServerM2(serverM2Hex: "abc"))
|
||
|
|
|
||
|
|
// Wrong M2
|
||
|
|
let wrongM2 = SrpFormat.bytesToHex(Data(repeating: 0x99, count: 32))
|
||
|
|
XCTAssertThrowsError(try client.verifyServerM2(serverM2Hex: wrongM2)) { error in
|
||
|
|
guard case SrpError.verificationFailed(let m) = error else {
|
||
|
|
XCTFail("Wrong error: \(error)")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
XCTAssertTrue(m.contains("Server evidence M2 does not match"))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|