Skip to content

Commit

Permalink
Merge pull request #80 from outfoxx/feature/check_trust
Browse files Browse the repository at this point in the history
Add `checkTrust` to `SecCertificate` to allow checking trust without returning a public key
  • Loading branch information
kdubb authored Nov 1, 2023
2 parents 4d6b38a + ee6d2a6 commit 84f27fa
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 0 deletions.
16 changes: 16 additions & 0 deletions Sources/ShieldSecurity/SecCertificate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
105 changes: 105 additions & 0 deletions Tests/SecCertificateTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 84f27fa

Please sign in to comment.