// swift-tools-version:6.2import PackageDescription let package = Package( name: "howto", platforms: [.macOS(.v15)], dependencies: [ .package(url: "https://github.com/apple/swift-certificates.git", exact: "1.21.0"), .package(url: "https://github.com/apple/swift-crypto.git", exact: "5.0.3"), ], targets: [ .executableTarget( name: "extract", dependencies: [ .product(name: "X509", package: "swift-certificates"), .product(name: "Crypto", package: "swift-crypto"), ] ), ]) import Cryptoimport Foundationimport SwiftASN1import X509 let certificate = try Certificate(pemEncoded: String(contentsOfFile: "cert.pem", encoding: .utf8))let publicKey = certificate.publicKey // Certificate.PublicKey: P-256/384/521, RSA or Ed25519 // The SPKI: Certificate.PublicKey serializes as SubjectPublicKeyInfo ("PUBLIC KEY")let spkiPEM = try publicKey.serializeAsPEM().pemStringprint(spkiPEM)var serializer = DER.Serializer()try serializer.serialize(publicKey)let spkiDER = serializer.serializedBytes let digest = SHA256.hash(data: spkiDER)print("SPKI SHA-256: \(digest.map { String(format: "%02x", $0) }.joined(separator: ":"))")print("SPKI pin (base64): \(Data(digest).base64EncodedString())") // Not the SPKI, despite the name: the BIT STRING inside it — here the bare 65-byte EC pointprint("subjectPublicKeyInfoBytes: \(publicKey.subjectPublicKeyInfoBytes.count) bytes") // Does key.pem belong to this certificate?let privateKey = try P256.Signing.PrivateKey(pemRepresentation: String(contentsOfFile: "key.pem", encoding: .utf8))print("key.pem matches: \(Certificate.PublicKey(privateKey.publicKey) == publicKey)") How it works
certificate.publicKeyis aCertificate.PublicKey;serializeAsPEM()writes it as a SubjectPublicKeyInfo PEM.DER.Serializerproduces the SPKI DER; SHA-256 over it is the key fingerprint (45:ca:a7:e3:…:7b:5f), and its base64 (Rcqn43J0…e18=) the SPKI pin.Certificate.PublicKey(privateKey.publicKey) == certificate.publicKeycompares keys by value:truemeanskey.pembelongs to the certificate.P256.Signing.PublicKey(certificate.publicKey)converts the key to a CryptoKit type for signature verification (and returnsnilfor other algorithms).
Gotchas
subjectPublicKeyInfoBytesis not the SubjectPublicKeyInfo despite its name: it returns the BIT STRING inside it, the bare 65-byte EC point. Serialize the key itself for fingerprints and pins.pemStringends without a trailing newline; add one when the file must match OpenSSL's output byte for byte.- The certificate's own hash (over the whole DER) changes at every renewal; the SPKI hash stays the same while the key does.