- Implement SCRAM-SHA-512 (RFC 5802 with SHA-512) as a Luma-side SaslMechanism and register it ahead of Martin's SHA-256/SHA-1/PLAIN, so modern servers preferring SHA-512 authenticate with it. - Verify the math against Python-computed reference vectors (salted password, client proof, server signature) in SCRAMSHA512Tests and guard the mechanism registration in Scripts/verify.sh. - Confirm the TLS stack negotiates TLS 1.3 (negotiatedSSL + ALPN via SecureTransport, handshake verified against a TLS 1.3-only server) and document the TLS/SCRAM posture in SECURITY.md.
87 lines
3.7 KiB
Swift
87 lines
3.7 KiB
Swift
import XCTest
|
|
@testable import Luma
|
|
|
|
final class SCRAMSHA512Tests: XCTestCase {
|
|
/// Reference values computed with Python hashlib/hmac for the RFC 5802
|
|
/// vector inputs (password \"pencil\", salt QSXCR+Q6sek8bf92, 4096 rounds).
|
|
private let referenceAuthMessage =
|
|
"n=user,r=fyko+d2lbbFgONRv9qkxdawL"
|
|
+ ",r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096"
|
|
+ ",c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j"
|
|
|
|
func testSaltedPasswordMatchesReference() throws {
|
|
let salt = try XCTUnwrap(Data(base64Encoded: "QSXCR+Q6sek8bf92"))
|
|
let salted = SCRAMSHA512.saltedPassword(password: "pencil", salt: salt, iterations: 4096)
|
|
let hex = salted.map { String(format: "%02x", $0) }.joined()
|
|
XCTAssertEqual(
|
|
hex,
|
|
"97382788b15cbe09512d2d20b7e0b8832f8dbab4b7388395440535cd9395e0ffaa1625453b6fde746412bbf903d4bc1d5f448d57f2ac3dd1d2c04979a914ee65"
|
|
)
|
|
}
|
|
|
|
func testClientProofMatchesReference() throws {
|
|
let salt = try XCTUnwrap(Data(base64Encoded: "QSXCR+Q6sek8bf92"))
|
|
let salted = SCRAMSHA512.saltedPassword(password: "pencil", salt: salt, iterations: 4096)
|
|
let proof = SCRAMSHA512.clientProof(
|
|
saltedPassword: salted,
|
|
authMessage: referenceAuthMessage
|
|
)
|
|
XCTAssertEqual(
|
|
Data(proof).base64EncodedString(),
|
|
"VdS8LkrURiej1tG6iX+fqCXQfUnBb//d9llXYaH+ylUbDwBUz9geyR9fC4TewskRUM2tlYSalhAT4Aay1Q5dTA=="
|
|
)
|
|
}
|
|
|
|
func testServerSignatureMatchesReference() throws {
|
|
let salt = try XCTUnwrap(Data(base64Encoded: "QSXCR+Q6sek8bf92"))
|
|
let salted = SCRAMSHA512.saltedPassword(password: "pencil", salt: salt, iterations: 4096)
|
|
let signature = SCRAMSHA512.serverSignature(
|
|
saltedPassword: salted,
|
|
authMessage: referenceAuthMessage
|
|
)
|
|
XCTAssertEqual(
|
|
Data(signature).base64EncodedString(),
|
|
"14PAAuavk9hxBEkgB0brDxUhvWu+N16meYk+qxVNFqchR8QPohM09Y4Z6WaTCuX4C6nqMB9KIJTDm6RpSM990g=="
|
|
)
|
|
}
|
|
|
|
func testVerifyServerSignatureAcceptsAndRejects() throws {
|
|
let salt = try XCTUnwrap(Data(base64Encoded: "QSXCR+Q6sek8bf92"))
|
|
let salted = SCRAMSHA512.saltedPassword(password: "pencil", salt: salt, iterations: 4096)
|
|
let good = "v=14PAAuavk9hxBEkgB0brDxUhvWu+N16meYk+qxVNFqchR8QPohM09Y4Z6WaTCuX4C6nqMB9KIJTDm6RpSM990g=="
|
|
XCTAssertTrue(SCRAMSHA512.verifyServerSignature(
|
|
saltedPassword: salted,
|
|
authMessage: referenceAuthMessage,
|
|
finalMessage: good
|
|
))
|
|
XCTAssertFalse(SCRAMSHA512.verifyServerSignature(
|
|
saltedPassword: salted,
|
|
authMessage: referenceAuthMessage,
|
|
finalMessage: "v=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="
|
|
))
|
|
XCTAssertFalse(SCRAMSHA512.verifyServerSignature(
|
|
saltedPassword: salted,
|
|
authMessage: referenceAuthMessage,
|
|
finalMessage: "e=server-error",
|
|
))
|
|
}
|
|
|
|
func testParseServerFirst() throws {
|
|
let parsed = try SCRAMSHA512.parseServerFirst(
|
|
"r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096",
|
|
expectedNoncePrefix: "fyko+d2lbbFgONRv9qkxdawL"
|
|
)
|
|
XCTAssertEqual(parsed.nonce, "fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j")
|
|
XCTAssertEqual(parsed.salt, Data(base64Encoded: "QSXCR+Q6sek8bf92"))
|
|
XCTAssertEqual(parsed.iterations, 4096)
|
|
}
|
|
|
|
func testParseServerFirstRejectsWrongNonce() {
|
|
XCTAssertThrowsError(try SCRAMSHA512.parseServerFirst(
|
|
"r=OTHER,s=QSXCR+Q6sek8bf92,i=4096",
|
|
expectedNoncePrefix: "fyko+d2lbbFgONRv9qkxdawL"
|
|
))
|
|
}
|
|
}
|
|
|