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.
53 lines
2.4 KiB
Swift
53 lines
2.4 KiB
Swift
import Foundation
|
|
|
|
// B4 · Read back the fields of a PKCS#10 CSR so tests can assert on what was
|
|
// ACTUALLY sent to the control plane (subject CN, embedded public key) instead
|
|
// of trusting the encoder's own view. Uses the throwaway `TestDER` reader from
|
|
// CertificateSigningRequestTests.
|
|
|
|
/// The two CSR fields the store/rotation invariants are stated in terms of.
|
|
struct ParsedCSR: Equatable {
|
|
/// `CertificationRequestInfo.subject` — the single CN RDN.
|
|
let subjectCommonName: String
|
|
/// `subjectPKInfo` BIT STRING content: the X9.63 uncompressed point.
|
|
let publicPointX963: Data
|
|
/// The exact `CertificationRequestInfo` DER (the bytes that were signed).
|
|
let certificationRequestInfoDER: Data
|
|
}
|
|
|
|
/// Parse a `CertificationRequest` DER. `nil` when the shape is not the canonical
|
|
/// `SEQUENCE { info, algId, BIT STRING }` this package emits.
|
|
func parseCSR(_ der: Data) -> ParsedCSR? {
|
|
let bytes = [UInt8](der)
|
|
guard let outer = TestDER.read(bytes, at: 0), outer.end == bytes.count else { return nil }
|
|
let parts = TestDER.children(bytes, outer)
|
|
guard parts.count == 3 else { return nil }
|
|
|
|
let info = parts[0]
|
|
let infoChildren = TestDER.children(bytes, info)
|
|
guard infoChildren.count == 4 else { return nil }
|
|
|
|
// subject: SEQUENCE { SET { SEQUENCE { OID commonName, UTF8String } } }
|
|
let rdnSequence = TestDER.children(bytes, infoChildren[1])
|
|
guard let rdnSet = rdnSequence.first else { return nil }
|
|
let attributes = TestDER.children(bytes, rdnSet)
|
|
guard let attribute = attributes.first else { return nil }
|
|
let attributeParts = TestDER.children(bytes, attribute)
|
|
guard attributeParts.count == 2 else { return nil }
|
|
let commonNameBytes = Array(bytes[attributeParts[1].valueStart..<attributeParts[1].valueEnd])
|
|
guard let commonName = String(bytes: commonNameBytes, encoding: .utf8) else { return nil }
|
|
|
|
// subjectPKInfo: SEQUENCE { AlgorithmIdentifier, BIT STRING point }
|
|
let spkiChildren = TestDER.children(bytes, infoChildren[2])
|
|
guard spkiChildren.count == 2 else { return nil }
|
|
let bitString = spkiChildren[1]
|
|
guard bitString.valueEnd > bitString.valueStart + 1 else { return nil }
|
|
let point = Data(bytes[(bitString.valueStart + 1)..<bitString.valueEnd])
|
|
|
|
return ParsedCSR(
|
|
subjectCommonName: commonName,
|
|
publicPointX963: point,
|
|
certificationRequestInfoDER: Data(bytes[info.start..<info.end])
|
|
)
|
|
}
|