Files

161 lines
5.8 KiB
Swift
Raw Permalink Normal View History

2026-09-13 12:15:36 -07:00
//
// 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"))
}
}
}