// // 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")) } } }