1import Foundation
2import X509
3
4func load(_ path: String) throws -> Certificate {
5 try Certificate(pemEncoded: String(contentsOfFile: path, encoding: .utf8))
6}
7
8/// RFC 5280 path building and validation against the given roots, plus the server name.
9func verify(leaf: Certificate, intermediates: [Certificate], roots: [Certificate], host: String) async -> String {
10 var verifier = Verifier(rootCertificates: CertificateStore(roots)) {
11 RFC5280Policy(validationTime: Date())
12 ServerIdentityPolicy(serverHostname: host, serverIP: nil)
13 }
14 let result = await verifier.validate(leaf: leaf, intermediates: CertificateStore(intermediates))
15 switch result {
16 case .validCertificate(let chain):
17 return "valid, " + chain.map { cert in
18 cert.subject.flatMap { $0 }.first { $0.type == .RDNAttributeType.commonName }.map { "\($0.value)" } ?? "?"
19 }.joined(separator: " -> ")
20 case .couldNotValidate(let failures):
21 // one entry per candidate path that a policy rejected; empty when no path to a root exists
22 guard let first = failures.first else { return "could not validate: no path to a trusted root" }
23 return "could not validate: \(first.policyFailureReason)"
24 }
25}
26
27let root = try load("root.pem"), intermediate = try load("intermediate.pem")
28print("good chain:", await verify(leaf: try load("leaf.pem"), intermediates: [intermediate], roots: [root], host: "api.example.com"))
29print("impostor root:", await verify(leaf: try load("leaf.pem"), intermediates: [intermediate], roots: [try load("impostor-root.pem")], host: "api.example.com"))
30print("expired leaf:", await verify(leaf: try load("expired.pem"), intermediates: [intermediate], roots: [root], host: "expired.example.com"))
31print("missing intermediate:", await verify(leaf: try load("leaf.pem"), intermediates: [], roots: [root], host: "api.example.com"))
32