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.
104 lines
5.0 KiB
Swift
104 lines
5.0 KiB
Swift
import Foundation
|
|
import Security
|
|
|
|
// B4 · Two real, DER-decodable leaf certificates for the enrolled-identity
|
|
// persistence tests, plus the cleanup they need.
|
|
//
|
|
// Both carry the SAME public key (the `KnownGoodCSR` fixed key) and the SAME
|
|
// subject, and differ only in serial + validity — exactly the shape of a
|
|
// rotation: `SecItemAdd` treats them as two distinct items, while re-adding one
|
|
// of them byte-for-byte is `errSecDuplicateItem`.
|
|
//
|
|
// CLEANUP, and why it needs its own path: on macOS the file-based keychain
|
|
// OVERRIDES the `kSecAttrLabel` supplied at add time with the certificate's own
|
|
// subject summary (verified 2026-07-30: added under
|
|
// `…test-XYZ.device-leaf`, found only under `enrolled-device-fixture`). So the
|
|
// production `KeychainClientIdentityStore` label-keyed delete cannot remove them
|
|
// on macOS, and the tests must sweep by subject instead — `purgeFixtureLeaves()`
|
|
// runs both BEFORE (in case a previous run was killed) and AFTER every test that
|
|
// installs one. `SecItemDelete` removes ONE match per call here, hence the loop.
|
|
//
|
|
// Regenerated with (OpenSSL 3.0.18, key = KnownGoodCSR.privateKeyX963Base64):
|
|
// openssl req -x509 -new -key fixed.key.pem -subj "/CN=enrolled-device-fixture" \
|
|
// -days 3650 -sha256 -outform DER -out leaf1.der # then -days 3651 → leaf2
|
|
// openssl req -x509 -new -key chain.key.pem \
|
|
// -subj "/CN=webterm-enrollment-ca-fixture" -days 3650 -sha256 -outform DER
|
|
enum EnrolledLeafFixtures {
|
|
/// The subject summary macOS files these certificates under.
|
|
static let subjectSummary = "enrolled-device-fixture"
|
|
|
|
static var leaf: Data { der(leafBase64) }
|
|
/// A second, DISTINCT leaf (different serial) for the rotation case.
|
|
static var rotatedLeaf: Data { der(rotatedLeafBase64) }
|
|
/// An issuer certificate for the stored `caChain`.
|
|
static var issuer: Data { der(issuerBase64) }
|
|
|
|
private static func der(_ base64: String) -> Data {
|
|
Data(base64Encoded: base64, options: .ignoreUnknownCharacters)!
|
|
}
|
|
|
|
private static let leafBase64 = """
|
|
MIIBmTCCAT+gAwIBAgIUcHFyXdi5QFelGKhdzcsbFlgnYzUwCgYIKoZIzj0EAwIwIjEgMB4GA1UE\
|
|
AwwXZW5yb2xsZWQtZGV2aWNlLWZpeHR1cmUwHhcNMjYwNzMwMDc1NTAxWhcNMzYwNzI3MDc1NTAx\
|
|
WjAiMSAwHgYDVQQDDBdlbnJvbGxlZC1kZXZpY2UtZml4dHVyZTBZMBMGByqGSM49AgEGCCqGSM49\
|
|
AwEHA0IABGInyJAZfMKCGjVNFg801yeq2Ar+auw1/4IC+g9tIf7Xa/VDY59VGBU9Vh5LYA7fLzs5\
|
|
6X1TF7ZCweW4mcaiJ9KjUzBRMB0GA1UdDgQWBBRAua2aufygDDq2CJTBF31OG7cNSDAfBgNVHSME\
|
|
GDAWgBRAua2aufygDDq2CJTBF31OG7cNSDAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gA\
|
|
MEUCIQCoLSypPlXvtMsQRMpztt/RpbYKc6iTmBLypqOZc0MxTAIgF/MVWpaLLYVCmcKBPxf7y9yQ\
|
|
NX3c6BrYB/fi/WzJsmA=
|
|
"""
|
|
|
|
private static let rotatedLeafBase64 = """
|
|
MIIBmTCCAT+gAwIBAgIUarD2zMEMcxZJNQevMMJenCAlLpkwCgYIKoZIzj0EAwIwIjEgMB4GA1UE\
|
|
AwwXZW5yb2xsZWQtZGV2aWNlLWZpeHR1cmUwHhcNMjYwNzMwMDc1NTAyWhcNMzYwNzI4MDc1NTAy\
|
|
WjAiMSAwHgYDVQQDDBdlbnJvbGxlZC1kZXZpY2UtZml4dHVyZTBZMBMGByqGSM49AgEGCCqGSM49\
|
|
AwEHA0IABGInyJAZfMKCGjVNFg801yeq2Ar+auw1/4IC+g9tIf7Xa/VDY59VGBU9Vh5LYA7fLzs5\
|
|
6X1TF7ZCweW4mcaiJ9KjUzBRMB0GA1UdDgQWBBRAua2aufygDDq2CJTBF31OG7cNSDAfBgNVHSME\
|
|
GDAWgBRAua2aufygDDq2CJTBF31OG7cNSDAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gA\
|
|
MEUCID2FFmKH2+qHB8sICyDuxQPtyj/wOLE24t4ghBEitLRvAiEAnTQQ2FP8Wei+8ITL/K57UP2Z\
|
|
YtYbEokKkBCj2bMr3vk=
|
|
"""
|
|
|
|
private static let issuerBase64 = """
|
|
MIIBpTCCAUugAwIBAgIUIkl7VDf6o9lBxtWxh7zFxPYTpo8wCgYIKoZIzj0EAwIwKDEmMCQGA1UE\
|
|
Awwdd2VidGVybS1lbnJvbGxtZW50LWNhLWZpeHR1cmUwHhcNMjYwNzMwMDc1NTAyWhcNMzYwNzI3\
|
|
MDc1NTAyWjAoMSYwJAYDVQQDDB13ZWJ0ZXJtLWVucm9sbG1lbnQtY2EtZml4dHVyZTBZMBMGByqG\
|
|
SM49AgEGCCqGSM49AwEHA0IABM4iWgauVe86+SKZg+jlHkh8VbVf70pgMdGOcpJW+xWb5oqfzxY3\
|
|
fW6pIdn3SCo9PG7X22qGtq/PLgpv97wtJvajUzBRMB0GA1UdDgQWBBTT9mdiVZTWcatuYz3NkxCb\
|
|
DxDSAjAfBgNVHSMEGDAWgBTT9mdiVZTWcatuYz3NkxCbDxDSAjAPBgNVHRMBAf8EBTADAQH/MAoG\
|
|
CCqGSM49BAMCA0gAMEUCIQCxfF8zyRHMW7nSmieDoBxIsOXkMFVPko86THW3TbwrUwIgMZlBP5rz\
|
|
FT4Y/G2oA+87xceEDjCrA7FnRZrCrZ4AADk=
|
|
"""
|
|
|
|
private static func labelQuery() -> [String: Any] {
|
|
[
|
|
kSecClass as String: kSecClassCertificate,
|
|
kSecAttrLabel as String: subjectSummary,
|
|
]
|
|
}
|
|
|
|
/// How many fixture leaves are currently installed.
|
|
static func installedCount() -> Int {
|
|
var query = labelQuery()
|
|
query[kSecReturnRef as String] = true
|
|
query[kSecMatchLimit as String] = kSecMatchLimitAll
|
|
var result: CFTypeRef?
|
|
guard SecItemCopyMatching(query as CFDictionary, &result) == errSecSuccess else {
|
|
return 0
|
|
}
|
|
if let array = result as? [Any] { return array.count }
|
|
return result == nil ? 0 : 1
|
|
}
|
|
|
|
/// Remove every installed fixture leaf. Idempotent; safe to call when none
|
|
/// are installed.
|
|
static func purgeFixtureLeaves() {
|
|
var guardCounter = 0
|
|
let maxSweeps = 16 // a leaf per enroll in a test; never unbounded
|
|
while SecItemDelete(labelQuery() as CFDictionary) == errSecSuccess,
|
|
guardCounter < maxSweeps {
|
|
guardCounter += 1
|
|
}
|
|
}
|
|
}
|