// 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: "decode", dependencies: [ .product(name: "X509", package: "swift-certificates"), .product(name: "Crypto", package: "swift-crypto"), ] ), ]) import Cryptoimport Foundationimport SwiftASN1import X509 let pem = try String(contentsOfFile: "cert.pem", encoding: .utf8)let certificate = try Certificate(pemEncoded: pem) // Certificate(derEncoded:) for binary input print("subject: \(certificate.subject)")let commonName = certificate.subject.flatMap { $0 }.first { $0.type == .RDNAttributeType.commonName }print("common name: \(commonName.map { String(describing: $0.value) } ?? "-")")print("issuer: \(certificate.issuer)")print("serial: \(certificate.serialNumber)")print("valid: \(certificate.notValidBefore) -> \(certificate.notValidAfter)")print("signed with: \(certificate.signatureAlgorithm)") // the issuer's algorithm // typed extension accessors: nil when absent, throwing when malformedif let names = try certificate.extensions.subjectAlternativeNames { for case .dnsName(let host) in names { print("DNS name: \(host)") }}if let keyUsage = try certificate.extensions.keyUsage { print("key usage: \(keyUsage)") }if P256.Signing.PublicKey(certificate.publicKey) != nil { print("key: ECDSA P-256") } var serializer = DER.Serializer()try serializer.serialize(certificate)let fingerprint = SHA256.hash(data: serializer.serializedBytes) // the whole DER certificateprint("SHA-256 fingerprint: \(fingerprint.map { String(format: "%02X", $0) }.joined(separator: ":"))")print("self-signed signature valid: \(certificate.publicKey.isValidSignature(certificate.signature, for: certificate))") How it works
Certificate(pemEncoded:)parses aBEGIN CERTIFICATEblock;Certificate(derEncoded:)takes the binary form.subjectandissuerareDistinguishedNamevalues; flattening their RDNs and matching.RDNAttributeType.commonNameextracts the CN without string parsing.- Extension accessors such as
subjectAlternativeNamesandkeyUsageare typed:nilwhen the extension is absent, a thrown error when it is malformed. P256.Signing.PublicKey(certificate.publicKey)converts the key to a CryptoKit type when it is P-256 (and returnsnilotherwise), ready forisValidSignature.- The fingerprint hashes the DER of the whole certificate, which
DER.Serializerreproduces from the parsed value;publicKey.isValidSignature(_:for:)checks who signed it.
Gotchas
descriptionof the serial number prints colon-separated hex without leading zeros (4:a1:b2:…:7), not OpenSSL's04A1B2C3D4E5F607. Compare the serial's bytes, not strings.- Names print as RFC 4514 strings (
C=US,O=…,CN=…), which list the attributes in reverse of the order stored in the certificate — and of OpenSSL's one-line form. Another reason to compare structured values. - Checking one signature is not chain validation. For trust decisions use the package's
Verifierwith aCertificateStoreof roots and a policy such asRFC5280Policy. SubjectAlternativeNamesis its own collection type, sotry extensions.subjectAlternativeNames ?? []does not compile; unwrap it withif let, as above.