diff --git a/Sources/ShieldSecurity/SecCertificate.swift b/Sources/ShieldSecurity/SecCertificate.swift index 9be76e4d2..303cd9e68 100644 --- a/Sources/ShieldSecurity/SecCertificate.swift +++ b/Sources/ShieldSecurity/SecCertificate.swift @@ -100,6 +100,22 @@ public extension SecCertificate { } #endif + func checkTrust(trustedCertificates: [SecCertificate]) throws { + + let trust = try createCertificateValidationTrust(anchorCertificates: trustedCertificates) + + try evaluateTrust(trust) + } + +#if swift(>=5.5) + func checkTrust(trustedCertificates: [SecCertificate]) async throws { + + let trust = try createCertificateValidationTrust(anchorCertificates: trustedCertificates) + + try await evaluateTrust(trust) + } +#endif + private func createCertificateValidationTrust(anchorCertificates: [SecCertificate]) throws -> SecTrust { let policy = SecPolicyCreateBasicX509() diff --git a/Tests/SecCertificateTests.swift b/Tests/SecCertificateTests.swift index fa6aca830..a269b5a10 100644 --- a/Tests/SecCertificateTests.swift +++ b/Tests/SecCertificateTests.swift @@ -194,6 +194,56 @@ class SecCertificateTests: XCTestCase { XCTAssertEqual(certSec.derEncoded, try SecCertificate.load(der: certDer).derEncoded) } + func testCheckTrust() throws { + + let rootName = try NameBuilder().add("Unit Testing Root", forTypeName: "CN").name + let rootID = try Random.generate(count: 10) + let rootSerialNumber = try Certificate.Builder.randomSerialNumber() + let rootKeyHash = try Digester.digest(keyPair.encodedPublicKey(), using: .sha1) + let rootCertData = + try Certificate.Builder() + .serialNumber(rootSerialNumber) + .subject(name: rootName, uniqueID: rootID) + .subjectAlternativeNames(names: .dnsName("io.outfoxx.shield.tests.ca")) + .publicKey(keyPair: keyPair, usage: [.keyCertSign, .cRLSign]) + .subjectKeyIdentifier(rootKeyHash) + .issuer(name: rootName) + .issuerAlternativeNames(names: .dnsName("io.outfoxx.shield.tests.ca")) + .basicConstraints(ca: true) + .valid(for: 86400 * 5) + .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256) + .encoded() + output(rootCertData) + + let rootCert = try SecCertificate.from(data: rootCertData) + + let certKeyPair = try generateTestKeyPairChecked(type: .ec, keySize: 256, flags: []) + defer { try? certKeyPair.delete() } + + let certName = try NameBuilder().add("Unit Testing", forTypeName: "CN").name + let certID = try Random.generate(count: 10) + + let certData = + try Certificate.Builder() + .serialNumber(Certificate.Builder.randomSerialNumber()) + .subject(name: certName, uniqueID: certID) + .subjectAlternativeNames(names: .dnsName("io.outfoxx.shield.tests.cert")) + .publicKey(keyPair: certKeyPair, usage: [.keyEncipherment, .digitalSignature]) + .computeSubjectKeyIdentifier() + .issuer(name: rootName) + .issuerAlternativeNames(names: .dnsName("io.outfoxx.shield.tests.ca")) + .authorityKeyIdentifier(rootKeyHash, certIssuer: [.directoryName(rootName)], certSerialNumber: rootSerialNumber) + .valid(for: 86400 * 5) + .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256) + .encoded() + output(certData) + + + let cert = try SecCertificate.from(data: certData) + + XCTAssertNoThrow(try cert.checkTrust(trustedCertificates: [rootCert])) + } + func testValidatedPublicKey() throws { let rootName = try NameBuilder().add("Unit Testing Root", forTypeName: "CN").name @@ -260,6 +310,61 @@ class SecCertificateTests: XCTestCase { } #if swift(>=5.5) + func testCheckTrustAsync() async throws { + + let rootName = try NameBuilder().add("Unit Testing Root", forTypeName: "CN").name + let rootID = try Random.generate(count: 10) + let rootSerialNumber = try Certificate.Builder.randomSerialNumber() + let rootKeyHash = try Digester.digest(keyPair.encodedPublicKey(), using: .sha1) + let rootCertData = + try Certificate.Builder() + .serialNumber(rootSerialNumber) + .subject(name: rootName, uniqueID: rootID) + .subjectAlternativeNames(names: .dnsName("io.outfoxx.shield.tests.ca")) + .publicKey(keyPair: keyPair, usage: [.keyCertSign, .cRLSign]) + .subjectKeyIdentifier(rootKeyHash) + .issuer(name: rootName) + .issuerAlternativeNames(names: .dnsName("io.outfoxx.shield.tests.ca")) + .basicConstraints(ca: true) + .valid(for: 86400 * 5) + .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256) + .encoded() + output(rootCertData) + + let rootCert = try SecCertificate.from(data: rootCertData) + + let certKeyPair = try generateTestKeyPairChecked(type: .ec, keySize: 256, flags: []) + defer { try? certKeyPair.delete() } + + let certName = try NameBuilder().add("Unit Testing", forTypeName: "CN").name + let certID = try Random.generate(count: 10) + + let certData = + try Certificate.Builder() + .serialNumber(Certificate.Builder.randomSerialNumber()) + .subject(name: certName, uniqueID: certID) + .subjectAlternativeNames(names: .dnsName("io.outfoxx.shield.tests.cert")) + .publicKey(keyPair: certKeyPair, usage: [.keyEncipherment, .digitalSignature]) + .computeSubjectKeyIdentifier() + .issuer(name: rootName) + .issuerAlternativeNames(names: .dnsName("io.outfoxx.shield.tests.ca")) + .authorityKeyIdentifier(rootKeyHash, certIssuer: [.directoryName(rootName)], certSerialNumber: rootSerialNumber) + .valid(for: 86400 * 5) + .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256) + .encoded() + output(certData) + + + let cert = try SecCertificate.from(data: certData) + + do { + try await cert.checkTrust(trustedCertificates: [rootCert]) + } + catch { + XCTFail("Unexpected error: \(error)") + } + } + func testValidatedPublicKeyAsync() async throws { let rootName = try NameBuilder().add("Unit Testing Root", forTypeName: "CN").name