Files
web-terminal/ios/Packages/ClientTLS/Tests/ClientTLSTests/CertificateSigningRequestVectorTests.swift
Yaojia Wang a5fa843f00 test(ios): ClientTLS coverage 55.76% -> 89.49%, gate it, fix the three dead CI legs
ClientTLS was the most security-sensitive package in the tree and the least
covered, and it was not in the coverage gate at all (the gate's 4-package set
predates it). 48 -> 84 tests against the real macOS keychain, serialized with a
custom Testing trait after a @globalActor proved insufficient (actors yield at
await, so cross-await critical sections got interleaved by other cases' cleanup).

CI: the app/ipad/ios17 legs ran a bundle containing LiveServerSmokeTests, which
spawns tsx, with no npm ci -- a hard failure, not a skip, on a bare checkout.
Adds the missing iPad UI-test leg, and makes a missing iOS 17 runtime fail loudly
instead of silently reporting green.
2026-07-30 12:45:26 +02:00

122 lines
4.5 KiB
Swift

import Foundation
import Security
import Testing
@testable import ClientTLS
// B4 · Pins the hand-written PKCS#10 encoder against an EXTERNAL known-good
// vector (OpenSSL) plus the two boundary behaviours the structural tests in
// CertificateSigningRequestTests don't reach: a signer that hands back a
// non-X9.63 public key, and a subject long enough to need a long-form DER
// length. `verifyCsrPoPEc` re-serializes `CertificationRequestInfo` server-side
// before checking the PoP, so a single byte of drift breaks enrollment.
@Test("CertificationRequestInfo is byte-identical to OpenSSL's for the same key + subject")
func csrRequestInfoMatchesOpenSSLVector() throws {
// Arrange the exact key OpenSSL used for the embedded vector.
let signer = try KnownGoodCSR.fixedKey()
// Act
let der = try CertificateSigningRequest.der(
subjectCommonName: KnownGoodCSR.subjectCommonName, signer: signer
)
// Assert the signed half must match OpenSSL byte for byte.
let parsed = try #require(parseCSR(der))
#expect(parsed.certificationRequestInfoDER == KnownGoodCSR.certificationRequestInfoDER)
}
@Test("the vector's self-signature verifies over the exact CertificationRequestInfo bytes")
func csrVectorSignatureVerifies() throws {
// Arrange
let signer = try KnownGoodCSR.fixedKey()
let der = try CertificateSigningRequest.der(
subjectCommonName: KnownGoodCSR.subjectCommonName, signer: signer
)
let bytes = [UInt8](der)
let outer = try #require(TestDER.read(bytes, at: 0))
let parts = TestDER.children(bytes, outer)
let signature = Data(bytes[(parts[2].valueStart + 1)..<parts[2].valueEnd])
// Act verify against the public point embedded in the CSR itself.
let parsed = try #require(parseCSR(der))
let publicKey = try #require(
SecKeyCreateWithData(
parsed.publicPointX963 as CFData,
[
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
kSecAttrKeySizeInBits as String: 256,
] as CFDictionary,
nil
)
)
var error: Unmanaged<CFError>?
let verified = SecKeyVerifySignature(
publicKey,
.ecdsaSignatureMessageX962SHA256,
KnownGoodCSR.certificationRequestInfoDER as CFData,
signature as CFData,
&error
)
// Assert
#expect(verified, "\(String(describing: error?.takeRetainedValue()))")
}
@Test("a signer whose public key is not a 65-byte uncompressed point is rejected")
func csrRejectsNonX963PublicKey() {
// Arrange a compressed (33-byte) point: valid EC, wrong encoding for SPKI.
let compressed = Data([0x02] + [UInt8](repeating: 0x11, count: 32))
let signer = StubSigner(publicKey: compressed)
// Act / Assert must fail BEFORE signing (no PoP over a bogus SPKI).
#expect(throws: CertificateSigningRequest.CSRError.invalidPublicKey) {
_ = try CertificateSigningRequest.der(subjectCommonName: "device", signer: signer)
}
#expect(signer.signCallCount == 0)
}
@Test("a subject long enough to need a 2-byte DER length still round-trips")
func csrLongSubjectUsesLongFormLength() throws {
// Arrange 300 chars pushes CertificationRequestInfo past 255 bytes, so its
// length must be encoded long-form as 0x82 <hi> <lo>.
let longCommonName = String(repeating: "d", count: 300)
let signer = try KnownGoodCSR.fixedKey()
// Act
let der = try CertificateSigningRequest.der(
subjectCommonName: longCommonName, signer: signer
)
// Assert
let parsed = try #require(parseCSR(der))
#expect(parsed.subjectCommonName == longCommonName)
let infoBytes = [UInt8](parsed.certificationRequestInfoDER)
#expect(infoBytes[1] == 0x82) // long form, two length bytes
let declaredLength = Int(infoBytes[2]) << 8 | Int(infoBytes[3])
#expect(declaredLength == infoBytes.count - 4) // minimal + accurate
}
// MARK: - Helpers
/// A `P256HardwareKey` that returns a caller-chosen public key and records
/// whether it was ever asked to sign.
private final class StubSigner: P256HardwareKey, @unchecked Sendable {
private let publicKey: Data
private let lock = NSLock()
private var signCalls = 0
init(publicKey: Data) {
self.publicKey = publicKey
}
var signCallCount: Int { lock.withLock { signCalls } }
func publicKeyX963() throws -> Data { publicKey }
func sign(_ message: Data) throws -> Data {
lock.withLock { signCalls += 1 }
return Data([0x30, 0x00])
}
}