From fb89843395e5e22bfe0daf9f661dbdf47ef31d51 Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Fri, 26 Apr 2024 07:00:49 +1000
Subject: [PATCH 01/30] WIP attestation

---
 .../AttestationConveyancePreference.swift     |   4 +-
 .../Registration/AttestationObject.swift      |  18 +-
 .../Ceremonies/Registration/Credential.swift  |   2 +
 .../Formats/PackedAttestation.swift           | 222 +++++++++---------
 Sources/WebAuthn/WebAuthnManager.swift        |   1 +
 5 files changed, 126 insertions(+), 121 deletions(-)

diff --git a/Sources/WebAuthn/Ceremonies/Registration/AttestationConveyancePreference.swift b/Sources/WebAuthn/Ceremonies/Registration/AttestationConveyancePreference.swift
index cba6e10a..69de1e0d 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/AttestationConveyancePreference.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/AttestationConveyancePreference.swift
@@ -18,7 +18,7 @@
 public enum AttestationConveyancePreference: String, Encodable {
     /// Indicates the Relying Party is not interested in authenticator attestation.
     case none
-    // case indirect
-    // case direct
+    //case indirect
+    case direct
     // case enterprise
 }
diff --git a/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift b/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
index d03bb646..be5242dc 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
@@ -56,21 +56,21 @@ public struct AttestationObject {
             throw WebAuthnError.unsupportedCredentialPublicKeyAlgorithm
         }
 
-        // let pemRootCertificates = pemRootCertificatesByFormat[format] ?? []
+        let pemRootCertificates = pemRootCertificatesByFormat[format] ?? []
         switch format {
         case .none:
             // if format is `none` statement must be empty
             guard attestationStatement == .map([:]) else {
                 throw WebAuthnError.attestationStatementMustBeEmpty
             }
-        // case .packed:
-        //     try await PackedAttestation.verify(
-        //         attStmt: attestationStatement,
-        //         authenticatorData: rawAuthenticatorData,
-        //         clientDataHash: Data(clientDataHash),
-        //         credentialPublicKey: credentialPublicKey,
-        //         pemRootCertificates: pemRootCertificates
-        //     )
+        case .packed:
+            try await PackedAttestation.verify(
+                attStmt: attestationStatement,
+                authenticatorData: Data(rawAuthenticatorData),
+                clientDataHash: Data(clientDataHash),
+                credentialPublicKey: credentialPublicKey,
+                pemRootCertificates: pemRootCertificates
+            )
         // case .tpm:
         //     try TPMAttestation.verify(
         //         attStmt: attestationStatement,
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Credential.swift b/Sources/WebAuthn/Ceremonies/Registration/Credential.swift
index 1c656f4d..32288d9f 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Credential.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Credential.swift
@@ -41,6 +41,8 @@ public struct Credential {
 
     // MARK: Optional content
 
+    public let aaguid: [UInt8]?
+    
     public let attestationObject: AttestationObject
 
     public let attestationClientDataJSON: CollectedClientData
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
index 4e835994..28e2c0c9 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
@@ -12,124 +12,126 @@
 //
 //===----------------------------------------------------------------------===//
 
-// // 🚨 WIP
+import Foundation
+import SwiftCBOR
+import SwiftASN1
+import X509
+import Crypto
+import _CryptoExtras
 
-// import Foundation
-// import SwiftCBOR
-// import X509
-// import Crypto
+struct PackedAttestation {
+    enum PackedAttestationError: Error {
+        case invalidAlg
+        case invalidSig
+        case invalidX5C
+        case invalidLeafCertificate
+        case invalidLeafCertificatePublicKey
+        case missingAttestationCertificate
+        case algDoesNotMatch
+        case missingAttestedCredential
+        // Authenticator data cannot be verified
+        case invalidVerificationData
+        case notImplemented
+    }
 
-// /// 🚨 WIP
-// struct PackedAttestation {
-//     enum PackedAttestationError: Error {
-//         case invalidAlg
-//         case invalidSig
-//         case invalidX5C
-//         case invalidLeafCertificate
-//         case missingAttestationCertificate
-//         case algDoesNotMatch
-//         case missingAttestedCredential
-//         case notImplemented
-//     }
+    static func verify(
+        attStmt: CBOR,
+        authenticatorData: Data,
+        clientDataHash: Data,
+        credentialPublicKey: CredentialPublicKey,
+        pemRootCertificates: [Data]
+    ) async throws {
+        guard let algCBOR = attStmt["alg"],
+            case let .negativeInt(algorithmNegative) = algCBOR,
+            let alg = COSEAlgorithmIdentifier(rawValue: -1 - Int(algorithmNegative)) else {
+            throw PackedAttestationError.invalidAlg
+        }
+        guard let sigCBOR = attStmt["sig"], case let .byteString(sig) = sigCBOR else {
+            throw PackedAttestationError.invalidSig
+        }
+        
+        let verificationData = authenticatorData + clientDataHash
 
-//     static func verify(
-//         attStmt: CBOR,
-//         authenticatorData: Data,
-//         clientDataHash: Data,
-//         credentialPublicKey: CredentialPublicKey,
-//         pemRootCertificates: [Data]
-//     ) async throws {
-//         guard let algCBOR = attStmt["alg"],
-//             case let .negativeInt(algorithmNegative) = algCBOR,
-//             let alg = COSEAlgorithmIdentifier(rawValue: -1 - Int(algorithmNegative)) else {
-//             throw PackedAttestationError.invalidAlg
-//         }
-//         guard let sigCBOR = attStmt["sig"], case let .byteString(sig) = sigCBOR else {
-//             throw PackedAttestationError.invalidSig
-//         }
+        if let x5cCBOR = attStmt["x5c"] {
+            guard case let .array(x5cCBOR) = x5cCBOR else {
+                throw PackedAttestationError.invalidX5C
+            }
 
-//         let verificationData = authenticatorData + clientDataHash
+            let x5c: [Certificate] = try x5cCBOR.map {
+                guard case let .byteString(certificate) = $0 else {
+                    throw PackedAttestationError.invalidX5C
+                }
+                return try Certificate(derEncoded: certificate)
+            }
+            guard let leafCertificate = x5c.first else { throw PackedAttestationError.invalidX5C }
+            let intermediates = CertificateStore(x5c[1...])
+            let rootCertificates = CertificateStore(
+                try pemRootCertificates.map { try Certificate(derEncoded: [UInt8]($0)) }
+            )
 
-//         if let x5cCBOR = attStmt["x5c"] {
-//             guard case let .array(x5cCBOR) = x5cCBOR else {
-//                 throw PackedAttestationError.invalidX5C
-//             }
+            var verifier = Verifier(rootCertificates: rootCertificates) {
+                // TODO: do we really want to validate a cert expiry for devices that cannot be updated?
+                // An expired device cert just means that the device is "old". 
+                RFC5280Policy(validationTime: Date())
+            }
+            let verifierResult: VerificationResult = await verifier.validate(
+                leafCertificate: leafCertificate,
+                intermediates: intermediates
+            )
+            guard case .validCertificate = verifierResult else {
+                throw PackedAttestationError.invalidLeafCertificate
+            }
 
-//             let x5c: [Certificate] = try x5cCBOR.map {
-//                 guard case let .byteString(certificate) = $0 else {
-//                     throw PackedAttestationError.invalidX5C
-//                 }
-//                 return try Certificate(derEncoded: certificate)
-//             }
-//             guard let leafCertificate = x5c.first else { throw PackedAttestationError.invalidX5C }
-//             let intermediates = CertificateStore(x5c[1...])
-//             let rootCertificates = CertificateStore(
-//                 try pemRootCertificates.map { try Certificate(derEncoded: [UInt8]($0)) }
-//             )
+            // 2. Verify signature
+            // 2.1 Determine key type (with new Swift ASN.1/ Certificates library)
+            // 2.2 Create corresponding public key object (EC2PublicKey/RSAPublicKey/OKPPublicKey)
+            // 2.3 Call verify method on public key with signature + data
+            let leafCertificatePublicKey: Certificate.PublicKey = leafCertificate.publicKey
+            guard try leafCertificatePublicKey.verifySignature(Data(sig), algorithm: leafCertificate.signatureAlgorithm, data: verificationData) else {
+                throw PackedAttestationError.invalidVerificationData
+            }
 
-//             var verifier = Verifier(rootCertificates: rootCertificates, policy: .init(policies: []))
-//             let verifierResult: VerificationResult = await verifier.validate(
-//                 leafCertificate: leafCertificate,
-//                 intermediates: intermediates
-//             )
-//             guard case .validCertificate = verifierResult else {
-//                 throw PackedAttestationError.invalidLeafCertificate
-//             }
+        } else { // self attestation is in use
+            guard credentialPublicKey.key.algorithm == alg else {
+                throw PackedAttestationError.algDoesNotMatch
+            }
 
-//             // 2. Verify signature
-//             // let leafCertificatePublicKey: Certificate.PublicKey = leafCertificate.publicKey
+            try credentialPublicKey.verify(signature: Data(sig), data: verificationData)
+        }
+    }
+}
 
-//             // 2.1 Determine key type (with new Swift ASN.1/ Certificates library)
-//             // 2.2 Create corresponding public key object (EC2PublicKey/RSAPublicKey/OKPPublicKey)
-//             // 2.3 Call verify method on public key with signature + data
-//             throw PackedAttestationError.notImplemented
-//         } else { // self attestation is in use
-//             guard credentialPublicKey.key.algorithm == alg else {
-//                 throw PackedAttestationError.algDoesNotMatch
-//             }
 
-//             try credentialPublicKey.verify(signature: Data(sig), data: verificationData)
-//         }
-//     }
-// }
+extension Certificate.PublicKey {
+    func verifySignature(_ signature: Data, algorithm: Certificate.SignatureAlgorithm, data: Data) throws -> Bool {
+        switch algorithm {
 
-// extension Certificate.PublicKey {
-// //    func verifySignature(_ signature: Data, algorithm: COSEAlgorithmIdentifier, data: Data) throws -> Bool {
-// //        switch algorithm {
-// //
-// //        case .algES256:
-// //            guard case let .p256(key) = backing else { return false }
-// //            let signature = try P256.Signing.ECDSASignature(derRepresentation: signature)
-// //            return key.isValidSignature(signature, for: data)
-// //        case .algES384:
-// //            guard case let .p384(key) = backing else { return false }
-// //            let signature = try P384.Signing.ECDSASignature(derRepresentation: signature)
-// //            return key.isValidSignature(signature, for: data)
-// //        case .algES512:
-// //            guard case let .p521(key) = backing else { return false }
-// //            let signature = try P521.Signing.ECDSASignature(derRepresentation: signature)
-// //            return key.isValidSignature(signature, for: data)
-// //        case .algPS256:
-// //        case .algPS384:
-// //        case .algPS512:
-// //        case .algRS1:
-// //        case .algRS256:
-// //        case .algRS384:
-// //        case .algRS512:
-// //}
-// //        switch backing {
-// //        case let .p256(key):
-// //            try EC2PublicKey(rawRepresentation: key.rawRepresentation, algorithm: algorithm)
-// //                .verify(signature: signature, data: data)
-// //        case let .p384(key):
-// //            try EC2PublicKey(rawRepresentation: key.rawRepresentation, algorithm: algorithm)
-// //                .verify(signature: signature, data: data)
-// //        case let .p521(key):
-// //            try EC2PublicKey(rawRepresentation: key.rawRepresentation, algorithm: algorithm)
-// //                .verify(signature: signature, data: data)
-// //        case let .rsa(key):
-// //            try RSAPublicKeyData(rawRepresentation: key.derRepresentation, algorithm: algorithm)
-// //                .verify(signature: signature, data: data)
-// //        }
-// //    }
-// }
+        case .ecdsaWithSHA256:
+            guard let key = P256.Signing.PublicKey(self) else {
+                return false
+            }
+            let signature = try P256.Signing.ECDSASignature(derRepresentation: signature)
+            return key.isValidSignature(signature, for: data)
+        case .ecdsaWithSHA384:
+            guard let key = P384.Signing.PublicKey(self) else {
+                return false
+            }
+            let signature = try P384.Signing.ECDSASignature(derRepresentation: signature)
+            return key.isValidSignature(signature, for: data)
+        case .ecdsaWithSHA512:
+            guard let key = P521.Signing.PublicKey(self) else {
+                return false
+            }
+            let signature = try P521.Signing.ECDSASignature(derRepresentation: signature)
+            return key.isValidSignature(signature, for: data)
+        case .sha1WithRSAEncryption, .sha256WithRSAEncryption, .sha384WithRSAEncryption, .sha512WithRSAEncryption:
+            guard let key = _RSA.Signing.PublicKey(self) else {
+                return false
+            }
+            let signature = _RSA.Signing.RSASignature(rawRepresentation: signature)
+            return key.isValidSignature(signature, for: data)
+        default: // Should we return more explicit info (signature alg not supported) in that case?
+            return false
+        }
+    }
+}
diff --git a/Sources/WebAuthn/WebAuthnManager.swift b/Sources/WebAuthn/WebAuthnManager.swift
index b2597982..695ffba3 100644
--- a/Sources/WebAuthn/WebAuthnManager.swift
+++ b/Sources/WebAuthn/WebAuthnManager.swift
@@ -119,6 +119,7 @@ public struct WebAuthnManager {
             signCount: parsedData.response.attestationObject.authenticatorData.counter,
             backupEligible: parsedData.response.attestationObject.authenticatorData.flags.isBackupEligible,
             isBackedUp: parsedData.response.attestationObject.authenticatorData.flags.isCurrentlyBackedUp,
+            aaguid: parsedData.response.attestationObject.authenticatorData.attestedData?.aaguid,
             attestationObject: parsedData.response.attestationObject,
             attestationClientDataJSON: parsedData.response.clientData
         )

From d40d26f0a42e93f47cba7dbe5ef953dd6cd05611 Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Sat, 27 Apr 2024 13:44:31 +1000
Subject: [PATCH 02/30] WIP

---
 .../Registration/AttestationObject.swift      |  10 ++
 .../Formats/FidoU2FAttestation.swift          | 107 ++++++++++++++++++
 .../Formats/PackedAttestation.swift           |   3 +
 3 files changed, 120 insertions(+)
 create mode 100644 Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift

diff --git a/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift b/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
index be5242dc..1025af10 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
@@ -30,6 +30,7 @@ public struct AttestationObject {
         supportedPublicKeyAlgorithms: [PublicKeyCredentialParameters],
         pemRootCertificatesByFormat: [AttestationFormat: [Data]] = [:]
     ) async throws -> AttestedCredentialData {
+        print("\n•••••••• \(Self.self).verify(): format=\(format) ***\n")
         let relyingPartyIDHash = SHA256.hash(data: relyingPartyID.data(using: .utf8)!)
 
         guard relyingPartyIDHash == authenticatorData.relyingPartyIDHash else {
@@ -80,6 +81,15 @@ public struct AttestationObject {
         //         credentialPublicKey: credentialPublicKey,
         //         pemRootCertificates: pemRootCertificates
         //     )
+            
+        case .fidoU2F:
+            try await FidoU2FAttestation.verify(
+                attStmt: attestationStatement,
+                authenticatorData: Data(rawAuthenticatorData),
+                clientDataHash: Data(clientDataHash),
+                credentialPublicKey: credentialPublicKey,
+                pemRootCertificates: pemRootCertificates
+            )
         default:
             throw WebAuthnError.attestationVerificationNotSupported
         }
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
new file mode 100644
index 00000000..ba8b2ea8
--- /dev/null
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
@@ -0,0 +1,107 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the WebAuthn Swift open source project
+//
+// Copyright (c) 2023 the WebAuthn Swift project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of WebAuthn Swift project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+import Foundation
+import SwiftCBOR
+import SwiftASN1
+import X509
+import Crypto
+
+struct FidoU2FAttestation {
+    enum FidoU2FAttestationError: Error {
+        case invalidAlg
+        case invalidSig
+        case invalidX5C
+        case invalidLeafCertificate
+        case invalidLeafCertificatePublicKey
+        case missingAttestationCertificate
+        case algDoesNotMatch
+        case missingAttestedCredential
+        // Authenticator data cannot be verified
+        case invalidVerificationData
+        case notImplemented
+    }
+
+    static func verify(
+        attStmt: CBOR,
+        authenticatorData: Data,
+        clientDataHash: Data,
+        credentialPublicKey: CredentialPublicKey,
+        pemRootCertificates: [Data]
+    ) async throws {
+        print("\n••••••• attStmt[alg]=\(attStmt["alg"])")
+        guard let algCBOR = attStmt["alg"] else {
+            throw FidoU2FAttestationError.invalidAlg
+            //case let .negativeInt(algorithmNegative) = algCBOR,
+            //let alg = COSEAlgorithmIdentifier(rawValue: -1 - Int(algorithmNegative)) else {
+            
+        }
+        guard let sigCBOR = attStmt["sig"], case let .byteString(sig) = sigCBOR else {
+            throw FidoU2FAttestationError.invalidSig
+        }
+        
+        let verificationData = authenticatorData + clientDataHash
+
+        if let x5cCBOR = attStmt["x5c"] {
+            print("\n ••••••• Full attestation!!!!!! ••••••• \n")
+            guard case let .array(x5cCBOR) = x5cCBOR else {
+                throw FidoU2FAttestationError.invalidX5C
+            }
+
+            let x5c: [Certificate] = try x5cCBOR.map {
+                guard case let .byteString(certificate) = $0 else {
+                    throw FidoU2FAttestationError.invalidX5C
+                }
+                return try Certificate(derEncoded: certificate)
+            }
+            guard let leafCertificate = x5c.first else { throw FidoU2FAttestationError.invalidX5C }
+            let intermediates = CertificateStore(x5c[1...])
+            let rootCertificates = CertificateStore(
+                try pemRootCertificates.map { try Certificate(derEncoded: [UInt8]($0)) }
+            )
+
+            var verifier = Verifier(rootCertificates: rootCertificates) {
+                // TODO: do we really want to validate a cert expiry for devices that cannot be updated?
+                // An expired device cert just means that the device is "old".
+                RFC5280Policy(validationTime: Date())
+            }
+            let verifierResult: VerificationResult = await verifier.validate(
+                leafCertificate: leafCertificate,
+                intermediates: intermediates
+            )
+
+            guard case .validCertificate = verifierResult else {
+                throw FidoU2FAttestationError.invalidLeafCertificate
+            }
+
+            // 2. Verify signature
+            // 2.1 Determine key type (with new Swift ASN.1/ Certificates library)
+            // 2.2 Create corresponding public key object (EC2PublicKey/RSAPublicKey/OKPPublicKey)
+            // 2.3 Call verify method on public key with signature + data
+            let leafCertificatePublicKey: Certificate.PublicKey = leafCertificate.publicKey
+            guard try leafCertificatePublicKey.verifySignature(Data(sig), algorithm: leafCertificate.signatureAlgorithm, data: verificationData) else {
+                throw FidoU2FAttestationError.invalidVerificationData
+            }
+
+        } else { // self attestation is in use
+            print("\n ••••••• Self attestation!!!!!! ••••••• \n")
+            /*guard credentialPublicKey.key.algorithm == alg else {
+                throw PackedAttestationError.algDoesNotMatch
+            }*/
+
+            try credentialPublicKey.verify(signature: Data(sig), data: verificationData)
+        }
+    }
+}
+
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
index 28e2c0c9..3dc06f61 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
@@ -53,6 +53,7 @@ struct PackedAttestation {
         let verificationData = authenticatorData + clientDataHash
 
         if let x5cCBOR = attStmt["x5c"] {
+            print("\n ••••••• Full attestation!!!!!! ••••••• \n")
             guard case let .array(x5cCBOR) = x5cCBOR else {
                 throw PackedAttestationError.invalidX5C
             }
@@ -78,6 +79,7 @@ struct PackedAttestation {
                 leafCertificate: leafCertificate,
                 intermediates: intermediates
             )
+
             guard case .validCertificate = verifierResult else {
                 throw PackedAttestationError.invalidLeafCertificate
             }
@@ -92,6 +94,7 @@ struct PackedAttestation {
             }
 
         } else { // self attestation is in use
+            print("\n ••••••• Self attestation!!!!!! ••••••• \n")
             guard credentialPublicKey.key.algorithm == alg else {
                 throw PackedAttestationError.algDoesNotMatch
             }

From f399a3f6b29bad869e0e62f5e0fa9afe30b698d7 Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Sat, 27 Apr 2024 18:11:20 +1000
Subject: [PATCH 03/30] FidoU2FAttestation

---
 Package.swift                                 |  2 +
 .../Registration/AttestationObject.swift      |  2 +-
 .../Formats/FidoU2FAttestation.swift          | 99 +++++++++----------
 3 files changed, 51 insertions(+), 52 deletions(-)

diff --git a/Package.swift b/Package.swift
index 53962bc7..0590044f 100644
--- a/Package.swift
+++ b/Package.swift
@@ -26,6 +26,7 @@ let package = Package(
     dependencies: [
         .package(url: "https://github.com/unrelentingtech/SwiftCBOR.git", from: "0.4.7"),
         .package(url: "https://github.com/apple/swift-crypto.git", "2.0.0" ..< "4.0.0"),
+        .package(url: "https://github.com/apple/swift-certificates.git", from: "1.0.0"),
         .package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
         .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.1.0")
     ],
@@ -36,6 +37,7 @@ let package = Package(
                 "SwiftCBOR",
                 .product(name: "Crypto", package: "swift-crypto"),
                 .product(name: "_CryptoExtras", package: "swift-crypto"),
+                .product(name: "X509", package: "swift-certificates"),
                 .product(name: "Logging", package: "swift-log"),
             ]
         ),
diff --git a/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift b/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
index 1025af10..10ff6efa 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
@@ -85,7 +85,7 @@ public struct AttestationObject {
         case .fidoU2F:
             try await FidoU2FAttestation.verify(
                 attStmt: attestationStatement,
-                authenticatorData: Data(rawAuthenticatorData),
+                authenticatorData: authenticatorData,
                 clientDataHash: Data(clientDataHash),
                 credentialPublicKey: credentialPublicKey,
                 pemRootCertificates: pemRootCertificates
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
index ba8b2ea8..0a557ff5 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
@@ -27,6 +27,7 @@ struct FidoU2FAttestation {
         case invalidLeafCertificatePublicKey
         case missingAttestationCertificate
         case algDoesNotMatch
+        case invalidAttestationKeyType
         case missingAttestedCredential
         // Authenticator data cannot be verified
         case invalidVerificationData
@@ -35,73 +36,69 @@ struct FidoU2FAttestation {
 
     static func verify(
         attStmt: CBOR,
-        authenticatorData: Data,
+        authenticatorData: AuthenticatorData,
         clientDataHash: Data,
         credentialPublicKey: CredentialPublicKey,
         pemRootCertificates: [Data]
     ) async throws {
-        print("\n••••••• attStmt[alg]=\(attStmt["alg"])")
-        guard let algCBOR = attStmt["alg"] else {
-            throw FidoU2FAttestationError.invalidAlg
-            //case let .negativeInt(algorithmNegative) = algCBOR,
-            //let alg = COSEAlgorithmIdentifier(rawValue: -1 - Int(algorithmNegative)) else {
-            
-        }
         guard let sigCBOR = attStmt["sig"], case let .byteString(sig) = sigCBOR else {
             throw FidoU2FAttestationError.invalidSig
         }
+
+        let credentialId = authenticatorData.attestedData!.credentialID
+        let publicKeyBytes = authenticatorData.attestedData!.publicKey
         
-        let verificationData = authenticatorData + clientDataHash
+        guard case let .ec2(key) = credentialPublicKey else {
+            throw FidoU2FAttestationError.invalidAttestationKeyType
+        }
+        
+        // With U2F, the public key format used when calculating the signature (`sig`) was encoded in ANSI X9.62 format
+        let ansiPublicKey = [0x04] + key.xCoordinate + key.yCoordinate
+        // https://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915/fido-u2f-raw-message-formats-v1.1-id-20160915.html#registration-response-message-success
+        let verificationData = Data([0x00] + authenticatorData.relyingPartyIDHash + Array(clientDataHash) + credentialId + ansiPublicKey)
 
-        if let x5cCBOR = attStmt["x5c"] {
-            print("\n ••••••• Full attestation!!!!!! ••••••• \n")
-            guard case let .array(x5cCBOR) = x5cCBOR else {
+        guard let x5cCBOR = attStmt["x5c"], case let .array(x5cCBOR) = x5cCBOR else {
                 throw FidoU2FAttestationError.invalidX5C
-            }
-
-            let x5c: [Certificate] = try x5cCBOR.map {
-                guard case let .byteString(certificate) = $0 else {
-                    throw FidoU2FAttestationError.invalidX5C
-                }
-                return try Certificate(derEncoded: certificate)
-            }
-            guard let leafCertificate = x5c.first else { throw FidoU2FAttestationError.invalidX5C }
-            let intermediates = CertificateStore(x5c[1...])
-            let rootCertificates = CertificateStore(
-                try pemRootCertificates.map { try Certificate(derEncoded: [UInt8]($0)) }
-            )
-
-            var verifier = Verifier(rootCertificates: rootCertificates) {
-                // TODO: do we really want to validate a cert expiry for devices that cannot be updated?
-                // An expired device cert just means that the device is "old".
-                RFC5280Policy(validationTime: Date())
-            }
-            let verifierResult: VerificationResult = await verifier.validate(
-                leafCertificate: leafCertificate,
-                intermediates: intermediates
-            )
+        }
 
-            guard case .validCertificate = verifierResult else {
-                throw FidoU2FAttestationError.invalidLeafCertificate
+        let x5c: [Certificate] = try x5cCBOR.map {
+            guard case let .byteString(certificate) = $0 else {
+                throw FidoU2FAttestationError.invalidX5C
             }
+            return try Certificate(derEncoded: certificate)
+        }
 
-            // 2. Verify signature
-            // 2.1 Determine key type (with new Swift ASN.1/ Certificates library)
-            // 2.2 Create corresponding public key object (EC2PublicKey/RSAPublicKey/OKPPublicKey)
-            // 2.3 Call verify method on public key with signature + data
-            let leafCertificatePublicKey: Certificate.PublicKey = leafCertificate.publicKey
-            guard try leafCertificatePublicKey.verifySignature(Data(sig), algorithm: leafCertificate.signatureAlgorithm, data: verificationData) else {
-                throw FidoU2FAttestationError.invalidVerificationData
-            }
+        guard let leafCertificate = x5c.first else { throw FidoU2FAttestationError.invalidX5C }
+        let intermediates = CertificateStore(x5c[1...])
+        let rootCertificates = CertificateStore(
+            try pemRootCertificates.map { try Certificate(derEncoded: [UInt8]($0)) }
+        )
+        
+        /*var verifier = Verifier(rootCertificates: rootCertificates) {
+            // TODO: do we really want to validate a cert expiry for devices that cannot be updated?
+            // An expired device cert just means that the device is "old".
+            RFC5280Policy(validationTime: Date())
+        }
+        let verifierResult: VerificationResult = await verifier.validate(
+            leafCertificate: leafCertificate,
+            intermediates: intermediates
+        )
 
-        } else { // self attestation is in use
-            print("\n ••••••• Self attestation!!!!!! ••••••• \n")
-            /*guard credentialPublicKey.key.algorithm == alg else {
-                throw PackedAttestationError.algDoesNotMatch
-            }*/
+        guard case .validCertificate = verifierResult else {
+            throw FidoU2FAttestationError.invalidLeafCertificate
+        }*/
 
-            try credentialPublicKey.verify(signature: Data(sig), data: verificationData)
+        // 2. Verify signature
+        // 2.1 Determine key type (with new Swift ASN.1/ Certificates library)
+        // 2.2 Create corresponding public key object (EC2PublicKey/RSAPublicKey/OKPPublicKey)
+        // 2.3 Call verify method on public key with signature + data
+        let leafCertificatePublicKey: Certificate.PublicKey = leafCertificate.publicKey
+        guard try leafCertificatePublicKey.verifySignature(Data(sig), algorithm: .ecdsaWithSHA256, data: verificationData) else {
+            throw FidoU2FAttestationError.invalidVerificationData
         }
+        print("\n••••• Verified FidoU2FAttestation !!!! ••••")
+
+        
     }
 }
 

From bce79f02cd0c3e249abf350d772647a767c34255 Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Sat, 27 Apr 2024 18:59:04 +1000
Subject: [PATCH 04/30] Cleanuo

---
 .../Registration/AttestationObject.swift       |  1 +
 .../Formats/FidoU2FAttestation.swift           | 18 ++++++------------
 .../Formats/PackedAttestation.swift            |  6 ------
 3 files changed, 7 insertions(+), 18 deletions(-)

diff --git a/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift b/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
index 10ff6efa..016b06ce 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
@@ -82,6 +82,7 @@ public struct AttestationObject {
         //         pemRootCertificates: pemRootCertificates
         //     )
             
+        // Legacy format used mostly by older authenticators
         case .fidoU2F:
             try await FidoU2FAttestation.verify(
                 attStmt: attestationStatement,
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
index 0a557ff5..47830263 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
@@ -14,24 +14,18 @@
 
 import Foundation
 import SwiftCBOR
-import SwiftASN1
 import X509
 import Crypto
 
 struct FidoU2FAttestation {
     enum FidoU2FAttestationError: Error {
-        case invalidAlg
         case invalidSig
         case invalidX5C
         case invalidLeafCertificate
-        case invalidLeafCertificatePublicKey
-        case missingAttestationCertificate
-        case algDoesNotMatch
         case invalidAttestationKeyType
         case missingAttestedCredential
         // Authenticator data cannot be verified
         case invalidVerificationData
-        case notImplemented
     }
 
     static func verify(
@@ -45,8 +39,9 @@ struct FidoU2FAttestation {
             throw FidoU2FAttestationError.invalidSig
         }
 
-        let credentialId = authenticatorData.attestedData!.credentialID
-        let publicKeyBytes = authenticatorData.attestedData!.publicKey
+        guard let attestedData = authenticatorData.attestedData else {
+            throw FidoU2FAttestationError.missingAttestedCredential
+        }
         
         guard case let .ec2(key) = credentialPublicKey else {
             throw FidoU2FAttestationError.invalidAttestationKeyType
@@ -55,7 +50,7 @@ struct FidoU2FAttestation {
         // With U2F, the public key format used when calculating the signature (`sig`) was encoded in ANSI X9.62 format
         let ansiPublicKey = [0x04] + key.xCoordinate + key.yCoordinate
         // https://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915/fido-u2f-raw-message-formats-v1.1-id-20160915.html#registration-response-message-success
-        let verificationData = Data([0x00] + authenticatorData.relyingPartyIDHash + Array(clientDataHash) + credentialId + ansiPublicKey)
+        let verificationData = Data([0x00] + authenticatorData.relyingPartyIDHash + Array(clientDataHash) + attestedData.credentialID + ansiPublicKey)
 
         guard let x5cCBOR = attStmt["x5c"], case let .array(x5cCBOR) = x5cCBOR else {
                 throw FidoU2FAttestationError.invalidX5C
@@ -74,7 +69,7 @@ struct FidoU2FAttestation {
             try pemRootCertificates.map { try Certificate(derEncoded: [UInt8]($0)) }
         )
         
-        /*var verifier = Verifier(rootCertificates: rootCertificates) {
+        var verifier = Verifier(rootCertificates: rootCertificates) {
             // TODO: do we really want to validate a cert expiry for devices that cannot be updated?
             // An expired device cert just means that the device is "old".
             RFC5280Policy(validationTime: Date())
@@ -83,10 +78,9 @@ struct FidoU2FAttestation {
             leafCertificate: leafCertificate,
             intermediates: intermediates
         )
-
         guard case .validCertificate = verifierResult else {
             throw FidoU2FAttestationError.invalidLeafCertificate
-        }*/
+        }
 
         // 2. Verify signature
         // 2.1 Determine key type (with new Swift ASN.1/ Certificates library)
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
index 3dc06f61..597b5431 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
@@ -14,7 +14,6 @@
 
 import Foundation
 import SwiftCBOR
-import SwiftASN1
 import X509
 import Crypto
 import _CryptoExtras
@@ -25,13 +24,10 @@ struct PackedAttestation {
         case invalidSig
         case invalidX5C
         case invalidLeafCertificate
-        case invalidLeafCertificatePublicKey
-        case missingAttestationCertificate
         case algDoesNotMatch
         case missingAttestedCredential
         // Authenticator data cannot be verified
         case invalidVerificationData
-        case notImplemented
     }
 
     static func verify(
@@ -53,7 +49,6 @@ struct PackedAttestation {
         let verificationData = authenticatorData + clientDataHash
 
         if let x5cCBOR = attStmt["x5c"] {
-            print("\n ••••••• Full attestation!!!!!! ••••••• \n")
             guard case let .array(x5cCBOR) = x5cCBOR else {
                 throw PackedAttestationError.invalidX5C
             }
@@ -79,7 +74,6 @@ struct PackedAttestation {
                 leafCertificate: leafCertificate,
                 intermediates: intermediates
             )
-
             guard case .validCertificate = verifierResult else {
                 throw PackedAttestationError.invalidLeafCertificate
             }

From a316f131c3a4ea47a01d58e0c1680dd85a77cd8f Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Sat, 27 Apr 2024 19:04:18 +1000
Subject: [PATCH 05/30] Cleanuo

---
 .../Formats/FidoU2FAttestation.swift          | 22 +++++++++++--------
 1 file changed, 13 insertions(+), 9 deletions(-)

diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
index 47830263..5d1df865 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
@@ -46,11 +46,6 @@ struct FidoU2FAttestation {
         guard case let .ec2(key) = credentialPublicKey else {
             throw FidoU2FAttestationError.invalidAttestationKeyType
         }
-        
-        // With U2F, the public key format used when calculating the signature (`sig`) was encoded in ANSI X9.62 format
-        let ansiPublicKey = [0x04] + key.xCoordinate + key.yCoordinate
-        // https://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915/fido-u2f-raw-message-formats-v1.1-id-20160915.html#registration-response-message-success
-        let verificationData = Data([0x00] + authenticatorData.relyingPartyIDHash + Array(clientDataHash) + attestedData.credentialID + ansiPublicKey)
 
         guard let x5cCBOR = attStmt["x5c"], case let .array(x5cCBOR) = x5cCBOR else {
                 throw FidoU2FAttestationError.invalidX5C
@@ -82,10 +77,19 @@ struct FidoU2FAttestation {
             throw FidoU2FAttestationError.invalidLeafCertificate
         }
 
-        // 2. Verify signature
-        // 2.1 Determine key type (with new Swift ASN.1/ Certificates library)
-        // 2.2 Create corresponding public key object (EC2PublicKey/RSAPublicKey/OKPPublicKey)
-        // 2.3 Call verify method on public key with signature + data
+        // With U2F, the public key format used when calculating the signature (`sig`) was encoded in ANSI X9.62 format
+        let ansiPublicKey = [0x04] + key.xCoordinate + key.yCoordinate
+        
+        // https://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915/fido-u2f-raw-message-formats-v1.1-id-20160915.html#registration-response-message-success
+        let verificationData = Data(
+            [0x00]
+            + authenticatorData.relyingPartyIDHash
+            + Array(clientDataHash)
+            + attestedData.credentialID
+            + ansiPublicKey
+        )
+        
+        // Verify signature
         let leafCertificatePublicKey: Certificate.PublicKey = leafCertificate.publicKey
         guard try leafCertificatePublicKey.verifySignature(Data(sig), algorithm: .ecdsaWithSHA256, data: verificationData) else {
             throw FidoU2FAttestationError.invalidVerificationData

From c0b9c7d1f4457f9fc2fd7f986e928f0c24b5c8e6 Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Sun, 28 Apr 2024 07:17:47 +1000
Subject: [PATCH 06/30] Move verifySignature to dedicated extension file

---
 .../Formats/FidoU2FAttestation.swift          |  8 +--
 .../Formats/PackedAttestation.swift           | 39 +-------------
 .../Formats/PublicKey+verifySignature.swift   | 52 +++++++++++++++++++
 3 files changed, 55 insertions(+), 44 deletions(-)
 create mode 100644 Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift

diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
index 5d1df865..af5aebaf 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
@@ -15,7 +15,6 @@
 import Foundation
 import SwiftCBOR
 import X509
-import Crypto
 
 struct FidoU2FAttestation {
     enum FidoU2FAttestationError: Error {
@@ -63,7 +62,7 @@ struct FidoU2FAttestation {
         let rootCertificates = CertificateStore(
             try pemRootCertificates.map { try Certificate(derEncoded: [UInt8]($0)) }
         )
-        
+
         var verifier = Verifier(rootCertificates: rootCertificates) {
             // TODO: do we really want to validate a cert expiry for devices that cannot be updated?
             // An expired device cert just means that the device is "old".
@@ -82,7 +81,7 @@ struct FidoU2FAttestation {
         
         // https://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915/fido-u2f-raw-message-formats-v1.1-id-20160915.html#registration-response-message-success
         let verificationData = Data(
-            [0x00]
+            [0x00] // A byte "reserved for future use" with the value 0x00.
             + authenticatorData.relyingPartyIDHash
             + Array(clientDataHash)
             + attestedData.credentialID
@@ -94,9 +93,6 @@ struct FidoU2FAttestation {
         guard try leafCertificatePublicKey.verifySignature(Data(sig), algorithm: .ecdsaWithSHA256, data: verificationData) else {
             throw FidoU2FAttestationError.invalidVerificationData
         }
-        print("\n••••• Verified FidoU2FAttestation !!!! ••••")
-
-        
     }
 }
 
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
index 597b5431..0aaf4544 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
@@ -15,8 +15,6 @@
 import Foundation
 import SwiftCBOR
 import X509
-import Crypto
-import _CryptoExtras
 
 struct PackedAttestation {
     enum PackedAttestationError: Error {
@@ -77,7 +75,7 @@ struct PackedAttestation {
             guard case .validCertificate = verifierResult else {
                 throw PackedAttestationError.invalidLeafCertificate
             }
-
+            
             // 2. Verify signature
             // 2.1 Determine key type (with new Swift ASN.1/ Certificates library)
             // 2.2 Create corresponding public key object (EC2PublicKey/RSAPublicKey/OKPPublicKey)
@@ -97,38 +95,3 @@ struct PackedAttestation {
         }
     }
 }
-
-
-extension Certificate.PublicKey {
-    func verifySignature(_ signature: Data, algorithm: Certificate.SignatureAlgorithm, data: Data) throws -> Bool {
-        switch algorithm {
-
-        case .ecdsaWithSHA256:
-            guard let key = P256.Signing.PublicKey(self) else {
-                return false
-            }
-            let signature = try P256.Signing.ECDSASignature(derRepresentation: signature)
-            return key.isValidSignature(signature, for: data)
-        case .ecdsaWithSHA384:
-            guard let key = P384.Signing.PublicKey(self) else {
-                return false
-            }
-            let signature = try P384.Signing.ECDSASignature(derRepresentation: signature)
-            return key.isValidSignature(signature, for: data)
-        case .ecdsaWithSHA512:
-            guard let key = P521.Signing.PublicKey(self) else {
-                return false
-            }
-            let signature = try P521.Signing.ECDSASignature(derRepresentation: signature)
-            return key.isValidSignature(signature, for: data)
-        case .sha1WithRSAEncryption, .sha256WithRSAEncryption, .sha384WithRSAEncryption, .sha512WithRSAEncryption:
-            guard let key = _RSA.Signing.PublicKey(self) else {
-                return false
-            }
-            let signature = _RSA.Signing.RSASignature(rawRepresentation: signature)
-            return key.isValidSignature(signature, for: data)
-        default: // Should we return more explicit info (signature alg not supported) in that case?
-            return false
-        }
-    }
-}
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
new file mode 100644
index 00000000..90bfd06f
--- /dev/null
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
@@ -0,0 +1,52 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the WebAuthn Swift open source project
+//
+// Copyright (c) 2023 the WebAuthn Swift project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of WebAuthn Swift project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+import Foundation
+import X509
+import Crypto
+import _CryptoExtras
+
+extension Certificate.PublicKey {
+    func verifySignature(_ signature: Data, algorithm: Certificate.SignatureAlgorithm, data: Data) throws -> Bool {
+        switch algorithm {
+
+        case .ecdsaWithSHA256:
+            guard let key = P256.Signing.PublicKey(self) else {
+                return false
+            }
+            let signature = try P256.Signing.ECDSASignature(derRepresentation: signature)
+            return key.isValidSignature(signature, for: data)
+        case .ecdsaWithSHA384:
+            guard let key = P384.Signing.PublicKey(self) else {
+                return false
+            }
+            let signature = try P384.Signing.ECDSASignature(derRepresentation: signature)
+            return key.isValidSignature(signature, for: data)
+        case .ecdsaWithSHA512:
+            guard let key = P521.Signing.PublicKey(self) else {
+                return false
+            }
+            let signature = try P521.Signing.ECDSASignature(derRepresentation: signature)
+            return key.isValidSignature(signature, for: data)
+        case .sha1WithRSAEncryption, .sha256WithRSAEncryption, .sha384WithRSAEncryption, .sha512WithRSAEncryption:
+            guard let key = _RSA.Signing.PublicKey(self) else {
+                return false
+            }
+            let signature = _RSA.Signing.RSASignature(rawRepresentation: signature)
+            return key.isValidSignature(signature, for: data)
+        default: // Should we return more explicit info (signature alg not supported) in that case?
+            return false
+        }
+    }
+}

From 9733363c6986247d1d34406182a68d1e32a4775e Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Sun, 28 Apr 2024 13:00:24 +1000
Subject: [PATCH 07/30] AttestationResult.swift

---
 .../Registration/AttestationObject.swift      | 19 ++++++++----
 .../Registration/AttestationResult.swift      | 23 +++++++++++++++
 .../Ceremonies/Registration/Credential.swift  |  4 +--
 .../Formats/FidoU2FAttestation.swift          | 29 ++++++++++++++-----
 .../Formats/PackedAttestation.swift           |  6 ++--
 .../Formats/PublicKey+verifySignature.swift   |  1 +
 .../Registration/RegistrationCredential.swift |  6 ++--
 Sources/WebAuthn/WebAuthnManager.swift        |  7 ++---
 8 files changed, 71 insertions(+), 24 deletions(-)
 create mode 100644 Sources/WebAuthn/Ceremonies/Registration/AttestationResult.swift

diff --git a/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift b/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
index 016b06ce..2e5c1e51 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
@@ -15,6 +15,7 @@
 import Foundation
 import Crypto
 import SwiftCBOR
+import X509
 
 /// Contains the cryptographic attestation that a new key pair was created by that authenticator.
 public struct AttestationObject {
@@ -22,6 +23,7 @@ public struct AttestationObject {
     let rawAuthenticatorData: [UInt8]
     let format: AttestationFormat
     let attestationStatement: CBOR
+    var trustPath: [Certificate] = []
 
     func verify(
         relyingPartyID: String,
@@ -29,7 +31,8 @@ public struct AttestationObject {
         clientDataHash: SHA256.Digest,
         supportedPublicKeyAlgorithms: [PublicKeyCredentialParameters],
         pemRootCertificatesByFormat: [AttestationFormat: [Data]] = [:]
-    ) async throws -> AttestedCredentialData {
+    ) async throws -> AttestationResult {
+        // TODO: remove
         print("\n•••••••• \(Self.self).verify(): format=\(format) ***\n")
         let relyingPartyIDHash = SHA256.hash(data: relyingPartyID.data(using: .utf8)!)
 
@@ -58,6 +61,7 @@ public struct AttestationObject {
         }
 
         let pemRootCertificates = pemRootCertificatesByFormat[format] ?? []
+        var trustedPath: [Certificate]!
         switch format {
         case .none:
             // if format is `none` statement must be empty
@@ -65,7 +69,7 @@ public struct AttestationObject {
                 throw WebAuthnError.attestationStatementMustBeEmpty
             }
         case .packed:
-            try await PackedAttestation.verify(
+            trustedPath = try await PackedAttestation.verify(
                 attStmt: attestationStatement,
                 authenticatorData: Data(rawAuthenticatorData),
                 clientDataHash: Data(clientDataHash),
@@ -84,7 +88,7 @@ public struct AttestationObject {
             
         // Legacy format used mostly by older authenticators
         case .fidoU2F:
-            try await FidoU2FAttestation.verify(
+            trustedPath = try await FidoU2FAttestation.verify(
                 attStmt: attestationStatement,
                 authenticatorData: authenticatorData,
                 clientDataHash: Data(clientDataHash),
@@ -94,7 +98,12 @@ public struct AttestationObject {
         default:
             throw WebAuthnError.attestationVerificationNotSupported
         }
-
-        return attestedCredentialData
+        
+        return AttestationResult(
+            aaguid: [],
+            format: format,
+            trustChain: trustedPath,
+            attestedCredentialData: attestedCredentialData
+        )
     }
 }
diff --git a/Sources/WebAuthn/Ceremonies/Registration/AttestationResult.swift b/Sources/WebAuthn/Ceremonies/Registration/AttestationResult.swift
new file mode 100644
index 00000000..8c6a67a0
--- /dev/null
+++ b/Sources/WebAuthn/Ceremonies/Registration/AttestationResult.swift
@@ -0,0 +1,23 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the WebAuthn Swift open source project
+//
+// Copyright (c) 2022 the WebAuthn Swift project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of WebAuthn Swift project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+import X509
+
+public struct AttestationResult {
+    public let aaguid: [UInt8]?
+    public let format: AttestationFormat
+    public let trustChain: [Certificate]
+    
+    let attestedCredentialData: AttestedCredentialData
+}
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Credential.swift b/Sources/WebAuthn/Ceremonies/Registration/Credential.swift
index 32288d9f..125c7094 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Credential.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Credential.swift
@@ -40,10 +40,8 @@ public struct Credential {
     public let isBackedUp: Bool
 
     // MARK: Optional content
-
-    public let aaguid: [UInt8]?
     
-    public let attestationObject: AttestationObject
+    public let attestationResult: AttestationResult
 
     public let attestationClientDataJSON: CollectedClientData
 }
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
index af5aebaf..f8b20498 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
@@ -21,6 +21,8 @@ struct FidoU2FAttestation {
         case invalidSig
         case invalidX5C
         case invalidLeafCertificate
+        // attestation cert can only have a ecdsaWithSHA256 signature
+        case invalidLeafCertificateSigType
         case invalidAttestationKeyType
         case missingAttestedCredential
         // Authenticator data cannot be verified
@@ -33,7 +35,7 @@ struct FidoU2FAttestation {
         clientDataHash: Data,
         credentialPublicKey: CredentialPublicKey,
         pemRootCertificates: [Data]
-    ) async throws {
+    ) async throws -> [Certificate] {
         guard let sigCBOR = attStmt["sig"], case let .byteString(sig) = sigCBOR else {
             throw FidoU2FAttestationError.invalidSig
         }
@@ -42,7 +44,7 @@ struct FidoU2FAttestation {
             throw FidoU2FAttestationError.missingAttestedCredential
         }
         
-        guard case let .ec2(key) = credentialPublicKey else {
+        guard case let .ec2(key) = credentialPublicKey, key.algorithm == .algES256 else {
             throw FidoU2FAttestationError.invalidAttestationKeyType
         }
 
@@ -57,12 +59,20 @@ struct FidoU2FAttestation {
             return try Certificate(derEncoded: certificate)
         }
 
+        // U2F attestation can only have 1 certificate
+        guard x5c.count == 1 else {
+            throw FidoU2FAttestationError.invalidX5C
+        }
+        
         guard let leafCertificate = x5c.first else { throw FidoU2FAttestationError.invalidX5C }
-        let intermediates = CertificateStore(x5c[1...])
         let rootCertificates = CertificateStore(
             try pemRootCertificates.map { try Certificate(derEncoded: [UInt8]($0)) }
         )
 
+        guard leafCertificate.signatureAlgorithm == .ecdsaWithSHA256 else {
+            throw FidoU2FAttestationError.invalidLeafCertificateSigType
+        }
+        
         var verifier = Verifier(rootCertificates: rootCertificates) {
             // TODO: do we really want to validate a cert expiry for devices that cannot be updated?
             // An expired device cert just means that the device is "old".
@@ -70,13 +80,13 @@ struct FidoU2FAttestation {
         }
         let verifierResult: VerificationResult = await verifier.validate(
             leafCertificate: leafCertificate,
-            intermediates: intermediates
+            intermediates: .init()
         )
-        guard case .validCertificate = verifierResult else {
+        guard case .validCertificate(let chain) = verifierResult else {
             throw FidoU2FAttestationError.invalidLeafCertificate
         }
 
-        // With U2F, the public key format used when calculating the signature (`sig`) was encoded in ANSI X9.62 format
+        // With U2F, the public key used when calculating the signature (`sig`) was encoded in ANSI X9.62 format
         let ansiPublicKey = [0x04] + key.xCoordinate + key.yCoordinate
         
         // https://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915/fido-u2f-raw-message-formats-v1.1-id-20160915.html#registration-response-message-success
@@ -90,9 +100,14 @@ struct FidoU2FAttestation {
         
         // Verify signature
         let leafCertificatePublicKey: Certificate.PublicKey = leafCertificate.publicKey
-        guard try leafCertificatePublicKey.verifySignature(Data(sig), algorithm: .ecdsaWithSHA256, data: verificationData) else {
+        guard try leafCertificatePublicKey.verifySignature(
+            Data(sig),
+            algorithm: leafCertificate.signatureAlgorithm,
+            data: verificationData) else {
             throw FidoU2FAttestationError.invalidVerificationData
         }
+        
+        return chain
     }
 }
 
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
index 0aaf4544..13532e4e 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
@@ -34,7 +34,7 @@ struct PackedAttestation {
         clientDataHash: Data,
         credentialPublicKey: CredentialPublicKey,
         pemRootCertificates: [Data]
-    ) async throws {
+    ) async throws -> [Certificate] {
         guard let algCBOR = attStmt["alg"],
             case let .negativeInt(algorithmNegative) = algCBOR,
             let alg = COSEAlgorithmIdentifier(rawValue: -1 - Int(algorithmNegative)) else {
@@ -72,7 +72,7 @@ struct PackedAttestation {
                 leafCertificate: leafCertificate,
                 intermediates: intermediates
             )
-            guard case .validCertificate = verifierResult else {
+            guard case .validCertificate(let chain) = verifierResult else {
                 throw PackedAttestationError.invalidLeafCertificate
             }
             
@@ -84,6 +84,7 @@ struct PackedAttestation {
             guard try leafCertificatePublicKey.verifySignature(Data(sig), algorithm: leafCertificate.signatureAlgorithm, data: verificationData) else {
                 throw PackedAttestationError.invalidVerificationData
             }
+            return chain
 
         } else { // self attestation is in use
             print("\n ••••••• Self attestation!!!!!! ••••••• \n")
@@ -92,6 +93,7 @@ struct PackedAttestation {
             }
 
             try credentialPublicKey.verify(signature: Data(sig), data: verificationData)
+            return []
         }
     }
 }
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
index 90bfd06f..a966b331 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
@@ -39,6 +39,7 @@ extension Certificate.PublicKey {
             }
             let signature = try P521.Signing.ECDSASignature(derRepresentation: signature)
             return key.isValidSignature(signature, for: data)
+        // This hasn't been tested
         case .sha1WithRSAEncryption, .sha256WithRSAEncryption, .sha384WithRSAEncryption, .sha512WithRSAEncryption:
             guard let key = _RSA.Signing.PublicKey(self) else {
                 return false
diff --git a/Sources/WebAuthn/Ceremonies/Registration/RegistrationCredential.swift b/Sources/WebAuthn/Ceremonies/Registration/RegistrationCredential.swift
index 30bc5816..49b28770 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/RegistrationCredential.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/RegistrationCredential.swift
@@ -87,7 +87,7 @@ struct ParsedCredentialCreationResponse {
         relyingPartyOrigin: String,
         supportedPublicKeyAlgorithms: [PublicKeyCredentialParameters],
         pemRootCertificatesByFormat: [AttestationFormat: [Data]]
-    ) async throws -> AttestedCredentialData {
+    ) async throws -> AttestationResult {
         // Step 7. - 9.
         try response.clientData.verify(
             storedChallenge: storedChallenge,
@@ -101,7 +101,7 @@ struct ParsedCredentialCreationResponse {
         // CBOR decoding happened already. Skipping Step 11.
 
         // Step 12. - 17.
-        let attestedCredentialData = try await response.attestationObject.verify(
+        let attestationResult = try await response.attestationObject.verify(
             relyingPartyID: relyingPartyID,
             verificationRequired: verifyUser,
             clientDataHash: hash,
@@ -114,6 +114,6 @@ struct ParsedCredentialCreationResponse {
             throw WebAuthnError.credentialRawIDTooLong
         }
 
-        return attestedCredentialData
+        return attestationResult
     }
 }
diff --git a/Sources/WebAuthn/WebAuthnManager.swift b/Sources/WebAuthn/WebAuthnManager.swift
index 695ffba3..acb95e58 100644
--- a/Sources/WebAuthn/WebAuthnManager.swift
+++ b/Sources/WebAuthn/WebAuthnManager.swift
@@ -95,7 +95,7 @@ public struct WebAuthnManager {
         confirmCredentialIDNotRegisteredYet: (String) async throws -> Bool
     ) async throws -> Credential {
         let parsedData = try ParsedCredentialCreationResponse(from: credentialCreationData)
-        let attestedCredentialData = try await parsedData.verify(
+        let attestationResult = try await parsedData.verify(
             storedChallenge: challenge,
             verifyUser: requireUserVerification,
             relyingPartyID: configuration.relyingPartyID,
@@ -115,12 +115,11 @@ public struct WebAuthnManager {
         return Credential(
             type: parsedData.type,
             id: parsedData.id.urlDecoded.asString(),
-            publicKey: attestedCredentialData.publicKey,
+            publicKey: attestationResult.attestedCredentialData.publicKey,
             signCount: parsedData.response.attestationObject.authenticatorData.counter,
             backupEligible: parsedData.response.attestationObject.authenticatorData.flags.isBackupEligible,
             isBackedUp: parsedData.response.attestationObject.authenticatorData.flags.isCurrentlyBackedUp,
-            aaguid: parsedData.response.attestationObject.authenticatorData.attestedData?.aaguid,
-            attestationObject: parsedData.response.attestationObject,
+            attestationResult: attestationResult,
             attestationClientDataJSON: parsedData.response.clientData
         )
     }

From 7275dc321b246935225495e992705400247a4027 Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Mon, 29 Apr 2024 09:57:15 +1000
Subject: [PATCH 08/30] Update TPM attestation; validate cert extension for
 .packed and .tpm

---
 .../Registration/AttestationObject.swift      |  20 +-
 .../Registration/AttestationResult.swift      |  10 +
 .../Formats/PackedAttestation.swift           |  32 +-
 .../Formats/PublicKey+verifySignature.swift   |  10 +
 .../Formats/TPMAttestation+Structs.swift      | 793 +++++++++---------
 .../Registration/Formats/TPMAttestation.swift | 245 +++---
 6 files changed, 604 insertions(+), 506 deletions(-)

diff --git a/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift b/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
index 2e5c1e51..5be4a8bd 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
@@ -61,7 +61,7 @@ public struct AttestationObject {
         }
 
         let pemRootCertificates = pemRootCertificatesByFormat[format] ?? []
-        var trustedPath: [Certificate]!
+        var trustedPath: [Certificate] = []
         switch format {
         case .none:
             // if format is `none` statement must be empty
@@ -76,15 +76,15 @@ public struct AttestationObject {
                 credentialPublicKey: credentialPublicKey,
                 pemRootCertificates: pemRootCertificates
             )
-        // case .tpm:
-        //     try TPMAttestation.verify(
-        //         attStmt: attestationStatement,
-        //         authenticatorData: rawAuthenticatorData,
-        //         attestedCredentialData: attestedCredentialData,
-        //         clientDataHash: Data(clientDataHash),
-        //         credentialPublicKey: credentialPublicKey,
-        //         pemRootCertificates: pemRootCertificates
-        //     )
+        case .tpm:
+            trustedPath = try await TPMAttestation.verify(
+                attStmt: attestationStatement,
+                authenticatorData: Data(rawAuthenticatorData),
+                attestedCredentialData: attestedCredentialData,
+                clientDataHash: Data(clientDataHash),
+                credentialPublicKey: credentialPublicKey,
+                pemRootCertificates: pemRootCertificates
+            )
             
         // Legacy format used mostly by older authenticators
         case .fidoU2F:
diff --git a/Sources/WebAuthn/Ceremonies/Registration/AttestationResult.swift b/Sources/WebAuthn/Ceremonies/Registration/AttestationResult.swift
index 8c6a67a0..875ae7b1 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/AttestationResult.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/AttestationResult.swift
@@ -15,8 +15,18 @@
 import X509
 
 public struct AttestationResult {
+    enum AttestationType {
+        /// Attestation key pair validated by device manufacturer CA
+        case basicFull
+        /// Attestation signed by the public key generated during the registration
+        case `self`
+        case attCA
+        case anonCA
+        case none
+    }
     public let aaguid: [UInt8]?
     public let format: AttestationFormat
+    //public let type: AttestationType
     public let trustChain: [Certificate]
     
     let attestedCredentialData: AttestedCredentialData
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
index 13532e4e..9e127a29 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
@@ -15,6 +15,7 @@
 import Foundation
 import SwiftCBOR
 import X509
+import SwiftASN1
 
 struct PackedAttestation {
     enum PackedAttestationError: Error {
@@ -26,6 +27,8 @@ struct PackedAttestation {
         case missingAttestedCredential
         // Authenticator data cannot be verified
         case invalidVerificationData
+        case invalidCertAaguid
+        case aaguidMismatch
     }
 
     static func verify(
@@ -57,7 +60,7 @@ struct PackedAttestation {
                 }
                 return try Certificate(derEncoded: certificate)
             }
-            guard let leafCertificate = x5c.first else { throw PackedAttestationError.invalidX5C }
+            guard let attestnCert = x5c.first else { throw PackedAttestationError.invalidX5C }
             let intermediates = CertificateStore(x5c[1...])
             let rootCertificates = CertificateStore(
                 try pemRootCertificates.map { try Certificate(derEncoded: [UInt8]($0)) }
@@ -69,7 +72,7 @@ struct PackedAttestation {
                 RFC5280Policy(validationTime: Date())
             }
             let verifierResult: VerificationResult = await verifier.validate(
-                leafCertificate: leafCertificate,
+                leafCertificate: attestnCert,
                 intermediates: intermediates
             )
             guard case .validCertificate(let chain) = verifierResult else {
@@ -80,10 +83,31 @@ struct PackedAttestation {
             // 2.1 Determine key type (with new Swift ASN.1/ Certificates library)
             // 2.2 Create corresponding public key object (EC2PublicKey/RSAPublicKey/OKPPublicKey)
             // 2.3 Call verify method on public key with signature + data
-            let leafCertificatePublicKey: Certificate.PublicKey = leafCertificate.publicKey
-            guard try leafCertificatePublicKey.verifySignature(Data(sig), algorithm: leafCertificate.signatureAlgorithm, data: verificationData) else {
+            let leafCertificatePublicKey: Certificate.PublicKey = attestnCert.publicKey
+            guard try leafCertificatePublicKey.verifySignature(
+                Data(sig),
+                algorithm: attestnCert.signatureAlgorithm,
+                data: verificationData) else {
                 throw PackedAttestationError.invalidVerificationData
             }
+            
+            // Verify that the value of the aaguid extension, if present, matches aaguid in authenticatorData
+            if let certAAGUID = attestnCert.extensions.first(
+                where: {$0.oid == .idFidoGenCeAaguid}
+            ) {
+                // The AAGUID is wrapped in two OCTET STRINGS
+                let derValue = try DER.parse(certAAGUID.value)
+                guard case .primitive(let certAaguidValue) = derValue.content else {
+                    throw PackedAttestationError.invalidCertAaguid
+                }
+                
+                let authenticatorData = try AuthenticatorData(bytes: Array(authenticatorData))
+                guard let attestedData = authenticatorData.attestedData,
+                      attestedData.aaguid == Array(certAaguidValue) else {
+                    throw PackedAttestationError.aaguidMismatch
+                }
+            }
+            
             return chain
 
         } else { // self attestation is in use
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
index a966b331..95533e77 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
@@ -14,6 +14,7 @@
 
 import Foundation
 import X509
+import SwiftASN1
 import Crypto
 import _CryptoExtras
 
@@ -51,3 +52,12 @@ extension Certificate.PublicKey {
         }
     }
 }
+
+extension SwiftASN1.ASN1ObjectIdentifier {
+    static var idFidoGenCeAaguid: Self {
+        .init(arrayLiteral: 1, 3, 6, 1, 4, 1, 45724, 1, 1, 4)
+    }
+    static var tcgKpAIKCertificate: Self {
+        .init(arrayLiteral: 2, 23, 133, 8, 3)
+    }
+}
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation+Structs.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation+Structs.swift
index 0c75674e..8a147590 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation+Structs.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation+Structs.swift
@@ -12,399 +12,400 @@
 //
 //===----------------------------------------------------------------------===//
 
-// import Foundation
-// extension TPMAttestation {
-//     enum CertInfoError: Error {
-//         case magicInvalid
-//         case typeInvalid
-//         case dataTooShort
-//         case tpmImplementationIsWIP
-//     }
-
-//     struct AttestationInformation {
-//         let name: Data
-//         let qualifiedName: Data
-//     }
-
-//     struct CertInfo {
-//         let magic: Data
-//         let type: Data
-//         let qualifiedSigner: Data
-//         let extraData: Data
-//         let clockInfo: Data
-//         let firmwareVersion: Data
-//         let attested: AttestationInformation
-
-//         init?(fromBytes data: Data) {
-//             var pointer = 0
-
-//             guard let magic = data[safe: pointer..<(pointer + 4)] else { return nil }
-//             self.magic = magic
-//             pointer += 4
-
-//             guard let type = data[safe: pointer..<(pointer + 2)] else { return nil }
-//             self.type = type
-//             pointer += 2
-
-//             guard let qualifiedSignerLengthData = data[safe: pointer..<(pointer + 2)] else { return nil }
-//             pointer += 2
-//             let qualifiedSignerLength: Int = qualifiedSignerLengthData.toInteger(endian: .big)
-//             guard let qualifiedSigner = data[safe: pointer..<(pointer + qualifiedSignerLength)] else { return nil }
-//             self.qualifiedSigner = qualifiedSigner
-//             pointer += qualifiedSignerLength
-
-//             guard let extraDataLengthData = data[safe: pointer..<(pointer + 2)] else { return nil }
-//             pointer += 2
-//             let extraDataLength: Int = extraDataLengthData.toInteger(endian: .big)
-//             guard let extraData = data[safe: pointer..<(pointer + extraDataLength)] else { return nil }
-//             self.extraData = extraData
-//             pointer += extraDataLength
-
-//             guard let clockInfo = data[safe: pointer..<(pointer + 17)] else { return nil }
-//             self.clockInfo = clockInfo
-//             pointer += 17
-
-//             guard let firmwareVersion = data[safe: pointer..<(pointer + 8)] else { return nil }
-//             self.firmwareVersion = firmwareVersion
-//             pointer += 8
-
-//             guard let attestedNameLengthData = data[safe: pointer..<(pointer + 2)] else { return nil }
-//             pointer += 2
-//             let attestedNameLength: Int = attestedNameLengthData.toInteger(endian: .big)
-//             guard let attestedName = data[safe: pointer..<(pointer + attestedNameLength)] else { return nil }
-//             pointer += attestedNameLength
-
-//             guard let qualifiedNameLengthData = data[safe: pointer..<(pointer + 2)] else { return nil }
-//             pointer += 2
-//             let qualifiedNameLength: Int = qualifiedNameLengthData.toInteger(endian: .big)
-//             guard let qualifiedName = data[safe: pointer..<(pointer + qualifiedNameLength)] else { return nil }
-//             pointer += qualifiedNameLength
-
-//             attested = AttestationInformation(name: attestedName, qualifiedName: qualifiedName)
-//         }
-
-//         func verify() throws {
-//             let tpmGeneratedValue = 0xFF544347
-//             guard magic.toInteger(endian: .big) == tpmGeneratedValue else {
-//                 throw CertInfoError.magicInvalid
-//             }
-
-//             let tpmStAttestCertify = 0x8017
-//             guard type.toInteger(endian: .big) == tpmStAttestCertify else {
-//                 throw CertInfoError.typeInvalid
-//             }
-
-//             throw CertInfoError.tpmImplementationIsWIP
-//         }
-//     }
-
-//     enum PubAreaParameters {
-//         case rsa(PubAreaParametersRSA)
-//         case ecc (PubAreaParametersECC)
-//     }
-
-//     struct PubArea {
-//         let type: Data
-//         let nameAlg: Data
-//         let objectAttributes: Data
-//         let authPolicy: Data
-//         let parameters: PubAreaParameters
-//         let unique: PubAreaUnique
-
-//         let mappedType: TPMAlg
-
-//         init?(from data: Data) {
-//             var pointer = 0
-
-//             guard let type = data.safeSlice(length: 2, using: &pointer),
-//                 let mappedType = TPMAlg(from: type),
-//                 let nameAlg = data.safeSlice(length: 2, using: &pointer),
-//                 let objectAttributes = data.safeSlice(length: 4, using: &pointer),
-//                 let authPolicyLength: Int = data.safeSlice(length: 2, using: &pointer)?.toInteger(endian: .big),
-//                 let authPolicy = data.safeSlice(length: authPolicyLength, using: &pointer) else {
-//                 return nil
-//             }
-
-//             self.type = type
-//             self.nameAlg = nameAlg
-//             self.objectAttributes = objectAttributes
-//             self.authPolicy = authPolicy
-
-//             self.mappedType = mappedType
-
-//             switch mappedType {
-//             case .rsa:
-//                 guard let rsa = data.safeSlice(length: 10, using: &pointer),
-//                     let parameters = PubAreaParametersRSA(from: rsa) else { return nil }
-//                 self.parameters = .rsa(parameters)
-//             case .ecc:
-//                 guard let ecc = data.safeSlice(length: 8, using: &pointer),
-//                     let parameters = PubAreaParametersECC(from: ecc) else { return nil }
-//                 self.parameters = .ecc(parameters)
-//             default:
-//                 return nil
-//             }
-
-//             guard data.count >= pointer,
-//                 let unique = PubAreaUnique(from: data[pointer...], algorithm: mappedType) else {
-//                 return nil
-//             }
-
-//             self.unique = unique
-//         }
-//     }
-// }
-
-// extension TPMAttestation {
-//     enum TPMAlg: String {
-//         case error = "TPM_ALG_ERROR"
-//         case rsa = "TPM_ALG_RSA"
-//         case sha1 = "TPM_ALG_SHA1"
-//         case hmac = "TPM_ALG_HMAC"
-//         case aes = "TPM_ALG_AES"
-//         case mgf1 = "TPM_ALG_MGF1"
-//         case keyedhash = "TPM_ALG_KEYEDHASH"
-//         case xor = "TPM_ALG_XOR"
-//         case sha256 = "TPM_ALG_SHA256"
-//         case sha384 = "TPM_ALG_SHA384"
-//         case sha512 = "TPM_ALG_SHA512"
-//         case null = "TPM_ALG_NULL"
-//         case sm3256 = "TPM_ALG_SM3_256"
-//         case sm4 = "TPM_ALG_SM4"
-//         case rsassa = "TPM_ALG_RSASSA"
-//         case rsaes = "TPM_ALG_RSAES"
-//         case rsapss = "TPM_ALG_RSAPSS"
-//         case oaep = "TPM_ALG_OAEP"
-//         case ecdsa = "TPM_ALG_ECDSA"
-//         case ecdh = "TPM_ALG_ECDH"
-//         case ecdaa = "TPM_ALG_ECDAA"
-//         case sm2 = "TPM_ALG_SM2"
-//         case ecschnorr = "TPM_ALG_ECSCHNORR"
-//         case ecmqv = "TPM_ALG_ECMQV"
-//         case kdf1Sp80056a = "TPM_ALG_KDF1_SP800_56A"
-//         case kdf2 = "TPM_ALG_KDF2"
-//         case kdf1Sp800108 = "TPM_ALG_KDF1_SP800_108"
-//         case ecc = "TPM_ALG_ECC"
-//         case symcipher = "TPM_ALG_SYMCIPHER"
-//         case camellia = "TPM_ALG_CAMELLIA"
-//         case ctr = "TPM_ALG_CTR"
-//         case ofb = "TPM_ALG_OFB"
-//         case cbc = "TPM_ALG_CBC"
-//         case cfb = "TPM_ALG_CFB"
-//         case ecb = "TPM_ALG_ECB"
-
-//         // swiftlint:disable:next cyclomatic_complexity function_body_length
-//         init?(from data: Data) {
-//             let bytes = [UInt8](data)
-//             switch bytes {
-//             case [0x00, 0x00]:
-//                 self = .error
-//             case [0x00, 0x01]:
-//                 self = .rsa
-//             case [0x00, 0x04]:
-//                 self = .sha1
-//             case [0x00, 0x05]:
-//                 self = .hmac
-//             case [0x00, 0x06]:
-//                 self = .aes
-//             case [0x00, 0x07]:
-//                 self = .mgf1
-//             case [0x00, 0x08]:
-//                 self = .keyedhash
-//             case [0x00, 0x0a]:
-//                 self = .xor
-//             case [0x00, 0x0b]:
-//                 self = .sha256
-//             case [0x00, 0x0c]:
-//                 self = .sha384
-//             case [0x00, 0x0d]:
-//                 self = .sha512
-//             case [0x00, 0x10]:
-//                 self = .null
-//             case [0x00, 0x12]:
-//                 self = .sm3256
-//             case [0x00, 0x13]:
-//                 self = .sm4
-//             case [0x00, 0x14]:
-//                 self = .rsassa
-//             case [0x00, 0x15]:
-//                 self = .rsaes
-//             case [0x00, 0x16]:
-//                 self = .rsapss
-//             case [0x00, 0x17]:
-//                 self = .oaep
-//             case [0x00, 0x18]:
-//                 self = .ecdsa
-//             case [0x00, 0x19]:
-//                 self = .ecdh
-//             case [0x00, 0x1a]:
-//                 self = .ecdaa
-//             case [0x00, 0x1b]:
-//                 self = .sm2
-//             case [0x00, 0x1c]:
-//                 self = .ecschnorr
-//             case [0x00, 0x1d]:
-//                 self = .ecmqv
-//             case [0x00, 0x20]:
-//                 self = .kdf1Sp80056a
-//             case [0x00, 0x21]:
-//                 self = .kdf2
-//             case [0x00, 0x22]:
-//                 self = .kdf1Sp800108
-//             case [0x00, 0x23]:
-//                 self = .ecc
-//             case [0x00, 0x25]:
-//                 self = .symcipher
-//             case [0x00, 0x26]:
-//                 self = .camellia
-//             case [0x00, 0x40]:
-//                 self = .ctr
-//             case [0x00, 0x41]:
-//                 self = .ofb
-//             case [0x00, 0x42]:
-//                 self = .cbc
-//             case [0x00, 0x43]:
-//                 self = .cfb
-//             case [0x00, 0x44]:
-//                 self = .ecb
-//             default:
-//                 return nil
-//             }
-//         }
-//     }
-// }
-
-// extension TPMAttestation {
-//     enum ECCCurve: String {
-//         case none = "NONE"
-//         case nistP192 = "NIST_P192"
-//         case nistP224 = "NIST_P224"
-//         case nistP256 = "NIST_P256"
-//         case nistP384 = "NIST_P384"
-//         case nistP521 = "NIST_P521"
-//         case bnP256 = "BN_P256"
-//         case bnP638 = "BN_P638"
-//         case sm2P256 = "SM2_P256"
-
-//         init?(from data: Data) {
-//             let bytes = [UInt8](data)
-//             switch bytes {
-//             case [0x00, 0x00]:
-//                 self = .none
-//             case [0x00, 0x01]:
-//                 self = .nistP192
-//             case [0x00, 0x02]:
-//                 self = .nistP224
-//             case [0x00, 0x03]:
-//                 self = .nistP256
-//             case [0x00, 0x04]:
-//                 self = .nistP384
-//             case [0x00, 0x05]:
-//                 self = .nistP521
-//             case [0x00, 0x10]:
-//                 self = .bnP256
-//             case [0x00, 0x11]:
-//                 self = .bnP638
-//             case [0x00, 0x20]:
-//                 self = .sm2P256
-//             default:
-//                 return nil
-//             }
-//         }
-//     }
-// }
-
-// extension TPMAttestation {
-//     struct PubAreaParametersRSA {
-//         let symmetric: TPMAlg
-//         let scheme: TPMAlg
-//         let key: Data
-//         let exponent: Data
-
-//         init?(from data: Data) {
-//             guard let symmetricData = data[safe: 0..<2],
-//                 let symmetric = TPMAlg(from: symmetricData),
-//                 let schemeData = data[safe: 2..<4],
-//                 let scheme = TPMAlg(from: schemeData),
-//                 let key = data[safe: 4..<6],
-//                 let exponent = data[safe: 6..<10] else {
-//                     return nil
-//                 }
-
-//             self.symmetric = symmetric
-//             self.scheme = scheme
-//             self.key = key
-//             self.exponent = exponent
-//         }
-//     }
-// }
-
-// extension TPMAttestation {
-//     struct PubAreaParametersECC {
-//         let symmetric: TPMAlg
-//         let scheme: TPMAlg
-//         let curveID: ECCCurve
-//         let kdf: TPMAlg
-
-//         init?(from data: Data) {
-//             guard let symmetricData = data[safe: 0..<2],
-//                 let symmetric = TPMAlg(from: symmetricData),
-//                 let schemeData = data[safe: 2..<4],
-//                 let scheme = TPMAlg(from: schemeData),
-//                 let curveIDData = data[safe: 4..<6],
-//                 let curveID = ECCCurve(from: curveIDData),
-//                 let kdfData = data[safe: 6..<8],
-//                 let kdf = TPMAlg(from: kdfData) else {
-//                     return nil
-//                 }
-
-//             self.symmetric = symmetric
-//             self.scheme = scheme
-//             self.curveID = curveID
-//             self.kdf = kdf
-//         }
-//     }
-// }
-
-// extension TPMAttestation {
-//     struct PubAreaUnique {
-//         let data: Data
-
-//         init?(from data: Data, algorithm: TPMAlg) {
-//             switch algorithm {
-//             case .rsa:
-//                 guard let uniqueLength: Int = data[safe: 0..<2]?.toInteger(endian: .big),
-//                     let rsaUnique = data[safe: 2..<(2 + uniqueLength)] else {
-//                         return nil
-//                     }
-//                 self.data = rsaUnique
-//             case .ecc:
-//                 var pointer = 0
-//                 guard let uniqueXLength: Int = data.safeSlice(length: 2, using: &pointer)?.toInteger(endian: .big),
-//                     let uniqueX = data.safeSlice(length: uniqueXLength, using: &pointer),
-//                     let uniqueYLength: Int = data.safeSlice(length: 2, using: &pointer)?.toInteger(endian: .big),
-//                     let uniqueY = data.safeSlice(length: uniqueYLength, using: &pointer) else {
-//                         return nil
-//                     }
-//                 self.data = uniqueX + uniqueY
-//             default:
-//                 return nil
-//             }
-//         }
-//     }
-// }
-
-// extension COSECurve {
-//     init?(from eccCurve: TPMAttestation.ECCCurve) {
-//         switch eccCurve {
-//         case .nistP256, .bnP256, .sm2P256:
-//             self = .p256
-//         case .nistP384:
-//             self = .p384
-//         case .nistP521:
-//             self = .p521
-//         default:
-//             return nil
-//         }
-//     }
-// }
\ No newline at end of file
+import Foundation
+
+extension TPMAttestation {
+    enum CertInfoError: Error {
+        case magicInvalid
+        case typeInvalid
+        case dataTooShort
+        case tpmImplementationIsWIP
+    }
+
+    struct AttestationInformation {
+        let name: Data
+        let qualifiedName: Data
+    }
+
+    struct CertInfo {
+        let magic: Data
+        let type: Data
+        let qualifiedSigner: Data
+        let extraData: Data
+        let clockInfo: Data
+        let firmwareVersion: Data
+        let attested: AttestationInformation
+
+        init?(fromBytes data: Data) {
+            var pointer = 0
+
+            guard let magic = data[safe: pointer..<(pointer + 4)] else { return nil }
+            self.magic = magic
+            pointer += 4
+
+            guard let type = data[safe: pointer..<(pointer + 2)] else { return nil }
+            self.type = type
+            pointer += 2
+
+            guard let qualifiedSignerLengthData = data[safe: pointer..<(pointer + 2)] else { return nil }
+            pointer += 2
+            let qualifiedSignerLength: Int = qualifiedSignerLengthData.toInteger(endian: .big)
+            guard let qualifiedSigner = data[safe: pointer..<(pointer + qualifiedSignerLength)] else { return nil }
+            self.qualifiedSigner = qualifiedSigner
+            pointer += qualifiedSignerLength
+
+            guard let extraDataLengthData = data[safe: pointer..<(pointer + 2)] else { return nil }
+            pointer += 2
+            let extraDataLength: Int = extraDataLengthData.toInteger(endian: .big)
+            guard let extraData = data[safe: pointer..<(pointer + extraDataLength)] else { return nil }
+            self.extraData = extraData
+            pointer += extraDataLength
+
+            guard let clockInfo = data[safe: pointer..<(pointer + 17)] else { return nil }
+            self.clockInfo = clockInfo
+            pointer += 17
+
+            guard let firmwareVersion = data[safe: pointer..<(pointer + 8)] else { return nil }
+            self.firmwareVersion = firmwareVersion
+            pointer += 8
+
+            guard let attestedNameLengthData = data[safe: pointer..<(pointer + 2)] else { return nil }
+            pointer += 2
+            let attestedNameLength: Int = attestedNameLengthData.toInteger(endian: .big)
+            guard let attestedName = data[safe: pointer..<(pointer + attestedNameLength)] else { return nil }
+            pointer += attestedNameLength
+
+            guard let qualifiedNameLengthData = data[safe: pointer..<(pointer + 2)] else { return nil }
+            pointer += 2
+            let qualifiedNameLength: Int = qualifiedNameLengthData.toInteger(endian: .big)
+            guard let qualifiedName = data[safe: pointer..<(pointer + qualifiedNameLength)] else { return nil }
+            pointer += qualifiedNameLength
+
+            attested = AttestationInformation(name: attestedName, qualifiedName: qualifiedName)
+        }
+
+        func verify() throws {
+            let tpmGeneratedValue = 0xFF544347
+            guard magic.toInteger(endian: .big) == tpmGeneratedValue else {
+                throw CertInfoError.magicInvalid
+            }
+
+            let tpmStAttestCertify = 0x8017
+            guard type.toInteger(endian: .big) == tpmStAttestCertify else {
+                throw CertInfoError.typeInvalid
+            }
+
+            throw CertInfoError.tpmImplementationIsWIP
+        }
+    }
+
+    enum PubAreaParameters {
+        case rsa(PubAreaParametersRSA)
+        case ecc (PubAreaParametersECC)
+    }
+
+    struct PubArea {
+        let type: Data
+        let nameAlg: Data
+        let objectAttributes: Data
+        let authPolicy: Data
+        let parameters: PubAreaParameters
+        let unique: PubAreaUnique
+
+        let mappedType: TPMAlg
+
+        init?(from data: Data) {
+            var pointer = 0
+
+            guard let type = data.safeSlice(length: 2, using: &pointer),
+                let mappedType = TPMAlg(from: type),
+                let nameAlg = data.safeSlice(length: 2, using: &pointer),
+                let objectAttributes = data.safeSlice(length: 4, using: &pointer),
+                let authPolicyLength: Int = data.safeSlice(length: 2, using: &pointer)?.toInteger(endian: .big),
+                let authPolicy = data.safeSlice(length: authPolicyLength, using: &pointer) else {
+                return nil
+            }
+
+            self.type = type
+            self.nameAlg = nameAlg
+            self.objectAttributes = objectAttributes
+            self.authPolicy = authPolicy
+
+            self.mappedType = mappedType
+
+            switch mappedType {
+            case .rsa:
+                guard let rsa = data.safeSlice(length: 10, using: &pointer),
+                    let parameters = PubAreaParametersRSA(from: rsa) else { return nil }
+                self.parameters = .rsa(parameters)
+            case .ecc:
+                guard let ecc = data.safeSlice(length: 8, using: &pointer),
+                    let parameters = PubAreaParametersECC(from: ecc) else { return nil }
+                self.parameters = .ecc(parameters)
+            default:
+                return nil
+            }
+
+            guard data.count >= pointer,
+                let unique = PubAreaUnique(from: data[pointer...], algorithm: mappedType) else {
+                return nil
+            }
+
+            self.unique = unique
+        }
+    }
+}
+
+extension TPMAttestation {
+    enum TPMAlg: String {
+        case error = "TPM_ALG_ERROR"
+        case rsa = "TPM_ALG_RSA"
+        case sha1 = "TPM_ALG_SHA1"
+        case hmac = "TPM_ALG_HMAC"
+        case aes = "TPM_ALG_AES"
+        case mgf1 = "TPM_ALG_MGF1"
+        case keyedhash = "TPM_ALG_KEYEDHASH"
+        case xor = "TPM_ALG_XOR"
+        case sha256 = "TPM_ALG_SHA256"
+        case sha384 = "TPM_ALG_SHA384"
+        case sha512 = "TPM_ALG_SHA512"
+        case null = "TPM_ALG_NULL"
+        case sm3256 = "TPM_ALG_SM3_256"
+        case sm4 = "TPM_ALG_SM4"
+        case rsassa = "TPM_ALG_RSASSA"
+        case rsaes = "TPM_ALG_RSAES"
+        case rsapss = "TPM_ALG_RSAPSS"
+        case oaep = "TPM_ALG_OAEP"
+        case ecdsa = "TPM_ALG_ECDSA"
+        case ecdh = "TPM_ALG_ECDH"
+        case ecdaa = "TPM_ALG_ECDAA"
+        case sm2 = "TPM_ALG_SM2"
+        case ecschnorr = "TPM_ALG_ECSCHNORR"
+        case ecmqv = "TPM_ALG_ECMQV"
+        case kdf1Sp80056a = "TPM_ALG_KDF1_SP800_56A"
+        case kdf2 = "TPM_ALG_KDF2"
+        case kdf1Sp800108 = "TPM_ALG_KDF1_SP800_108"
+        case ecc = "TPM_ALG_ECC"
+        case symcipher = "TPM_ALG_SYMCIPHER"
+        case camellia = "TPM_ALG_CAMELLIA"
+        case ctr = "TPM_ALG_CTR"
+        case ofb = "TPM_ALG_OFB"
+        case cbc = "TPM_ALG_CBC"
+        case cfb = "TPM_ALG_CFB"
+        case ecb = "TPM_ALG_ECB"
+
+        // swiftlint:disable:next cyclomatic_complexity function_body_length
+        init?(from data: Data) {
+            let bytes = [UInt8](data)
+            switch bytes {
+            case [0x00, 0x00]:
+                self = .error
+            case [0x00, 0x01]:
+                self = .rsa
+            case [0x00, 0x04]:
+                self = .sha1
+            case [0x00, 0x05]:
+                self = .hmac
+            case [0x00, 0x06]:
+                self = .aes
+            case [0x00, 0x07]:
+                self = .mgf1
+            case [0x00, 0x08]:
+                self = .keyedhash
+            case [0x00, 0x0a]:
+                self = .xor
+            case [0x00, 0x0b]:
+                self = .sha256
+            case [0x00, 0x0c]:
+                self = .sha384
+            case [0x00, 0x0d]:
+                self = .sha512
+            case [0x00, 0x10]:
+                self = .null
+            case [0x00, 0x12]:
+                self = .sm3256
+            case [0x00, 0x13]:
+                self = .sm4
+            case [0x00, 0x14]:
+                self = .rsassa
+            case [0x00, 0x15]:
+                self = .rsaes
+            case [0x00, 0x16]:
+                self = .rsapss
+            case [0x00, 0x17]:
+                self = .oaep
+            case [0x00, 0x18]:
+                self = .ecdsa
+            case [0x00, 0x19]:
+                self = .ecdh
+            case [0x00, 0x1a]:
+                self = .ecdaa
+            case [0x00, 0x1b]:
+                self = .sm2
+            case [0x00, 0x1c]:
+                self = .ecschnorr
+            case [0x00, 0x1d]:
+                self = .ecmqv
+            case [0x00, 0x20]:
+                self = .kdf1Sp80056a
+            case [0x00, 0x21]:
+                self = .kdf2
+            case [0x00, 0x22]:
+                self = .kdf1Sp800108
+            case [0x00, 0x23]:
+                self = .ecc
+            case [0x00, 0x25]:
+                self = .symcipher
+            case [0x00, 0x26]:
+                self = .camellia
+            case [0x00, 0x40]:
+                self = .ctr
+            case [0x00, 0x41]:
+                self = .ofb
+            case [0x00, 0x42]:
+                self = .cbc
+            case [0x00, 0x43]:
+                self = .cfb
+            case [0x00, 0x44]:
+                self = .ecb
+            default:
+                return nil
+            }
+        }
+    }
+}
+
+extension TPMAttestation {
+    enum ECCCurve: String {
+        case none = "NONE"
+        case nistP192 = "NIST_P192"
+        case nistP224 = "NIST_P224"
+        case nistP256 = "NIST_P256"
+        case nistP384 = "NIST_P384"
+        case nistP521 = "NIST_P521"
+        case bnP256 = "BN_P256"
+        case bnP638 = "BN_P638"
+        case sm2P256 = "SM2_P256"
+
+        init?(from data: Data) {
+            let bytes = [UInt8](data)
+            switch bytes {
+            case [0x00, 0x00]:
+                self = .none
+            case [0x00, 0x01]:
+                self = .nistP192
+            case [0x00, 0x02]:
+                self = .nistP224
+            case [0x00, 0x03]:
+                self = .nistP256
+            case [0x00, 0x04]:
+                self = .nistP384
+            case [0x00, 0x05]:
+                self = .nistP521
+            case [0x00, 0x10]:
+                self = .bnP256
+            case [0x00, 0x11]:
+                self = .bnP638
+            case [0x00, 0x20]:
+                self = .sm2P256
+            default:
+                return nil
+            }
+        }
+    }
+}
+
+extension TPMAttestation {
+    struct PubAreaParametersRSA {
+        let symmetric: TPMAlg
+        let scheme: TPMAlg
+        let key: Data
+        let exponent: Data
+
+        init?(from data: Data) {
+            guard let symmetricData = data[safe: 0..<2],
+                let symmetric = TPMAlg(from: symmetricData),
+                let schemeData = data[safe: 2..<4],
+                let scheme = TPMAlg(from: schemeData),
+                let key = data[safe: 4..<6],
+                let exponent = data[safe: 6..<10] else {
+                    return nil
+                }
+
+            self.symmetric = symmetric
+            self.scheme = scheme
+            self.key = key
+            self.exponent = exponent
+        }
+    }
+}
+
+extension TPMAttestation {
+    struct PubAreaParametersECC {
+        let symmetric: TPMAlg
+        let scheme: TPMAlg
+        let curveID: ECCCurve
+        let kdf: TPMAlg
+
+        init?(from data: Data) {
+            guard let symmetricData = data[safe: 0..<2],
+                let symmetric = TPMAlg(from: symmetricData),
+                let schemeData = data[safe: 2..<4],
+                let scheme = TPMAlg(from: schemeData),
+                let curveIDData = data[safe: 4..<6],
+                let curveID = ECCCurve(from: curveIDData),
+                let kdfData = data[safe: 6..<8],
+                let kdf = TPMAlg(from: kdfData) else {
+                    return nil
+                }
+
+            self.symmetric = symmetric
+            self.scheme = scheme
+            self.curveID = curveID
+            self.kdf = kdf
+        }
+    }
+}
+
+extension TPMAttestation {
+    struct PubAreaUnique {
+        let data: Data
+
+        init?(from data: Data, algorithm: TPMAlg) {
+            switch algorithm {
+            case .rsa:
+                guard let uniqueLength: Int = data[safe: 0..<2]?.toInteger(endian: .big),
+                    let rsaUnique = data[safe: 2..<(2 + uniqueLength)] else {
+                        return nil
+                    }
+                self.data = rsaUnique
+            case .ecc:
+                var pointer = 0
+                guard let uniqueXLength: Int = data.safeSlice(length: 2, using: &pointer)?.toInteger(endian: .big),
+                    let uniqueX = data.safeSlice(length: uniqueXLength, using: &pointer),
+                    let uniqueYLength: Int = data.safeSlice(length: 2, using: &pointer)?.toInteger(endian: .big),
+                    let uniqueY = data.safeSlice(length: uniqueYLength, using: &pointer) else {
+                        return nil
+                    }
+                self.data = uniqueX + uniqueY
+            default:
+                return nil
+            }
+        }
+    }
+}
+
+extension COSECurve {
+    init?(from eccCurve: TPMAttestation.ECCCurve) {
+        switch eccCurve {
+        case .nistP256, .bnP256, .sm2P256:
+            self = .p256
+        case .nistP384:
+            self = .p384
+        case .nistP521:
+            self = .p521
+        default:
+            return nil
+        }
+    }
+}
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation.swift
index 917d97fa..5117bfa0 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation.swift
@@ -12,109 +12,162 @@
 //
 //===----------------------------------------------------------------------===//
 
-// 🚨 WIP
+import Foundation
+import SwiftCBOR
+import X509
+import SwiftASN1
 
-// import Foundation
-// import SwiftCBOR
+struct TPMAttestation {
+    enum TPMAttestationError: Error {
+        case pubAreaInvalid
+        case certInfoInvalid
+        case invalidAlg
+        case invalidVersion
+        case invalidX5c
+        case invalidPublicKey
+        case invalidLeafCertificate
+        case attestationCertificateSubjectNotEmpty
+        case attestationCertificateMissingTcgKpAIKCertificate
+        case attestationCertificateIsCA
+        case invalidCertAaguid
+        case aaguidMismatch
+        case pubAreaExponentDoesNotMatchPubKeyExponent
+        case invalidPubAreaCurve
+        case extraDataDoesNotMatchAttToBeSignedHash
+    }
 
-// /// 🚨 WIP
-// struct TPMAttestation {
-//     enum TPMAttestationError: Error {
-//         case pubAreaInvalid
-//         case certInfoInvalid
-//         case invalidAlg
-//         case invalidVersion
-//         case invalidX5c
-//         case invalidPublicKey
-//         case pubAreaExponentDoesNotMatchPubKeyExponent
-//         case invalidPubAreaCurve
-//         case extraDataDoesNotMatchAttToBeSignedHash
-//     }
+    static func verify(
+        attStmt: CBOR,
+        authenticatorData: Data,
+        attestedCredentialData: AttestedCredentialData,
+        clientDataHash: Data,
+        credentialPublicKey: CredentialPublicKey,
+        pemRootCertificates: [Data]
+    ) async throws -> [Certificate] {
+        // Verify version
+        guard let verCBOR = attStmt["ver"],
+            case let .utf8String(ver) = verCBOR,
+            ver == "2.0" else {
+            throw TPMAttestationError.invalidVersion
+        }
 
-//     static func verify(
-//         attStmt: CBOR,
-//         authenticatorData: Data,
-//         attestedCredentialData: AttestedCredentialData,
-//         clientDataHash: Data,
-//         credentialPublicKey: CredentialPublicKey,
-//         pemRootCertificates: [Data]
-//     ) throws {
-//         // Verify version
-//         guard let verCBOR = attStmt["ver"],
-//             case let .utf8String(ver) = verCBOR,
-//             ver == "2.0" else {
-//             throw TPMAttestationError.invalidVersion
-//         }
+        guard let x5cCBOR = attStmt["x5c"],
+            case let .array(x5cCBOR) = x5cCBOR else {
+                throw TPMAttestationError.invalidX5c
+        }
+        
+        // Verify certificate chain
+        let x5c: [Certificate] = try x5cCBOR.map {
+            guard case let .byteString(certificate) = $0 else {
+                throw TPMAttestationError.invalidX5c
+            }
+            return try Certificate(derEncoded: certificate)
+        }
+        guard let aikCert = x5c.first else { throw TPMAttestationError.invalidX5c }
+        let intermediates = CertificateStore(x5c[1...])
+        let rootCertificates = CertificateStore(
+            try pemRootCertificates.map { try Certificate(derEncoded: [UInt8]($0)) }
+        )
 
-//         // Verify certificate chain
-//         guard let x5cCBOR = attStmt["x5c"],
-//             case let .array(x5cArray) = x5cCBOR,
-//             case let .byteString(aikCert) = x5cArray.first else {
-//             throw TPMAttestationError.invalidX5c
-//         }
-//         let certificateChain = try x5cArray[1...].map {
-//             guard case let .byteString(caCert) = $0 else { throw TPMAttestationError.invalidX5c }
-//             return caCert
-//         }
+        // TPM Attestation Statement Certificate Requirements
+        // Subject field MUST be set to empty.
+        guard aikCert.subject.isEmpty else {
+            throw TPMAttestationError.attestationCertificateSubjectNotEmpty
+        }
+        // The Extended Key Usage extension MUST contain the OID 2.23.133.8.3
+        guard aikCert.extensions.contains(where: {$0.oid == .tcgKpAIKCertificate}) else {
+            throw TPMAttestationError.attestationCertificateMissingTcgKpAIKCertificate
+        }
+        // The Basic Constraints extension MUST have the CA component set to false.
+        guard case .notCertificateAuthority = try aikCert.extensions.basicConstraints  else {
+            throw TPMAttestationError.attestationCertificateIsCA
+        }
+        
+        
+        var verifier = Verifier(rootCertificates: rootCertificates) {
+            // TODO: do we really want to validate a cert expiry for devices that cannot be updated?
+            // An expired device cert just means that the device is "old".
+            RFC5280Policy(validationTime: Date())
+        }
+        let verifierResult: VerificationResult = await verifier.validate(
+            leafCertificate: aikCert,
+            intermediates: intermediates
+        )
+        guard case .validCertificate(let chain) = verifierResult else {
+            throw TPMAttestationError.invalidLeafCertificate
+        }
+        
+        // Verify that the value of the aaguid extension, if present, matches aaguid in authenticatorData
+        if let certAAGUID = aikCert.extensions.first(
+            where: {$0.oid == .idFidoGenCeAaguid}
+        ) {
+            // The AAGUID is wrapped in two OCTET STRINGS
+            let derValue = try DER.parse(certAAGUID.value)
+            guard case .primitive(let certAaguidValue) = derValue.content else {
+                throw TPMAttestationError.invalidCertAaguid
+            }
+            
+            let authenticatorData = try AuthenticatorData(bytes: Array(authenticatorData))
+            guard let attestedData = authenticatorData.attestedData,
+                  attestedData.aaguid == Array(certAaguidValue) else {
+                throw TPMAttestationError.aaguidMismatch
+            }
+        }
 
-//         // TODO: Validate certificate chain
-//         // try CertificateChain.validate(
-//         //    x5c: aikCert + certificateChain,
-//         //    pemRootCertificates: pemRootCertificates
-//         // )
+        // Verify pubArea
+        guard let pubAreaCBOR = attStmt["pubArea"],
+            case let .byteString(pubArea) = pubAreaCBOR,
+            let pubArea = PubArea(from: Data(pubArea)) else {
+            throw TPMAttestationError.pubAreaInvalid
+        }
+       switch pubArea.parameters {
+       case let .rsa(rsaParameters):
+           guard case let .rsa(rsaPublicKeyData) = credentialPublicKey,
+               Array(pubArea.unique.data) == rsaPublicKeyData.n else {
+               throw TPMAttestationError.invalidPublicKey
+           }
+           var pubAreaExponent: Int = rsaParameters.exponent.toInteger(endian: .big)
+           if pubAreaExponent == 0 {
+               // "When zero, indicates that the exponent is the default of 2^16 + 1"
+               pubAreaExponent = 65537
+           }
 
-//         // Verify pubArea
-//         guard let pubAreaCBOR = attStmt["pubArea"],
-//             case let .byteString(pubArea) = pubAreaCBOR,
-//             let pubArea = PubArea(from: Data(pubArea)) else {
-//             throw TPMAttestationError.pubAreaInvalid
-//         }
-//        switch pubArea.parameters {
-//        case let .rsa(rsaParameters):
-//            guard case let .rsa(rsaPublicKeyData) = credentialPublicKey,
-//                pubArea.unique.data == rsaPublicKeyData.n else {
-//                throw TPMAttestationError.invalidPublicKey
-//            }
-//            var pubAreaExponent: Int = rsaParameters.exponent.toInteger(endian: .big)
-//            if pubAreaExponent == 0 {
-//                // "When zero, indicates that the exponent is the default of 2^16 + 1"
-//                pubAreaExponent = 65537
-//            }
-//
-//            let pubKeyExponent: Int = rsaPublicKeyData.e.toInteger(endian: .big)
-//            guard pubAreaExponent == pubKeyExponent else {
-//                throw TPMAttestationError.pubAreaExponentDoesNotMatchPubKeyExponent
-//            }
-//        case let .ecc(eccParameters):
-//            guard case let .ec2(ec2PublicKeyData) = credentialPublicKey,
-//                pubArea.unique.data == ec2PublicKeyData.rawRepresentation else {
-//                throw TPMAttestationError.invalidPublicKey
-//            }
-//
-//            guard let pubAreaCrv = COSECurve(from: eccParameters.curveID),
-//                pubAreaCrv == ec2PublicKeyData.curve else {
-//                throw TPMAttestationError.invalidPubAreaCurve
-//            }
-//        }
+           let pubKeyExponent: Int = rsaPublicKeyData.e.toInteger(endian: .big)
+           guard pubAreaExponent == pubKeyExponent else {
+               throw TPMAttestationError.pubAreaExponentDoesNotMatchPubKeyExponent
+           }
+       case let .ecc(eccParameters):
+           guard case let .ec2(ec2PublicKeyData) = credentialPublicKey,
+               Array(pubArea.unique.data) == ec2PublicKeyData.rawRepresentation else {
+               throw TPMAttestationError.invalidPublicKey
+           }
+
+           guard let pubAreaCrv = COSECurve(from: eccParameters.curveID),
+               pubAreaCrv == ec2PublicKeyData.curve else {
+               throw TPMAttestationError.invalidPubAreaCurve
+           }
+       }
 
-//         // Verify certInfo
-//         guard let certInfoCBOR = attStmt["certInfo"],
-//             case let .byteString(certInfo) = certInfoCBOR,
-//             let parsedCertInfo = CertInfo(fromBytes: Data(certInfo)) else {
-//             throw TPMAttestationError.certInfoInvalid
-//         }
-//         try parsedCertInfo.verify()
+        // Verify certInfo
+        guard let certInfoCBOR = attStmt["certInfo"],
+            case let .byteString(certInfo) = certInfoCBOR,
+            let parsedCertInfo = CertInfo(fromBytes: Data(certInfo)) else {
+            throw TPMAttestationError.certInfoInvalid
+        }
+        try parsedCertInfo.verify()
 
-//         let attToBeSigned = authenticatorData + clientDataHash
+        let attToBeSigned = authenticatorData + clientDataHash
 
-//         guard let algCBOR = attStmt["alg"],
-//             case let .negativeInt(algorithmNegative) = algCBOR,
-//             let alg = COSEAlgorithmIdentifier(rawValue: -1 - Int(algorithmNegative)) else {
-//             throw TPMAttestationError.invalidAlg
-//         }
+        guard let algCBOR = attStmt["alg"],
+            case let .negativeInt(algorithmNegative) = algCBOR,
+            let alg = COSEAlgorithmIdentifier(rawValue: -1 - Int(algorithmNegative)) else {
+            throw TPMAttestationError.invalidAlg
+        }
 
-//         guard alg.hashAndCompare(data: attToBeSigned, to: parsedCertInfo.extraData) else {
-//             throw TPMAttestationError.extraDataDoesNotMatchAttToBeSignedHash
-//         }
-//     }
-// }
+        guard alg.hashAndCompare(data: attToBeSigned, to: parsedCertInfo.extraData) else {
+            throw TPMAttestationError.extraDataDoesNotMatchAttToBeSignedHash
+        }
+        
+        return chain
+    }
+}

From 5d10ea5dca5c4736047ebb2219407957b5c58fcb Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Tue, 30 Apr 2024 08:31:12 +1000
Subject: [PATCH 09/30] WIP TPM attestation

---
 .../Formats/TPMAttestation+Structs.swift      | 33 ++++++++++++++-
 .../Registration/Formats/TPMAttestation.swift | 15 +++----
 .../Shared/COSE/COSEAlgorithmIdentifier.swift | 42 +++++++++----------
 .../Shared/CredentialPublicKey.swift          |  6 ++-
 .../WebAuthn/Helpers/Data+safeSubscript.swift |  3 +-
 .../TPMAttestationTests/CertInfoTests.swift   | 34 +++++++--------
 6 files changed, 83 insertions(+), 50 deletions(-)

diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation+Structs.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation+Structs.swift
index 8a147590..45a9cfad 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation+Structs.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation+Structs.swift
@@ -13,12 +13,15 @@
 //===----------------------------------------------------------------------===//
 
 import Foundation
+import Crypto
 
 extension TPMAttestation {
     enum CertInfoError: Error {
         case magicInvalid
         case typeInvalid
         case dataTooShort
+        case nameAlgInvalid
+        case pubAreaHashInvalid
         case tpmImplementationIsWIP
     }
 
@@ -84,18 +87,44 @@ extension TPMAttestation {
             attested = AttestationInformation(name: attestedName, qualifiedName: qualifiedName)
         }
 
-        func verify() throws {
+        func verify(pubArea: Data) throws {
+            // Verify that magic is set to TPM_GENERATED_VALUE
             let tpmGeneratedValue = 0xFF544347
             guard magic.toInteger(endian: .big) == tpmGeneratedValue else {
                 throw CertInfoError.magicInvalid
             }
 
+            // Verify that type is set to TPM_ST_ATTEST_CERTIFY
             let tpmStAttestCertify = 0x8017
             guard type.toInteger(endian: .big) == tpmStAttestCertify else {
                 throw CertInfoError.typeInvalid
             }
 
-            throw CertInfoError.tpmImplementationIsWIP
+            // Verify pubArea hash
+            guard let nameAlg = self.attested.name[safe: 0..<2],
+                let nameAlg = TPMAlg(from: nameAlg),
+                let nameHash = self.attested.name[safe: 2..<self.attested.name.count] else {
+                throw CertInfoError.nameAlgInvalid
+            }
+
+            let pubAreaHash: Data
+            switch nameAlg {
+            case .sha256:
+                pubAreaHash = Data(SHA256.hash(data: pubArea))
+            case .sha384:
+                pubAreaHash = Data(SHA384.hash(data: pubArea))
+            case .sha512:
+                pubAreaHash = Data(SHA512.hash(data: pubArea))
+            case .sha1:
+                pubAreaHash = Data(Insecure.SHA1.hash(data: pubArea))
+            // TODO: would we possibly receive other hash algorithms here?
+            default:
+                throw CertInfoError.tpmImplementationIsWIP
+            }
+            
+            guard pubAreaHash == nameHash else {
+                throw CertInfoError.pubAreaHashInvalid
+            }
         }
     }
 
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation.swift
index 5117bfa0..ee86a157 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation.swift
@@ -57,7 +57,7 @@ struct TPMAttestation {
         }
         
         // Verify certificate chain
-        let x5c: [Certificate] = try x5cCBOR.map {
+        /*let x5c: [Certificate] = try x5cCBOR.map {
             guard case let .byteString(certificate) = $0 else {
                 throw TPMAttestationError.invalidX5c
             }
@@ -112,12 +112,12 @@ struct TPMAttestation {
                   attestedData.aaguid == Array(certAaguidValue) else {
                 throw TPMAttestationError.aaguidMismatch
             }
-        }
+        }*/
 
         // Verify pubArea
         guard let pubAreaCBOR = attStmt["pubArea"],
-            case let .byteString(pubArea) = pubAreaCBOR,
-            let pubArea = PubArea(from: Data(pubArea)) else {
+            case let .byteString(pubAreaRaw) = pubAreaCBOR,
+            let pubArea = PubArea(from: Data(pubAreaRaw)) else {
             throw TPMAttestationError.pubAreaInvalid
         }
        switch pubArea.parameters {
@@ -154,9 +154,8 @@ struct TPMAttestation {
             let parsedCertInfo = CertInfo(fromBytes: Data(certInfo)) else {
             throw TPMAttestationError.certInfoInvalid
         }
-        try parsedCertInfo.verify()
 
-        let attToBeSigned = authenticatorData + clientDataHash
+        try parsedCertInfo.verify(pubArea: Data(pubAreaRaw))
 
         guard let algCBOR = attStmt["alg"],
             case let .negativeInt(algorithmNegative) = algCBOR,
@@ -164,10 +163,12 @@ struct TPMAttestation {
             throw TPMAttestationError.invalidAlg
         }
 
+        // Verify that extraData is set to the hash of attToBeSigned using the hash algorithm employed in "alg"
+        let attToBeSigned = authenticatorData + clientDataHash
         guard alg.hashAndCompare(data: attToBeSigned, to: parsedCertInfo.extraData) else {
             throw TPMAttestationError.extraDataDoesNotMatchAttToBeSignedHash
         }
         
-        return chain
+        return [] //chain
     }
 }
diff --git a/Sources/WebAuthn/Ceremonies/Shared/COSE/COSEAlgorithmIdentifier.swift b/Sources/WebAuthn/Ceremonies/Shared/COSE/COSEAlgorithmIdentifier.swift
index 40ac771c..eddaed08 100644
--- a/Sources/WebAuthn/Ceremonies/Shared/COSE/COSEAlgorithmIdentifier.swift
+++ b/Sources/WebAuthn/Ceremonies/Shared/COSE/COSEAlgorithmIdentifier.swift
@@ -26,33 +26,33 @@ public enum COSEAlgorithmIdentifier: Int, RawRepresentable, CaseIterable, Encoda
 	/// AlgES512 ECDSA with SHA-512
 	case algES512 = -36
 
-	// We don't support RSA yet
-
-	// /// AlgRS1 RSASSA-PKCS1-v1_5 with SHA-1
-	// case algRS1 = -65535
-	// /// AlgRS256 RSASSA-PKCS1-v1_5 with SHA-256
-	// case algRS256 = -257
-	// /// AlgRS384 RSASSA-PKCS1-v1_5 with SHA-384
-	// case algRS384 = -258
-	// /// AlgRS512 RSASSA-PKCS1-v1_5 with SHA-512
-	// case algRS512 = -259
-	// /// AlgPS256 RSASSA-PSS with SHA-256
-	// case algPS256 = -37
-	// /// AlgPS384 RSASSA-PSS with SHA-384
-	// case algPS384 = -38
-	// /// AlgPS512 RSASSA-PSS with SHA-512
-	// case algPS512 = -39
-	// // AlgEdDSA EdDSA
-	// case algEdDSA = -8
+	/// AlgRS1 RSASSA-PKCS1-v1_5 with SHA-1
+	case algRS1 = -65535
+	/// AlgRS256 RSASSA-PKCS1-v1_5 with SHA-256
+	case algRS256 = -257
+	/// AlgRS384 RSASSA-PKCS1-v1_5 with SHA-384
+	case algRS384 = -258
+	/// AlgRS512 RSASSA-PKCS1-v1_5 with SHA-512
+	case algRS512 = -259
+	/// AlgPS256 RSASSA-PSS with SHA-256
+	//case algPS256 = -37
+	/// AlgPS384 RSASSA-PSS with SHA-384
+	//case algPS384 = -38
+	/// AlgPS512 RSASSA-PSS with SHA-512
+	//case algPS512 = -39
+	// AlgEdDSA EdDSA
+	//case algEdDSA = -8
 
 	func hashAndCompare(data: Data, to compareHash: Data) -> Bool {
 		switch self {
-		case .algES256:
+        case .algES256, .algRS256:
 			return SHA256.hash(data: data) == compareHash
-		case .algES384:
+        case .algES384, .algRS384:
 			return SHA384.hash(data: data) == compareHash
-		case .algES512:
+        case .algES512, .algRS512:
 			return SHA512.hash(data: data) == compareHash
+        case .algRS1:
+            return Insecure.SHA1.hash(data: data) == compareHash
 		}
 	}
 }
diff --git a/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift b/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
index a85ad418..1b0c2a5e 100644
--- a/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
+++ b/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
@@ -77,8 +77,7 @@ enum CredentialPublicKey {
         case .ellipticKey:
             self = try .ec2(EC2PublicKey(publicKeyObject: publicKeyObject, algorithm: algorithm))
         case .rsaKey:
-            throw WebAuthnError.unsupported
-            // self = try .rsa(RSAPublicKeyData(publicKeyObject: publicKeyObject, algorithm: algorithm))
+            self = try .rsa(RSAPublicKeyData(publicKeyObject: publicKeyObject, algorithm: algorithm))
         case .octetKey:
             throw WebAuthnError.unsupported
             // self = try .okp(OKPPublicKey(publicKeyObject: publicKeyObject, algorithm: algorithm))
@@ -154,6 +153,8 @@ struct EC2PublicKey: PublicKey {
                 .isValidSignature(ecdsaSignature, for: data) else {
                 throw WebAuthnError.invalidSignature
             }
+        default:
+            throw WebAuthnError.unsupportedCOSEAlgorithm
         }
     }
 }
@@ -185,6 +186,7 @@ struct RSAPublicKeyData: PublicKey {
     }
 
     func verify(signature: some DataProtocol, data: some DataProtocol) throws {
+        print("\n•••••• \(Self.self).verify() ")
         throw WebAuthnError.unsupported
         // let rsaSignature = _RSA.Signing.RSASignature(derRepresentation: signature)
 
diff --git a/Sources/WebAuthn/Helpers/Data+safeSubscript.swift b/Sources/WebAuthn/Helpers/Data+safeSubscript.swift
index e3198207..3ac25984 100644
--- a/Sources/WebAuthn/Helpers/Data+safeSubscript.swift
+++ b/Sources/WebAuthn/Helpers/Data+safeSubscript.swift
@@ -18,8 +18,9 @@ extension Data {
     struct IndexOutOfBounds: Error {}
 
     subscript(safe range: Range<Int>) -> Data? {
+        let actualRange = range.lowerBound + self.startIndex..<range.upperBound+self.startIndex
         guard count >= range.upperBound else { return nil }
-        return self[range]
+        return self[actualRange]
     }
 
     /// Safely slices bytes from `pointer` to `pointer` + `length`. Updates the pointer afterwards.
diff --git a/Tests/WebAuthnTests/Formats/TPMAttestationTests/CertInfoTests.swift b/Tests/WebAuthnTests/Formats/TPMAttestationTests/CertInfoTests.swift
index 7f9b139a..1486f129 100644
--- a/Tests/WebAuthnTests/Formats/TPMAttestationTests/CertInfoTests.swift
+++ b/Tests/WebAuthnTests/Formats/TPMAttestationTests/CertInfoTests.swift
@@ -12,23 +12,23 @@
 //
 //===----------------------------------------------------------------------===//
 
-// @testable import WebAuthn
-// import XCTest
+@testable import WebAuthn
+import XCTest
 
-// final class CertInfoTests: XCTestCase {
-//     func testInitReturnsNilIfDataIsTooShort() {
-//         XCTAssertNil(TPMAttestation.CertInfo(fromBytes: Data([UInt8](repeating: 0, count: 8))))
-//         XCTAssertNil(TPMAttestation.CertInfo(fromBytes: Data()))
-//     }
+final class CertInfoTests: XCTestCase {
+    func testInitReturnsNilIfDataIsTooShort() {
+        XCTAssertNil(TPMAttestation.CertInfo(fromBytes: Data([UInt8](repeating: 0, count: 8))))
+        XCTAssertNil(TPMAttestation.CertInfo(fromBytes: Data()))
+    }
 
-//     func testVerifyThrowsIfMagicIsInvalid() throws {
-//         let certInfo = TPMAttestation.CertInfo(fromBytes: Data([UInt8](repeating: 0, count: 80)))!
-//         try assertThrowsError(certInfo.verify(), expect: TPMAttestation.CertInfoError.magicInvalid)
-//     }
+    func testVerifyThrowsIfMagicIsInvalid() throws {
+        let certInfo = TPMAttestation.CertInfo(fromBytes: Data([UInt8](repeating: 0, count: 80)))!
+        try assertThrowsError(certInfo.verify(pubArea: Data()), expect: TPMAttestation.CertInfoError.magicInvalid)
+    }
 
-//     func testVerifyThrowsIfTypeIsInvalid() throws {
-//         let certInfoBytes: [UInt8] = [0xFF, 0x54, 0x43, 0x47] + [UInt8](repeating: 0, count: 80)
-//         let certInfo = TPMAttestation.CertInfo(fromBytes: Data(certInfoBytes))!
-//         try assertThrowsError(certInfo.verify(), expect: TPMAttestation.CertInfoError.typeInvalid)
-//     }
-// }
+    func testVerifyThrowsIfTypeIsInvalid() throws {
+        let certInfoBytes: [UInt8] = [0xFF, 0x54, 0x43, 0x47] + [UInt8](repeating: 0, count: 80)
+        let certInfo = TPMAttestation.CertInfo(fromBytes: Data(certInfoBytes))!
+        try assertThrowsError(certInfo.verify(pubArea: Data()), expect: TPMAttestation.CertInfoError.typeInvalid)
+    }
+}

From 3fafcf3d534b422455feb0f2becbb0dc56d61f5e Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Wed, 1 May 2024 06:19:41 +1000
Subject: [PATCH 10/30] Certs veerifications

---
 .../Formats/FidoU2FAttestation.swift          | 16 ++---
 .../Formats/PackedAttestation.swift           |  9 ++-
 .../Formats/PackedVerificationPolicy.swift    | 49 ++++++++++++++
 .../Formats/PublicKey+verifySignature.swift   |  3 +
 .../Formats/TPMAttestation+Structs.swift      |  2 +-
 .../Registration/Formats/TPMAttestation.swift | 47 ++++++--------
 .../Formats/TPMVerificationPolicy.swift       | 64 +++++++++++++++++++
 7 files changed, 146 insertions(+), 44 deletions(-)
 create mode 100644 Sources/WebAuthn/Ceremonies/Registration/Formats/PackedVerificationPolicy.swift
 create mode 100644 Sources/WebAuthn/Ceremonies/Registration/Formats/TPMVerificationPolicy.swift

diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
index f8b20498..505039fc 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
@@ -20,7 +20,7 @@ struct FidoU2FAttestation {
     enum FidoU2FAttestationError: Error {
         case invalidSig
         case invalidX5C
-        case invalidLeafCertificate
+        case invalidTrustPath
         // attestation cert can only have a ecdsaWithSHA256 signature
         case invalidLeafCertificateSigType
         case invalidAttestationKeyType
@@ -63,7 +63,7 @@ struct FidoU2FAttestation {
         guard x5c.count == 1 else {
             throw FidoU2FAttestationError.invalidX5C
         }
-        
+
         guard let leafCertificate = x5c.first else { throw FidoU2FAttestationError.invalidX5C }
         let rootCertificates = CertificateStore(
             try pemRootCertificates.map { try Certificate(derEncoded: [UInt8]($0)) }
@@ -72,23 +72,21 @@ struct FidoU2FAttestation {
         guard leafCertificate.signatureAlgorithm == .ecdsaWithSHA256 else {
             throw FidoU2FAttestationError.invalidLeafCertificateSigType
         }
-        
+
         var verifier = Verifier(rootCertificates: rootCertificates) {
-            // TODO: do we really want to validate a cert expiry for devices that cannot be updated?
-            // An expired device cert just means that the device is "old".
-            RFC5280Policy(validationTime: Date())
+            PackedVerificationPolicy()
         }
         let verifierResult: VerificationResult = await verifier.validate(
             leafCertificate: leafCertificate,
             intermediates: .init()
         )
         guard case .validCertificate(let chain) = verifierResult else {
-            throw FidoU2FAttestationError.invalidLeafCertificate
+            throw FidoU2FAttestationError.invalidTrustPath
         }
 
         // With U2F, the public key used when calculating the signature (`sig`) was encoded in ANSI X9.62 format
         let ansiPublicKey = [0x04] + key.xCoordinate + key.yCoordinate
-        
+
         // https://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915/fido-u2f-raw-message-formats-v1.1-id-20160915.html#registration-response-message-success
         let verificationData = Data(
             [0x00] // A byte "reserved for future use" with the value 0x00.
@@ -97,7 +95,7 @@ struct FidoU2FAttestation {
             + attestedData.credentialID
             + ansiPublicKey
         )
-        
+
         // Verify signature
         let leafCertificatePublicKey: Certificate.PublicKey = leafCertificate.publicKey
         guard try leafCertificatePublicKey.verifySignature(
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
index 9e127a29..922582a9 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
@@ -22,7 +22,7 @@ struct PackedAttestation {
         case invalidAlg
         case invalidSig
         case invalidX5C
-        case invalidLeafCertificate
+        case invalidTrustPath
         case algDoesNotMatch
         case missingAttestedCredential
         // Authenticator data cannot be verified
@@ -69,14 +69,15 @@ struct PackedAttestation {
             var verifier = Verifier(rootCertificates: rootCertificates) {
                 // TODO: do we really want to validate a cert expiry for devices that cannot be updated?
                 // An expired device cert just means that the device is "old". 
-                RFC5280Policy(validationTime: Date())
+                //RFC5280Policy(validationTime: Date())
+                PackedVerificationPolicy()
             }
             let verifierResult: VerificationResult = await verifier.validate(
                 leafCertificate: attestnCert,
                 intermediates: intermediates
             )
             guard case .validCertificate(let chain) = verifierResult else {
-                throw PackedAttestationError.invalidLeafCertificate
+                throw PackedAttestationError.invalidTrustPath
             }
             
             // 2. Verify signature
@@ -109,9 +110,7 @@ struct PackedAttestation {
             }
             
             return chain
-
         } else { // self attestation is in use
-            print("\n ••••••• Self attestation!!!!!! ••••••• \n")
             guard credentialPublicKey.key.algorithm == alg else {
                 throw PackedAttestationError.algDoesNotMatch
             }
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedVerificationPolicy.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedVerificationPolicy.swift
new file mode 100644
index 00000000..4c379f9c
--- /dev/null
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedVerificationPolicy.swift
@@ -0,0 +1,49 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the WebAuthn Swift open source project
+//
+// Copyright (c) 2023 the WebAuthn Swift project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of WebAuthn Swift project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+import Foundation
+import SwiftASN1
+import X509
+
+/// Based on https://www.w3.org/TR/webauthn-2/#sctn-packed-attestation-cert-requirements
+/// Note: we are **not** validating the certificates dates.
+public struct PackedVerificationPolicy: VerifierPolicy {
+    public let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [
+        .X509ExtensionID.basicConstraints,
+        .X509ExtensionID.nameConstraints,
+        // The Subject Alternative Name extension MUST be set as defined in [TPMv2-EK-Profile] section 3.2.9.
+        .X509ExtensionID.subjectAlternativeName,
+        .X509ExtensionID.keyUsage,
+        .certificatePolicies,
+    ]
+
+    public func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) -> PolicyEvaluationResult {
+        let leaf = chain.leaf
+        
+        // Version MUST be set to 3
+        guard leaf.version == .v3 else {
+            return .failsToMeetPolicy(
+                reason: "Version MUST be set to 3: \(leaf)"
+            )
+        }
+        
+        // The Basic Constraints extension MUST have the CA component set to false
+        guard let basic = try? leaf.extensions.basicConstraints, case .notCertificateAuthority = basic else {
+            return .failsToMeetPolicy(
+                reason: "The Basic Constraints extension MUST have CA set to false: \(leaf)"
+            )
+        }
+        return .meetsPolicy
+    }
+}
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
index 95533e77..fc2c9707 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
@@ -60,4 +60,7 @@ extension SwiftASN1.ASN1ObjectIdentifier {
     static var tcgKpAIKCertificate: Self {
         .init(arrayLiteral: 2, 23, 133, 8, 3)
     }
+    static var certificatePolicies: Self {
+        .init(arrayLiteral: 2, 5, 29, 32)
+    }
 }
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation+Structs.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation+Structs.swift
index 45a9cfad..11434cd5 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation+Structs.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation+Structs.swift
@@ -130,7 +130,7 @@ extension TPMAttestation {
 
     enum PubAreaParameters {
         case rsa(PubAreaParametersRSA)
-        case ecc (PubAreaParametersECC)
+        case ecc(PubAreaParametersECC)
     }
 
     struct PubArea {
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation.swift
index ee86a157..2a2334c7 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation.swift
@@ -21,13 +21,16 @@ struct TPMAttestation {
     enum TPMAttestationError: Error {
         case pubAreaInvalid
         case certInfoInvalid
+        /// Invalid or unsupported attestation signature algorithm
         case invalidAlg
+        /// Unsupported TPM version
         case invalidVersion
         case invalidX5c
         case invalidPublicKey
-        case invalidLeafCertificate
+        case invalidTrustPath
         case attestationCertificateSubjectNotEmpty
         case attestationCertificateMissingTcgKpAIKCertificate
+        /// A leaf (atte4station) cert must not have the CA flag set.
         case attestationCertificateIsCA
         case invalidCertAaguid
         case aaguidMismatch
@@ -57,44 +60,31 @@ struct TPMAttestation {
         }
         
         // Verify certificate chain
-        /*let x5c: [Certificate] = try x5cCBOR.map {
+        let x5c: [Certificate] = try x5cCBOR.map {
             guard case let .byteString(certificate) = $0 else {
                 throw TPMAttestationError.invalidX5c
             }
             return try Certificate(derEncoded: certificate)
         }
+        
         guard let aikCert = x5c.first else { throw TPMAttestationError.invalidX5c }
         let intermediates = CertificateStore(x5c[1...])
         let rootCertificates = CertificateStore(
             try pemRootCertificates.map { try Certificate(derEncoded: [UInt8]($0)) }
         )
 
-        // TPM Attestation Statement Certificate Requirements
-        // Subject field MUST be set to empty.
-        guard aikCert.subject.isEmpty else {
-            throw TPMAttestationError.attestationCertificateSubjectNotEmpty
-        }
-        // The Extended Key Usage extension MUST contain the OID 2.23.133.8.3
-        guard aikCert.extensions.contains(where: {$0.oid == .tcgKpAIKCertificate}) else {
-            throw TPMAttestationError.attestationCertificateMissingTcgKpAIKCertificate
-        }
-        // The Basic Constraints extension MUST have the CA component set to false.
-        guard case .notCertificateAuthority = try aikCert.extensions.basicConstraints  else {
-            throw TPMAttestationError.attestationCertificateIsCA
-        }
-        
-        
         var verifier = Verifier(rootCertificates: rootCertificates) {
-            // TODO: do we really want to validate a cert expiry for devices that cannot be updated?
-            // An expired device cert just means that the device is "old".
-            RFC5280Policy(validationTime: Date())
+            TPMVerificationPolicy()
         }
         let verifierResult: VerificationResult = await verifier.validate(
             leafCertificate: aikCert,
-            intermediates: intermediates
+            intermediates: intermediates,
+            diagnosticCallback: { result in
+                print("\n •••• Self.self result=\(result)")
+            }
         )
         guard case .validCertificate(let chain) = verifierResult else {
-            throw TPMAttestationError.invalidLeafCertificate
+            throw TPMAttestationError.invalidTrustPath
         }
         
         // Verify that the value of the aaguid extension, if present, matches aaguid in authenticatorData
@@ -112,7 +102,7 @@ struct TPMAttestation {
                   attestedData.aaguid == Array(certAaguidValue) else {
                 throw TPMAttestationError.aaguidMismatch
             }
-        }*/
+        }
 
         // Verify pubArea
         guard let pubAreaCBOR = attStmt["pubArea"],
@@ -120,8 +110,8 @@ struct TPMAttestation {
             let pubArea = PubArea(from: Data(pubAreaRaw)) else {
             throw TPMAttestationError.pubAreaInvalid
         }
-       switch pubArea.parameters {
-       case let .rsa(rsaParameters):
+        switch pubArea.parameters {
+        case let .rsa(rsaParameters):
            guard case let .rsa(rsaPublicKeyData) = credentialPublicKey,
                Array(pubArea.unique.data) == rsaPublicKeyData.n else {
                throw TPMAttestationError.invalidPublicKey
@@ -136,7 +126,7 @@ struct TPMAttestation {
            guard pubAreaExponent == pubKeyExponent else {
                throw TPMAttestationError.pubAreaExponentDoesNotMatchPubKeyExponent
            }
-       case let .ecc(eccParameters):
+        case let .ecc(eccParameters):
            guard case let .ec2(ec2PublicKeyData) = credentialPublicKey,
                Array(pubArea.unique.data) == ec2PublicKeyData.rawRepresentation else {
                throw TPMAttestationError.invalidPublicKey
@@ -146,8 +136,7 @@ struct TPMAttestation {
                pubAreaCrv == ec2PublicKeyData.curve else {
                throw TPMAttestationError.invalidPubAreaCurve
            }
-       }
-
+        }
         // Verify certInfo
         guard let certInfoCBOR = attStmt["certInfo"],
             case let .byteString(certInfo) = certInfoCBOR,
@@ -169,6 +158,6 @@ struct TPMAttestation {
             throw TPMAttestationError.extraDataDoesNotMatchAttToBeSignedHash
         }
         
-        return [] //chain
+        return chain
     }
 }
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMVerificationPolicy.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMVerificationPolicy.swift
new file mode 100644
index 00000000..9091c36c
--- /dev/null
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMVerificationPolicy.swift
@@ -0,0 +1,64 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the WebAuthn Swift open source project
+//
+// Copyright (c) 2023 the WebAuthn Swift project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of WebAuthn Swift project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+import Foundation
+import SwiftASN1
+import X509
+
+/// Based on https://www.w3.org/TR/webauthn-2/#sctn-tpm-cert-requirements
+/// Note: we are **not** validating the certificates dates.
+public struct TPMVerificationPolicy: VerifierPolicy {
+    public let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [
+        .X509ExtensionID.basicConstraints,
+        .X509ExtensionID.nameConstraints,
+        // The Subject Alternative Name extension MUST be set as defined in [TPMv2-EK-Profile] section 3.2.9.
+        .X509ExtensionID.subjectAlternativeName,
+        .X509ExtensionID.keyUsage,
+        .certificatePolicies,
+    ]
+
+    public func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) -> PolicyEvaluationResult {
+        let leaf = chain.leaf
+        
+        // Version MUST be set to 3
+        guard leaf.version == .v3 else {
+            return .failsToMeetPolicy(
+                reason: "Version MUST be set to 3: \(leaf)"
+            )
+        }
+        
+        // The Subject Alternative Name extension MUST be set as defined in [TPMv2-EK-Profile] section 3.2.9.
+        // Note: looks like some TPM attestation certs signed by Microsoft have nither subject nor SAN.
+        /*guard let san = try? leaf.extensions.subjectAlternativeNames else {
+            return .failsToMeetPolicy(
+                reason: "Subject Alternative Name extension MUST be set: \(leaf)"
+            )
+        }*/
+        
+        // The Extended Key Usage extension MUST contain the "joint-iso-itu-t(2) internationalorganizations(23) 133 tcg-kp(8) tcg-kp-AIKCertificate(3)" OID.
+        guard let eku = try? leaf.extensions.extendedKeyUsage, eku.contains(.init(oid: .tcgKpAIKCertificate)) else {
+            return .failsToMeetPolicy(
+                reason: "Extended Key Usage extension MUST contain the tcg-kp-AIKCertificate OID: \(leaf)"
+            )
+        }
+        
+        // The Basic Constraints extension MUST have the CA component set to false
+        guard let basic = try? leaf.extensions.basicConstraints, case .notCertificateAuthority = basic else {
+            return .failsToMeetPolicy(
+                reason: "The Basic Constraints extension MUST have CA set to false: \(leaf)"
+            )
+        }
+        return .meetsPolicy
+    }
+}

From 18045021fd425003a3139c5d25accbd5f63bc054 Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Thu, 2 May 2024 09:51:05 +1000
Subject: [PATCH 11/30] Protocol for attestation verify(); add WIP AndroidKey
 attestation support

---
 .../Registration/AttestationObject.swift      | 15 ++-
 .../Registration/AttestationResult.swift      |  4 +-
 .../Registration/AttestedCredentialData.swift |  8 +-
 .../Formats/AndroidKeyAttestation.swift       | 94 +++++++++++++++++++
 .../AndroidKeyVerificationPolicy.swift        | 33 +++++++
 .../Formats/AttestationProtocol.swift         | 27 ++++++
 .../Formats/FidoU2FAttestation.swift          | 26 ++---
 .../Formats/FidoU2FVerificationPolicy.swift   | 46 +++++++++
 .../Formats/PackedAttestation.swift           | 18 ++--
 .../Formats/PackedVerificationPolicy.swift    |  2 -
 .../Registration/Formats/TPMAttestation.swift | 17 ++--
 .../Ceremonies/Shared/AuthenticatorData.swift |  3 +-
 .../Shared/CredentialPublicKey.swift          |  1 -
 13 files changed, 239 insertions(+), 55 deletions(-)
 create mode 100644 Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKeyAttestation.swift
 create mode 100644 Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKeyVerificationPolicy.swift
 create mode 100644 Sources/WebAuthn/Ceremonies/Registration/Formats/AttestationProtocol.swift
 create mode 100644 Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FVerificationPolicy.swift

diff --git a/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift b/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
index 5be4a8bd..c9276b0c 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
@@ -71,7 +71,7 @@ public struct AttestationObject {
         case .packed:
             trustedPath = try await PackedAttestation.verify(
                 attStmt: attestationStatement,
-                authenticatorData: Data(rawAuthenticatorData),
+                authenticatorData: authenticatorData,
                 clientDataHash: Data(clientDataHash),
                 credentialPublicKey: credentialPublicKey,
                 pemRootCertificates: pemRootCertificates
@@ -79,13 +79,19 @@ public struct AttestationObject {
         case .tpm:
             trustedPath = try await TPMAttestation.verify(
                 attStmt: attestationStatement,
-                authenticatorData: Data(rawAuthenticatorData),
-                attestedCredentialData: attestedCredentialData,
+                authenticatorData: authenticatorData,
+                clientDataHash: Data(clientDataHash),
+                credentialPublicKey: credentialPublicKey,
+                pemRootCertificates: pemRootCertificates
+            )
+        case .androidKey:
+            trustedPath = try await AndroidKeyAttestation.verify(
+                attStmt: attestationStatement,
+                authenticatorData: authenticatorData,
                 clientDataHash: Data(clientDataHash),
                 credentialPublicKey: credentialPublicKey,
                 pemRootCertificates: pemRootCertificates
             )
-            
         // Legacy format used mostly by older authenticators
         case .fidoU2F:
             trustedPath = try await FidoU2FAttestation.verify(
@@ -100,7 +106,6 @@ public struct AttestationObject {
         }
         
         return AttestationResult(
-            aaguid: [],
             format: format,
             trustChain: trustedPath,
             attestedCredentialData: attestedCredentialData
diff --git a/Sources/WebAuthn/Ceremonies/Registration/AttestationResult.swift b/Sources/WebAuthn/Ceremonies/Registration/AttestationResult.swift
index 875ae7b1..c578f059 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/AttestationResult.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/AttestationResult.swift
@@ -24,10 +24,10 @@ public struct AttestationResult {
         case anonCA
         case none
     }
-    public let aaguid: [UInt8]?
+    //public let aaguid: [UInt8]?
     public let format: AttestationFormat
     //public let type: AttestationType
     public let trustChain: [Certificate]
     
-    let attestedCredentialData: AttestedCredentialData
+    public let attestedCredentialData: AttestedCredentialData
 }
diff --git a/Sources/WebAuthn/Ceremonies/Registration/AttestedCredentialData.swift b/Sources/WebAuthn/Ceremonies/Registration/AttestedCredentialData.swift
index d264e977..432ab03c 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/AttestedCredentialData.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/AttestedCredentialData.swift
@@ -13,8 +13,8 @@
 //===----------------------------------------------------------------------===//
 
 // Contains the new public key created by the authenticator.
-struct AttestedCredentialData: Equatable {
-    let aaguid: [UInt8]
-    let credentialID: [UInt8]
-    let publicKey: [UInt8]
+public struct AttestedCredentialData: Equatable {
+    public let aaguid: [UInt8]
+    public let credentialID: [UInt8]
+    public let publicKey: [UInt8]
 }
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKeyAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKeyAttestation.swift
new file mode 100644
index 00000000..02411f1e
--- /dev/null
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKeyAttestation.swift
@@ -0,0 +1,94 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the WebAuthn Swift open source project
+//
+// Copyright (c) 2023 the WebAuthn Swift project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of WebAuthn Swift project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+import Foundation
+import SwiftCBOR
+import X509
+import SwiftASN1
+
+// https://www.w3.org/TR/webauthn-2/#sctn-android-key-attestation
+struct AndroidKeyAttestation: AttestationProtocol {
+    enum AndroidKeyAttestationError: Error {
+        case invalidSig
+        case invalidX5C
+        case invalidTrustPath
+        // Authenticator data cannot be verified
+        case invalidVerificationData
+        case credentialPublicKeyMismatch
+    }
+
+    static func verify(
+        attStmt: CBOR,
+        authenticatorData: AuthenticatorData,
+        clientDataHash: Data,
+        credentialPublicKey: CredentialPublicKey,
+        pemRootCertificates: [Data]
+    ) async throws -> [Certificate] {
+        guard let sigCBOR = attStmt["sig"], case let .byteString(sig) = sigCBOR else {
+            throw AndroidKeyAttestationError.invalidSig
+        }
+        
+        guard let x5cCBOR = attStmt["x5c"], case let .array(x5cCBOR) = x5cCBOR else {
+                throw AndroidKeyAttestationError.invalidX5C
+        }
+
+        let x5c: [Certificate] = try x5cCBOR.map {
+            guard case let .byteString(certificate) = $0 else {
+                throw AndroidKeyAttestationError.invalidX5C
+            }
+            return try Certificate(derEncoded: certificate)
+        }
+
+        guard let leafCertificate = x5c.first else { throw AndroidKeyAttestationError.invalidX5C }
+        let intermediates = CertificateStore(x5c[1...])
+        let rootCertificates = CertificateStore(
+            try pemRootCertificates.map { try Certificate(derEncoded: [UInt8]($0)) }
+        )
+
+        let verificationData = authenticatorData.rawData + clientDataHash
+        // Verify signature
+        let leafCertificatePublicKey: Certificate.PublicKey = leafCertificate.publicKey
+        guard try leafCertificatePublicKey.verifySignature(
+            Data(sig),
+            algorithm: leafCertificate.signatureAlgorithm,
+            data: verificationData) else {
+            throw AndroidKeyAttestationError.invalidVerificationData
+        }
+
+        // We need to verify that the authenticator certificate's public key matches the public key present in
+        // authenticatorData.attestedData (credentialPublicKey).
+        // We can't directly compare two public keys, so instead we verify the signature with both keys:
+        // the authenticator cert (previous step above) and credentialPublicKey (below).
+        guard let _ = try? credentialPublicKey.verify(signature: Data(sig), data: verificationData) else {
+            throw AndroidKeyAttestationError.credentialPublicKeyMismatch
+        }
+
+        var verifier = Verifier(rootCertificates: rootCertificates) {
+            AndroidKeyVerificationPolicy()
+        }
+        let verifierResult: VerificationResult = await verifier.validate(
+            leafCertificate: leafCertificate,
+            intermediates: intermediates,
+            diagnosticCallback: { result in
+                print("\n •••• \(Self.self) result=\(result)")
+            }
+        )
+        guard case .validCertificate(let chain) = verifierResult else {
+            throw AndroidKeyAttestationError.invalidTrustPath
+        }
+        
+        return chain
+    }
+}
+
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKeyVerificationPolicy.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKeyVerificationPolicy.swift
new file mode 100644
index 00000000..2593de7f
--- /dev/null
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKeyVerificationPolicy.swift
@@ -0,0 +1,33 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the WebAuthn Swift open source project
+//
+// Copyright (c) 2023 the WebAuthn Swift project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of WebAuthn Swift project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+import Foundation
+import SwiftASN1
+import X509
+
+/// Based on https://www.w3.org/TR/webauthn-2/#sctn-fido-u2f-attestation
+public struct AndroidKeyVerificationPolicy: VerifierPolicy {
+    public let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [
+        .X509ExtensionID.basicConstraints,
+        .X509ExtensionID.nameConstraints,
+        .X509ExtensionID.subjectAlternativeName,
+        .X509ExtensionID.keyUsage,
+    ]
+
+    public func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) -> PolicyEvaluationResult {
+        let leaf = chain.leaf
+        
+        return .meetsPolicy
+    }
+}
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/AttestationProtocol.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/AttestationProtocol.swift
new file mode 100644
index 00000000..f34d90f6
--- /dev/null
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/AttestationProtocol.swift
@@ -0,0 +1,27 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the WebAuthn Swift open source project
+//
+// Copyright (c) 2023 the WebAuthn Swift project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of WebAuthn Swift project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+import Foundation
+import SwiftCBOR
+import X509
+
+protocol AttestationProtocol {
+    static func verify(
+        attStmt: CBOR,
+        authenticatorData: AuthenticatorData,
+        clientDataHash: Data,
+        credentialPublicKey: CredentialPublicKey,
+        pemRootCertificates: [Data]
+    ) async throws -> [Certificate]
+}
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
index 505039fc..b1cb4220 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
@@ -16,7 +16,7 @@ import Foundation
 import SwiftCBOR
 import X509
 
-struct FidoU2FAttestation {
+struct FidoU2FAttestation: AttestationProtocol {
     enum FidoU2FAttestationError: Error {
         case invalidSig
         case invalidX5C
@@ -24,7 +24,6 @@ struct FidoU2FAttestation {
         // attestation cert can only have a ecdsaWithSHA256 signature
         case invalidLeafCertificateSigType
         case invalidAttestationKeyType
-        case missingAttestedCredential
         // Authenticator data cannot be verified
         case invalidVerificationData
     }
@@ -39,10 +38,6 @@ struct FidoU2FAttestation {
         guard let sigCBOR = attStmt["sig"], case let .byteString(sig) = sigCBOR else {
             throw FidoU2FAttestationError.invalidSig
         }
-
-        guard let attestedData = authenticatorData.attestedData else {
-            throw FidoU2FAttestationError.missingAttestedCredential
-        }
         
         guard case let .ec2(key) = credentialPublicKey, key.algorithm == .algES256 else {
             throw FidoU2FAttestationError.invalidAttestationKeyType
@@ -59,26 +54,18 @@ struct FidoU2FAttestation {
             return try Certificate(derEncoded: certificate)
         }
 
-        // U2F attestation can only have 1 certificate
-        guard x5c.count == 1 else {
-            throw FidoU2FAttestationError.invalidX5C
-        }
-
         guard let leafCertificate = x5c.first else { throw FidoU2FAttestationError.invalidX5C }
+        let intermediates = CertificateStore(x5c[1...])
         let rootCertificates = CertificateStore(
             try pemRootCertificates.map { try Certificate(derEncoded: [UInt8]($0)) }
         )
 
-        guard leafCertificate.signatureAlgorithm == .ecdsaWithSHA256 else {
-            throw FidoU2FAttestationError.invalidLeafCertificateSigType
-        }
-
         var verifier = Verifier(rootCertificates: rootCertificates) {
-            PackedVerificationPolicy()
+            FidoU2FVerificationPolicy()
         }
         let verifierResult: VerificationResult = await verifier.validate(
             leafCertificate: leafCertificate,
-            intermediates: .init()
+            intermediates: intermediates
         )
         guard case .validCertificate(let chain) = verifierResult else {
             throw FidoU2FAttestationError.invalidTrustPath
@@ -87,12 +74,13 @@ struct FidoU2FAttestation {
         // With U2F, the public key used when calculating the signature (`sig`) was encoded in ANSI X9.62 format
         let ansiPublicKey = [0x04] + key.xCoordinate + key.yCoordinate
 
-        // https://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915/fido-u2f-raw-message-formats-v1.1-id-20160915.html#registration-response-message-success
+        // https://www.w3.org/TR/webauthn-2/#sctn-fido-u2f-attestation Verification Procedure step 5.
         let verificationData = Data(
             [0x00] // A byte "reserved for future use" with the value 0x00.
             + authenticatorData.relyingPartyIDHash
             + Array(clientDataHash)
-            + attestedData.credentialID
+            // This has been verified as not nil in AttestationObject
+            + authenticatorData.attestedData!.credentialID
             + ansiPublicKey
         )
 
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FVerificationPolicy.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FVerificationPolicy.swift
new file mode 100644
index 00000000..9fefb54b
--- /dev/null
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FVerificationPolicy.swift
@@ -0,0 +1,46 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the WebAuthn Swift open source project
+//
+// Copyright (c) 2023 the WebAuthn Swift project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of WebAuthn Swift project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+import Foundation
+import SwiftASN1
+import X509
+
+/// Based on https://www.w3.org/TR/webauthn-2/#sctn-fido-u2f-attestation
+public struct FidoU2FVerificationPolicy: VerifierPolicy {
+    public let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [
+        .X509ExtensionID.basicConstraints,
+        .X509ExtensionID.nameConstraints,
+        .X509ExtensionID.subjectAlternativeName,
+        .X509ExtensionID.keyUsage,
+    ]
+
+    public func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) -> PolicyEvaluationResult {
+        
+        // Check that x5c has exactly one element
+        guard chain.count == 1 else {
+            return .failsToMeetPolicy(
+                reason: "Authenticator attestation must return exactly 1 certificate, got \(chain.count)"
+            )
+        }
+        
+        let leaf = chain.leaf
+        // Certificate public key must be an Elliptic Curve (EC) public key over the P-256 curve,
+        guard leaf.signatureAlgorithm == .ecdsaWithSHA256 else {
+            return .failsToMeetPolicy(
+                reason: "Public key must be Elliptic Curve (EC) P-256: \(leaf)"
+            )
+        }
+        return .meetsPolicy
+    }
+}
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
index 922582a9..8095dec2 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
@@ -17,14 +17,13 @@ import SwiftCBOR
 import X509
 import SwiftASN1
 
-struct PackedAttestation {
+struct PackedAttestation: AttestationProtocol {
     enum PackedAttestationError: Error {
         case invalidAlg
         case invalidSig
         case invalidX5C
         case invalidTrustPath
         case algDoesNotMatch
-        case missingAttestedCredential
         // Authenticator data cannot be verified
         case invalidVerificationData
         case invalidCertAaguid
@@ -33,7 +32,7 @@ struct PackedAttestation {
 
     static func verify(
         attStmt: CBOR,
-        authenticatorData: Data,
+        authenticatorData: AuthenticatorData,
         clientDataHash: Data,
         credentialPublicKey: CredentialPublicKey,
         pemRootCertificates: [Data]
@@ -47,7 +46,7 @@ struct PackedAttestation {
             throw PackedAttestationError.invalidSig
         }
         
-        let verificationData = authenticatorData + clientDataHash
+        let verificationData = authenticatorData.rawData + clientDataHash
 
         if let x5cCBOR = attStmt["x5c"] {
             guard case let .array(x5cCBOR) = x5cCBOR else {
@@ -61,15 +60,16 @@ struct PackedAttestation {
                 return try Certificate(derEncoded: certificate)
             }
             guard let attestnCert = x5c.first else { throw PackedAttestationError.invalidX5C }
+            if x5c.count > 1 {
+                
+            }
             let intermediates = CertificateStore(x5c[1...])
             let rootCertificates = CertificateStore(
                 try pemRootCertificates.map { try Certificate(derEncoded: [UInt8]($0)) }
             )
 
             var verifier = Verifier(rootCertificates: rootCertificates) {
-                // TODO: do we really want to validate a cert expiry for devices that cannot be updated?
-                // An expired device cert just means that the device is "old". 
-                //RFC5280Policy(validationTime: Date())
+                RFC5280Policy(validationTime: Date())
                 PackedVerificationPolicy()
             }
             let verifierResult: VerificationResult = await verifier.validate(
@@ -102,9 +102,7 @@ struct PackedAttestation {
                     throw PackedAttestationError.invalidCertAaguid
                 }
                 
-                let authenticatorData = try AuthenticatorData(bytes: Array(authenticatorData))
-                guard let attestedData = authenticatorData.attestedData,
-                      attestedData.aaguid == Array(certAaguidValue) else {
+                guard authenticatorData.attestedData?.aaguid == Array(certAaguidValue) else {
                     throw PackedAttestationError.aaguidMismatch
                 }
             }
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedVerificationPolicy.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedVerificationPolicy.swift
index 4c379f9c..548b6935 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedVerificationPolicy.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedVerificationPolicy.swift
@@ -22,10 +22,8 @@ public struct PackedVerificationPolicy: VerifierPolicy {
     public let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [
         .X509ExtensionID.basicConstraints,
         .X509ExtensionID.nameConstraints,
-        // The Subject Alternative Name extension MUST be set as defined in [TPMv2-EK-Profile] section 3.2.9.
         .X509ExtensionID.subjectAlternativeName,
         .X509ExtensionID.keyUsage,
-        .certificatePolicies,
     ]
 
     public func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) -> PolicyEvaluationResult {
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation.swift
index 2a2334c7..60b1ad48 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation.swift
@@ -17,7 +17,7 @@ import SwiftCBOR
 import X509
 import SwiftASN1
 
-struct TPMAttestation {
+struct TPMAttestation: AttestationProtocol {
     enum TPMAttestationError: Error {
         case pubAreaInvalid
         case certInfoInvalid
@@ -41,8 +41,7 @@ struct TPMAttestation {
 
     static func verify(
         attStmt: CBOR,
-        authenticatorData: Data,
-        attestedCredentialData: AttestedCredentialData,
+        authenticatorData: AuthenticatorData,
         clientDataHash: Data,
         credentialPublicKey: CredentialPublicKey,
         pemRootCertificates: [Data]
@@ -74,14 +73,12 @@ struct TPMAttestation {
         )
 
         var verifier = Verifier(rootCertificates: rootCertificates) {
+            RFC5280Policy(validationTime: Date())
             TPMVerificationPolicy()
         }
         let verifierResult: VerificationResult = await verifier.validate(
             leafCertificate: aikCert,
-            intermediates: intermediates,
-            diagnosticCallback: { result in
-                print("\n •••• Self.self result=\(result)")
-            }
+            intermediates: intermediates
         )
         guard case .validCertificate(let chain) = verifierResult else {
             throw TPMAttestationError.invalidTrustPath
@@ -97,9 +94,7 @@ struct TPMAttestation {
                 throw TPMAttestationError.invalidCertAaguid
             }
             
-            let authenticatorData = try AuthenticatorData(bytes: Array(authenticatorData))
-            guard let attestedData = authenticatorData.attestedData,
-                  attestedData.aaguid == Array(certAaguidValue) else {
+            guard authenticatorData.attestedData?.aaguid == Array(certAaguidValue) else {
                 throw TPMAttestationError.aaguidMismatch
             }
         }
@@ -153,7 +148,7 @@ struct TPMAttestation {
         }
 
         // Verify that extraData is set to the hash of attToBeSigned using the hash algorithm employed in "alg"
-        let attToBeSigned = authenticatorData + clientDataHash
+        let attToBeSigned = authenticatorData.rawData + clientDataHash
         guard alg.hashAndCompare(data: attToBeSigned, to: parsedCertInfo.extraData) else {
             throw TPMAttestationError.extraDataDoesNotMatchAttToBeSignedHash
         }
diff --git a/Sources/WebAuthn/Ceremonies/Shared/AuthenticatorData.swift b/Sources/WebAuthn/Ceremonies/Shared/AuthenticatorData.swift
index 1de8d0b4..0cac68c6 100644
--- a/Sources/WebAuthn/Ceremonies/Shared/AuthenticatorData.swift
+++ b/Sources/WebAuthn/Ceremonies/Shared/AuthenticatorData.swift
@@ -25,6 +25,7 @@ struct AuthenticatorData: Equatable {
     /// For attestation signatures this value will be set. For assertion signatures not.
     let attestedData: AttestedCredentialData?
     let extData: [UInt8]?
+    let rawData: Data
 }
 
 extension AuthenticatorData {
@@ -75,7 +76,7 @@ extension AuthenticatorData {
         self.counter = counter
         self.attestedData = attestedCredentialData
         self.extData = extensionData
-
+        self.rawData = Data(bytes)
     }
 
     /// Parse and return the attested credential data and its length.
diff --git a/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift b/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
index 1b0c2a5e..1981c529 100644
--- a/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
+++ b/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
@@ -72,7 +72,6 @@ enum CredentialPublicKey {
             throw WebAuthnError.unsupportedCOSEAlgorithm
         }
 
-        // Currently we only support elliptic curve algorithms
         switch keyType {
         case .ellipticKey:
             self = try .ec2(EC2PublicKey(publicKeyObject: publicKeyObject, algorithm: algorithm))

From c987f429555929912b048ee767475a9e37cf5f94 Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Sat, 4 May 2024 07:39:20 +1000
Subject: [PATCH 12/30] AndroidKey attestation support

---
 .../Formats/AndroidKeyAttestation.swift       |   6 +-
 .../AndroidKeyVerificationPolicy.swift        | 276 +++++++++++++++++-
 .../Formats/FidoU2FVerificationPolicy.swift   |   6 +-
 .../Formats/PackedVerificationPolicy.swift    |   6 +-
 .../Formats/PublicKey+verifySignature.swift   |   3 +
 .../Formats/TPMVerificationPolicy.swift       |   8 +-
 6 files changed, 288 insertions(+), 17 deletions(-)

diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKeyAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKeyAttestation.swift
index 02411f1e..91048528 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKeyAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKeyAttestation.swift
@@ -15,7 +15,6 @@
 import Foundation
 import SwiftCBOR
 import X509
-import SwiftASN1
 
 // https://www.w3.org/TR/webauthn-2/#sctn-android-key-attestation
 struct AndroidKeyAttestation: AttestationProtocol {
@@ -23,8 +22,9 @@ struct AndroidKeyAttestation: AttestationProtocol {
         case invalidSig
         case invalidX5C
         case invalidTrustPath
-        // Authenticator data cannot be verified
+        /// Authenticator data cannot be verified
         case invalidVerificationData
+        /// The authenticator certificate public key does not match the attested data public key
         case credentialPublicKeyMismatch
     }
 
@@ -75,7 +75,7 @@ struct AndroidKeyAttestation: AttestationProtocol {
         }
 
         var verifier = Verifier(rootCertificates: rootCertificates) {
-            AndroidKeyVerificationPolicy()
+            AndroidKeyVerificationPolicy(clientDataHash: clientDataHash)
         }
         let verifierResult: VerificationResult = await verifier.validate(
             leafCertificate: leafCertificate,
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKeyVerificationPolicy.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKeyVerificationPolicy.swift
index 2593de7f..4e4aaf66 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKeyVerificationPolicy.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKeyVerificationPolicy.swift
@@ -17,17 +17,285 @@ import SwiftASN1
 import X509
 
 /// Based on https://www.w3.org/TR/webauthn-2/#sctn-fido-u2f-attestation
-public struct AndroidKeyVerificationPolicy: VerifierPolicy {
-    public let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [
+struct AndroidKeyVerificationPolicy: VerifierPolicy {
+    let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [
         .X509ExtensionID.basicConstraints,
         .X509ExtensionID.nameConstraints,
         .X509ExtensionID.subjectAlternativeName,
         .X509ExtensionID.keyUsage,
     ]
 
-    public func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) -> PolicyEvaluationResult {
+    private let clientDataHash: [UInt8]
+    
+    init(clientDataHash: Data) {
+        self.clientDataHash = Array(clientDataHash)
+    }
+    
+    func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) -> PolicyEvaluationResult {
         let leaf = chain.leaf
-        
+
+        guard let androidExtension = leaf.extensions[oid: .androidAttestation] else {
+            return .failsToMeetPolicy(
+                reason: "Required extension \(ASN1ObjectIdentifier.androidAttestation) not present: \(leaf)"
+            )
+        }
+
+        let keyDesc: AndroidKeyDescription!
+        do {
+            keyDesc = try AndroidKeyDescription(derEncoded: androidExtension.value)
+        }
+        catch let error {
+            return .failsToMeetPolicy(
+                reason: "Error parsing KeyDescription extension (\(ASN1ObjectIdentifier.androidAttestation)): \(error): \(leaf)"
+            )
+        }
+
+        // Verify that the attestationChallenge field in the attestation certificate extension data is identical to clientDataHash.
+        guard Array(keyDesc.attestationChallenge.bytes) == clientDataHash else {
+            return .failsToMeetPolicy(
+                reason: "Challenge hash in keyDescription does not match clientDataHash: \(leaf)"
+            )
+        }
+
+        // Allow authenticator keys that were either generated in secure hardware or in software
+        guard keyDesc.softwareEnforced.origin == 0 && keyDesc.teeEnforced.origin == 0 else {
+            return .failsToMeetPolicy(
+                reason: "keyDescription says authenticator key was not hardware or software generated: \(leaf)"
+            )
+        }
+
+        // Key must be dedicated to the RP ID
+        guard keyDesc.softwareEnforced.allApplications == nil && keyDesc.teeEnforced.allApplications == nil else {
+            return .failsToMeetPolicy(
+                reason: "keyDescription says authenticator key is for all aplications: \(leaf)"
+            )
+        }
+
+        // Key must have a signing purpose
+        guard keyDesc.softwareEnforced.purpose.contains(.sign) || keyDesc.teeEnforced.purpose.contains(.sign) else {
+            return .failsToMeetPolicy(
+                reason: "keyDescription says authenticator key is not for signing: \(leaf)"
+            )
+        }
+
         return .meetsPolicy
     }
 }
+
+// https://source.android.com/docs/security/features/keystore/attestation#schema
+struct AndroidKeyDescription: DERImplicitlyTaggable {
+    internal init(
+        attestationVersion: Int,
+        attestationSecurityLevel: ASN1Any,
+        keymasterVersion: Int,
+        keymasterSecurityLevel: ASN1Any,
+        attestationChallenge: ASN1OctetString,
+        uniqueID: ASN1OctetString,
+        softwareEnforced: AuthorizationList,
+        teeEnforced: AuthorizationList
+    ) {
+        self.attestationVersion = attestationVersion
+        self.attestationSecurityLevel = attestationSecurityLevel
+        self.keymasterVersion = keymasterVersion
+        self.keymasterSecurityLevel = keymasterSecurityLevel
+        self.attestationChallenge = attestationChallenge
+        self.uniqueID = uniqueID
+        self.softwareEnforced = softwareEnforced
+        self.teeEnforced = teeEnforced
+    }
+
+    static var defaultIdentifier: ASN1Identifier {
+        .sequence
+    }
+
+    // We need these fields for verifying the attestation
+    var attestationChallenge: ASN1OctetString
+    var softwareEnforced: AuthorizationList
+    var teeEnforced: AuthorizationList
+    // We don't need or care about these fields
+    var attestationVersion: Int
+    var attestationSecurityLevel: ASN1Any
+    var keymasterVersion: Int
+    var keymasterSecurityLevel: ASN1Any
+    var uniqueID: ASN1OctetString
+
+    init(derEncoded rootNode: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
+        self = try DER.sequence(rootNode, identifier: identifier) { nodes in
+            let version = try Int(derEncoded: &nodes)
+            let secLevel = try ASN1Any(derEncoded: &nodes)
+            let kMasterVersion = try Int(derEncoded: &nodes)
+            let kMasterSecLevel = try ASN1Any(derEncoded: &nodes)
+            let challenge = try ASN1OctetString(derEncoded: &nodes)
+            let id = try ASN1OctetString(derEncoded: &nodes)
+            let softwareEnforced = try AuthorizationList(derEncoded: &nodes)
+            let teeEnforced = try AuthorizationList(derEncoded: &nodes)
+            return AndroidKeyDescription.init(
+                attestationVersion: version,
+                attestationSecurityLevel: secLevel,
+                keymasterVersion: kMasterVersion,
+                keymasterSecurityLevel: kMasterSecLevel,
+                attestationChallenge: challenge,
+                uniqueID: id,
+                softwareEnforced: softwareEnforced,
+                teeEnforced: teeEnforced
+            )
+        }
+    }
+    
+    func serialize(into coder: inout DER.Serializer, withIdentifier identifier: ASN1Identifier) throws {}
+}
+
+struct AuthorizationList: DERParseable {
+    enum KeyPurpose: Int {
+        case encrypt, decrypt, sign, verify, derive, wrap
+    }
+    init(purpose: [KeyPurpose], origin: Int, allApplications: ASN1Any?) {
+        self.purpose = purpose
+        self.origin = origin
+        self.allApplications = allApplications
+    }
+
+    // We only need these fields for verifying the attestation
+    var purpose: [KeyPurpose] = []
+    var origin: Int?
+    var allApplications: ASN1Any?
+
+    init(derEncoded rootNode: ASN1Node) throws {
+        self = try DER.sequence(rootNode, identifier: .sequence) { nodes in
+            var purpose: [KeyPurpose] = []
+            _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 1, tagClass: .contextSpecific) { node in
+                try DER.set(node, identifier: .set) { items in
+                    while let item = items.next() {
+                        if let intValue = try? Int(derEncoded: item), let currentPurpose = KeyPurpose(rawValue: intValue) {
+                            purpose.append(currentPurpose)
+                        }
+                    }
+                }
+            }
+            
+            // We don't care about these fields but must decode them
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 2, tagClass: .contextSpecific) {
+                try Int(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 3, tagClass: .contextSpecific) {
+                try Int(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 5, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 6, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 10, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 200, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 303, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 400, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 401, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 402, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 503, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 504, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 505, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 506, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 507, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 508, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 509, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+
+            let allApplications = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 600, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            
+            // We don't care about these fields but must decode them
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 601, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 701, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            
+            let origin = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 702, tagClass: .contextSpecific) {
+                try Int(derEncoded: $0)
+            }
+            
+            // We don't care about these fields but must decode them
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 703, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 704, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 705, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 706, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 709, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 709, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 709, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 710, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 711, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 712, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 713, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 714, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 715, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 716, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 717, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 718, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            let _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 719, tagClass: .contextSpecific) {
+                ASN1Any(derEncoded: $0)
+            }
+            
+            return AuthorizationList(purpose: purpose, origin: origin ?? 0, allApplications: allApplications)
+        }
+    }
+}
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FVerificationPolicy.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FVerificationPolicy.swift
index 9fefb54b..732cfb1b 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FVerificationPolicy.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FVerificationPolicy.swift
@@ -17,15 +17,15 @@ import SwiftASN1
 import X509
 
 /// Based on https://www.w3.org/TR/webauthn-2/#sctn-fido-u2f-attestation
-public struct FidoU2FVerificationPolicy: VerifierPolicy {
-    public let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [
+struct FidoU2FVerificationPolicy: VerifierPolicy {
+    let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [
         .X509ExtensionID.basicConstraints,
         .X509ExtensionID.nameConstraints,
         .X509ExtensionID.subjectAlternativeName,
         .X509ExtensionID.keyUsage,
     ]
 
-    public func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) -> PolicyEvaluationResult {
+    func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) -> PolicyEvaluationResult {
         
         // Check that x5c has exactly one element
         guard chain.count == 1 else {
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedVerificationPolicy.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedVerificationPolicy.swift
index 548b6935..7f9591cc 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedVerificationPolicy.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedVerificationPolicy.swift
@@ -18,15 +18,15 @@ import X509
 
 /// Based on https://www.w3.org/TR/webauthn-2/#sctn-packed-attestation-cert-requirements
 /// Note: we are **not** validating the certificates dates.
-public struct PackedVerificationPolicy: VerifierPolicy {
-    public let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [
+struct PackedVerificationPolicy: VerifierPolicy {
+    let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [
         .X509ExtensionID.basicConstraints,
         .X509ExtensionID.nameConstraints,
         .X509ExtensionID.subjectAlternativeName,
         .X509ExtensionID.keyUsage,
     ]
 
-    public func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) -> PolicyEvaluationResult {
+    func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) -> PolicyEvaluationResult {
         let leaf = chain.leaf
         
         // Version MUST be set to 3
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
index fc2c9707..8a94948b 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
@@ -63,4 +63,7 @@ extension SwiftASN1.ASN1ObjectIdentifier {
     static var certificatePolicies: Self {
         .init(arrayLiteral: 2, 5, 29, 32)
     }
+    static var androidAttestation: Self {
+        .init(arrayLiteral: 1, 3, 6, 1, 4, 1, 11129, 2, 1, 17)
+    }
 }
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMVerificationPolicy.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMVerificationPolicy.swift
index 9091c36c..ac7da93e 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMVerificationPolicy.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMVerificationPolicy.swift
@@ -18,8 +18,8 @@ import X509
 
 /// Based on https://www.w3.org/TR/webauthn-2/#sctn-tpm-cert-requirements
 /// Note: we are **not** validating the certificates dates.
-public struct TPMVerificationPolicy: VerifierPolicy {
-    public let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [
+struct TPMVerificationPolicy: VerifierPolicy {
+    let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [
         .X509ExtensionID.basicConstraints,
         .X509ExtensionID.nameConstraints,
         // The Subject Alternative Name extension MUST be set as defined in [TPMv2-EK-Profile] section 3.2.9.
@@ -28,7 +28,7 @@ public struct TPMVerificationPolicy: VerifierPolicy {
         .certificatePolicies,
     ]
 
-    public func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) -> PolicyEvaluationResult {
+    func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) -> PolicyEvaluationResult {
         let leaf = chain.leaf
         
         // Version MUST be set to 3
@@ -39,7 +39,7 @@ public struct TPMVerificationPolicy: VerifierPolicy {
         }
         
         // The Subject Alternative Name extension MUST be set as defined in [TPMv2-EK-Profile] section 3.2.9.
-        // Note: looks like some TPM attestation certs signed by Microsoft have nither subject nor SAN.
+        // Note: looks like some TPM attestation certs signed by Microsoft have neither subject nor SAN.
         /*guard let san = try? leaf.extensions.subjectAlternativeNames else {
             return .failsToMeetPolicy(
                 reason: "Subject Alternative Name extension MUST be set: \(leaf)"

From 760f7a6021d547b129266dd5e1aefba9f6c0f555 Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Sat, 4 May 2024 07:49:58 +1000
Subject: [PATCH 13/30] 1 folder per attestation format

---
 .../Formats/{ => AndroidKey}/AndroidKeyAttestation.swift    | 0
 .../{ => AndroidKey}/AndroidKeyVerificationPolicy.swift     | 3 ++-
 .../Formats/{ => FidoU2F}/FidoU2FAttestation.swift          | 1 +
 .../Formats/{ => FidoU2F}/FidoU2FVerificationPolicy.swift   | 0
 .../Formats/{ => Packed}/PackedAttestation.swift            | 1 +
 .../Formats/{ => Packed}/PackedVerificationPolicy.swift     | 0
 .../Formats/{ => TPM}/TPMAttestation+Structs.swift          | 0
 .../Registration/Formats/{ => TPM}/TPMAttestation.swift     | 1 +
 .../Formats/{ => TPM}/TPMVerificationPolicy.swift           | 6 +++---
 9 files changed, 8 insertions(+), 4 deletions(-)
 rename Sources/WebAuthn/Ceremonies/Registration/Formats/{ => AndroidKey}/AndroidKeyAttestation.swift (100%)
 rename Sources/WebAuthn/Ceremonies/Registration/Formats/{ => AndroidKey}/AndroidKeyVerificationPolicy.swift (98%)
 rename Sources/WebAuthn/Ceremonies/Registration/Formats/{ => FidoU2F}/FidoU2FAttestation.swift (98%)
 rename Sources/WebAuthn/Ceremonies/Registration/Formats/{ => FidoU2F}/FidoU2FVerificationPolicy.swift (100%)
 rename Sources/WebAuthn/Ceremonies/Registration/Formats/{ => Packed}/PackedAttestation.swift (98%)
 rename Sources/WebAuthn/Ceremonies/Registration/Formats/{ => Packed}/PackedVerificationPolicy.swift (100%)
 rename Sources/WebAuthn/Ceremonies/Registration/Formats/{ => TPM}/TPMAttestation+Structs.swift (100%)
 rename Sources/WebAuthn/Ceremonies/Registration/Formats/{ => TPM}/TPMAttestation.swift (99%)
 rename Sources/WebAuthn/Ceremonies/Registration/Formats/{ => TPM}/TPMVerificationPolicy.swift (92%)

diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKeyAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyAttestation.swift
similarity index 100%
rename from Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKeyAttestation.swift
rename to Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyAttestation.swift
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKeyVerificationPolicy.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyVerificationPolicy.swift
similarity index 98%
rename from Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKeyVerificationPolicy.swift
rename to Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyVerificationPolicy.swift
index 4e4aaf66..1e693744 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKeyVerificationPolicy.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyVerificationPolicy.swift
@@ -16,7 +16,7 @@ import Foundation
 import SwiftASN1
 import X509
 
-/// Based on https://www.w3.org/TR/webauthn-2/#sctn-fido-u2f-attestation
+/// Based on https://www.w3.org/TR/webauthn-2/#sctn-android-key-attestation
 struct AndroidKeyVerificationPolicy: VerifierPolicy {
     let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [
         .X509ExtensionID.basicConstraints,
@@ -34,6 +34,7 @@ struct AndroidKeyVerificationPolicy: VerifierPolicy {
     func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) -> PolicyEvaluationResult {
         let leaf = chain.leaf
 
+        // https://www.w3.org/TR/webauthn-2/#sctn-key-attstn-cert-requirements
         guard let androidExtension = leaf.extensions[oid: .androidAttestation] else {
             return .failsToMeetPolicy(
                 reason: "Required extension \(ASN1ObjectIdentifier.androidAttestation) not present: \(leaf)"
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FAttestation.swift
similarity index 98%
rename from Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
rename to Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FAttestation.swift
index b1cb4220..5e68806b 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FAttestation.swift
@@ -16,6 +16,7 @@ import Foundation
 import SwiftCBOR
 import X509
 
+// https://www.w3.org/TR/webauthn-2/#sctn-fido-u2f-attestation
 struct FidoU2FAttestation: AttestationProtocol {
     enum FidoU2FAttestationError: Error {
         case invalidSig
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FVerificationPolicy.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FVerificationPolicy.swift
similarity index 100%
rename from Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2FVerificationPolicy.swift
rename to Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FVerificationPolicy.swift
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedAttestation.swift
similarity index 98%
rename from Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
rename to Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedAttestation.swift
index 8095dec2..373a9164 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedAttestation.swift
@@ -17,6 +17,7 @@ import SwiftCBOR
 import X509
 import SwiftASN1
 
+// https://www.w3.org/TR/webauthn-2/#sctn-packed-attestation
 struct PackedAttestation: AttestationProtocol {
     enum PackedAttestationError: Error {
         case invalidAlg
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/PackedVerificationPolicy.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedVerificationPolicy.swift
similarity index 100%
rename from Sources/WebAuthn/Ceremonies/Registration/Formats/PackedVerificationPolicy.swift
rename to Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedVerificationPolicy.swift
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation+Structs.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation+Structs.swift
similarity index 100%
rename from Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation+Structs.swift
rename to Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation+Structs.swift
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
similarity index 99%
rename from Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation.swift
rename to Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
index 60b1ad48..ced0a635 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
@@ -17,6 +17,7 @@ import SwiftCBOR
 import X509
 import SwiftASN1
 
+// https://www.w3.org/TR/webauthn-2/#sctn-tpm-attestation
 struct TPMAttestation: AttestationProtocol {
     enum TPMAttestationError: Error {
         case pubAreaInvalid
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMVerificationPolicy.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMVerificationPolicy.swift
similarity index 92%
rename from Sources/WebAuthn/Ceremonies/Registration/Formats/TPMVerificationPolicy.swift
rename to Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMVerificationPolicy.swift
index ac7da93e..67cb2791 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPMVerificationPolicy.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMVerificationPolicy.swift
@@ -34,7 +34,7 @@ struct TPMVerificationPolicy: VerifierPolicy {
         // Version MUST be set to 3
         guard leaf.version == .v3 else {
             return .failsToMeetPolicy(
-                reason: "Version MUST be set to 3: \(leaf)"
+                reason: "Authenticator certificate version must be set to 3: \(leaf)"
             )
         }
         
@@ -49,14 +49,14 @@ struct TPMVerificationPolicy: VerifierPolicy {
         // The Extended Key Usage extension MUST contain the "joint-iso-itu-t(2) internationalorganizations(23) 133 tcg-kp(8) tcg-kp-AIKCertificate(3)" OID.
         guard let eku = try? leaf.extensions.extendedKeyUsage, eku.contains(.init(oid: .tcgKpAIKCertificate)) else {
             return .failsToMeetPolicy(
-                reason: "Extended Key Usage extension MUST contain the tcg-kp-AIKCertificate OID: \(leaf)"
+                reason: "Extended Key Usage extension must contain the tcg-kp-AIKCertificate OID: \(leaf)"
             )
         }
         
         // The Basic Constraints extension MUST have the CA component set to false
         guard let basic = try? leaf.extensions.basicConstraints, case .notCertificateAuthority = basic else {
             return .failsToMeetPolicy(
-                reason: "The Basic Constraints extension MUST have CA set to false: \(leaf)"
+                reason: "The Basic Constraints extension must have CA set to false: \(leaf)"
             )
         }
         return .meetsPolicy

From 124e615f2199f8d9938276d0508c71388c92cf78 Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Sat, 4 May 2024 08:55:43 +1000
Subject: [PATCH 14/30] Return attestation type

---
 .../Ceremonies/Registration/AttestationObject.swift    | 10 ++++++----
 .../Ceremonies/Registration/AttestationResult.swift    |  6 +++---
 .../Formats/AndroidKey/AndroidKeyAttestation.swift     |  4 ++--
 .../Registration/Formats/AttestationProtocol.swift     |  2 +-
 .../Formats/FidoU2F/FidoU2FAttestation.swift           |  4 ++--
 .../Formats/Packed/PackedAttestation.swift             |  6 +++---
 .../Registration/Formats/TPM/TPMAttestation.swift      |  4 ++--
 7 files changed, 19 insertions(+), 17 deletions(-)

diff --git a/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift b/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
index c9276b0c..2f92dad3 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
@@ -61,6 +61,7 @@ public struct AttestationObject {
         }
 
         let pemRootCertificates = pemRootCertificatesByFormat[format] ?? []
+        var attestationType: AttestationResult.AttestationType!
         var trustedPath: [Certificate] = []
         switch format {
         case .none:
@@ -69,7 +70,7 @@ public struct AttestationObject {
                 throw WebAuthnError.attestationStatementMustBeEmpty
             }
         case .packed:
-            trustedPath = try await PackedAttestation.verify(
+            (attestationType, trustedPath) = try await PackedAttestation.verify(
                 attStmt: attestationStatement,
                 authenticatorData: authenticatorData,
                 clientDataHash: Data(clientDataHash),
@@ -77,7 +78,7 @@ public struct AttestationObject {
                 pemRootCertificates: pemRootCertificates
             )
         case .tpm:
-            trustedPath = try await TPMAttestation.verify(
+            (attestationType, trustedPath) = try await TPMAttestation.verify(
                 attStmt: attestationStatement,
                 authenticatorData: authenticatorData,
                 clientDataHash: Data(clientDataHash),
@@ -85,7 +86,7 @@ public struct AttestationObject {
                 pemRootCertificates: pemRootCertificates
             )
         case .androidKey:
-            trustedPath = try await AndroidKeyAttestation.verify(
+            (attestationType, trustedPath) = try await AndroidKeyAttestation.verify(
                 attStmt: attestationStatement,
                 authenticatorData: authenticatorData,
                 clientDataHash: Data(clientDataHash),
@@ -94,7 +95,7 @@ public struct AttestationObject {
             )
         // Legacy format used mostly by older authenticators
         case .fidoU2F:
-            trustedPath = try await FidoU2FAttestation.verify(
+            (attestationType, trustedPath) = try await FidoU2FAttestation.verify(
                 attStmt: attestationStatement,
                 authenticatorData: authenticatorData,
                 clientDataHash: Data(clientDataHash),
@@ -107,6 +108,7 @@ public struct AttestationObject {
         
         return AttestationResult(
             format: format,
+            type: attestationType,
             trustChain: trustedPath,
             attestedCredentialData: attestedCredentialData
         )
diff --git a/Sources/WebAuthn/Ceremonies/Registration/AttestationResult.swift b/Sources/WebAuthn/Ceremonies/Registration/AttestationResult.swift
index c578f059..6d11c893 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/AttestationResult.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/AttestationResult.swift
@@ -15,7 +15,7 @@
 import X509
 
 public struct AttestationResult {
-    enum AttestationType {
+    public enum AttestationType {
         /// Attestation key pair validated by device manufacturer CA
         case basicFull
         /// Attestation signed by the public key generated during the registration
@@ -24,9 +24,9 @@ public struct AttestationResult {
         case anonCA
         case none
     }
-    //public let aaguid: [UInt8]?
+    
     public let format: AttestationFormat
-    //public let type: AttestationType
+    public let type: AttestationType
     public let trustChain: [Certificate]
     
     public let attestedCredentialData: AttestedCredentialData
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyAttestation.swift
index 91048528..e4ebc2dd 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyAttestation.swift
@@ -34,7 +34,7 @@ struct AndroidKeyAttestation: AttestationProtocol {
         clientDataHash: Data,
         credentialPublicKey: CredentialPublicKey,
         pemRootCertificates: [Data]
-    ) async throws -> [Certificate] {
+    ) async throws -> (AttestationResult.AttestationType, [Certificate]) {
         guard let sigCBOR = attStmt["sig"], case let .byteString(sig) = sigCBOR else {
             throw AndroidKeyAttestationError.invalidSig
         }
@@ -88,7 +88,7 @@ struct AndroidKeyAttestation: AttestationProtocol {
             throw AndroidKeyAttestationError.invalidTrustPath
         }
         
-        return chain
+        return (.basicFull, chain)
     }
 }
 
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/AttestationProtocol.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/AttestationProtocol.swift
index f34d90f6..87833c77 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/AttestationProtocol.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/AttestationProtocol.swift
@@ -23,5 +23,5 @@ protocol AttestationProtocol {
         clientDataHash: Data,
         credentialPublicKey: CredentialPublicKey,
         pemRootCertificates: [Data]
-    ) async throws -> [Certificate]
+    ) async throws -> (AttestationResult.AttestationType, [Certificate])
 }
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FAttestation.swift
index 5e68806b..9ff94d1f 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FAttestation.swift
@@ -35,7 +35,7 @@ struct FidoU2FAttestation: AttestationProtocol {
         clientDataHash: Data,
         credentialPublicKey: CredentialPublicKey,
         pemRootCertificates: [Data]
-    ) async throws -> [Certificate] {
+    ) async throws -> (AttestationResult.AttestationType, [Certificate]) {
         guard let sigCBOR = attStmt["sig"], case let .byteString(sig) = sigCBOR else {
             throw FidoU2FAttestationError.invalidSig
         }
@@ -94,7 +94,7 @@ struct FidoU2FAttestation: AttestationProtocol {
             throw FidoU2FAttestationError.invalidVerificationData
         }
         
-        return chain
+        return (.basicFull, chain)
     }
 }
 
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedAttestation.swift
index 373a9164..3adb74f4 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedAttestation.swift
@@ -37,7 +37,7 @@ struct PackedAttestation: AttestationProtocol {
         clientDataHash: Data,
         credentialPublicKey: CredentialPublicKey,
         pemRootCertificates: [Data]
-    ) async throws -> [Certificate] {
+    ) async throws -> (AttestationResult.AttestationType, [Certificate]) {
         guard let algCBOR = attStmt["alg"],
             case let .negativeInt(algorithmNegative) = algCBOR,
             let alg = COSEAlgorithmIdentifier(rawValue: -1 - Int(algorithmNegative)) else {
@@ -108,14 +108,14 @@ struct PackedAttestation: AttestationProtocol {
                 }
             }
             
-            return chain
+            return (.basicFull, chain)
         } else { // self attestation is in use
             guard credentialPublicKey.key.algorithm == alg else {
                 throw PackedAttestationError.algDoesNotMatch
             }
 
             try credentialPublicKey.verify(signature: Data(sig), data: verificationData)
-            return []
+            return (.`self`, [])
         }
     }
 }
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
index ced0a635..052c42ff 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
@@ -46,7 +46,7 @@ struct TPMAttestation: AttestationProtocol {
         clientDataHash: Data,
         credentialPublicKey: CredentialPublicKey,
         pemRootCertificates: [Data]
-    ) async throws -> [Certificate] {
+    ) async throws -> (AttestationResult.AttestationType, [Certificate]) {
         // Verify version
         guard let verCBOR = attStmt["ver"],
             case let .utf8String(ver) = verCBOR,
@@ -154,6 +154,6 @@ struct TPMAttestation: AttestationProtocol {
             throw TPMAttestationError.extraDataDoesNotMatchAttToBeSignedHash
         }
         
-        return chain
+        return (.attCA, chain)
     }
 }

From 1488ec0b7568d51ff4f7054855325bd636341904 Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Sat, 4 May 2024 10:02:56 +1000
Subject: [PATCH 15/30] Use X509 Certificate from swift-certificates

---
 .../Registration/AttestationObject.swift         | 16 +++++++---------
 .../AndroidKey/AndroidKeyAttestation.swift       |  8 +++-----
 .../Formats/AttestationProtocol.swift            |  2 +-
 .../Formats/FidoU2F/FidoU2FAttestation.swift     |  8 +++-----
 .../Formats/Packed/PackedAttestation.swift       |  8 +++-----
 .../Formats/TPM/TPMAttestation.swift             |  8 +++-----
 .../Registration/RegistrationCredential.swift    |  5 +++--
 Sources/WebAuthn/WebAuthnManager.swift           |  5 +++--
 8 files changed, 26 insertions(+), 34 deletions(-)

diff --git a/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift b/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
index 2f92dad3..4f5f1f5a 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
@@ -30,10 +30,8 @@ public struct AttestationObject {
         verificationRequired: Bool,
         clientDataHash: SHA256.Digest,
         supportedPublicKeyAlgorithms: [PublicKeyCredentialParameters],
-        pemRootCertificatesByFormat: [AttestationFormat: [Data]] = [:]
+        rootCertificatesByFormat: [AttestationFormat: [Certificate]] = [:]
     ) async throws -> AttestationResult {
-        // TODO: remove
-        print("\n•••••••• \(Self.self).verify(): format=\(format) ***\n")
         let relyingPartyIDHash = SHA256.hash(data: relyingPartyID.data(using: .utf8)!)
 
         guard relyingPartyIDHash == authenticatorData.relyingPartyIDHash else {
@@ -60,8 +58,8 @@ public struct AttestationObject {
             throw WebAuthnError.unsupportedCredentialPublicKeyAlgorithm
         }
 
-        let pemRootCertificates = pemRootCertificatesByFormat[format] ?? []
-        var attestationType: AttestationResult.AttestationType!
+        let rootCertificates = rootCertificatesByFormat[format] ?? []
+        var attestationType: AttestationResult.AttestationType = .none
         var trustedPath: [Certificate] = []
         switch format {
         case .none:
@@ -75,7 +73,7 @@ public struct AttestationObject {
                 authenticatorData: authenticatorData,
                 clientDataHash: Data(clientDataHash),
                 credentialPublicKey: credentialPublicKey,
-                pemRootCertificates: pemRootCertificates
+                rootCertificates: rootCertificates
             )
         case .tpm:
             (attestationType, trustedPath) = try await TPMAttestation.verify(
@@ -83,7 +81,7 @@ public struct AttestationObject {
                 authenticatorData: authenticatorData,
                 clientDataHash: Data(clientDataHash),
                 credentialPublicKey: credentialPublicKey,
-                pemRootCertificates: pemRootCertificates
+                rootCertificates: rootCertificates
             )
         case .androidKey:
             (attestationType, trustedPath) = try await AndroidKeyAttestation.verify(
@@ -91,7 +89,7 @@ public struct AttestationObject {
                 authenticatorData: authenticatorData,
                 clientDataHash: Data(clientDataHash),
                 credentialPublicKey: credentialPublicKey,
-                pemRootCertificates: pemRootCertificates
+                rootCertificates: rootCertificates
             )
         // Legacy format used mostly by older authenticators
         case .fidoU2F:
@@ -100,7 +98,7 @@ public struct AttestationObject {
                 authenticatorData: authenticatorData,
                 clientDataHash: Data(clientDataHash),
                 credentialPublicKey: credentialPublicKey,
-                pemRootCertificates: pemRootCertificates
+                rootCertificates: rootCertificates
             )
         default:
             throw WebAuthnError.attestationVerificationNotSupported
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyAttestation.swift
index e4ebc2dd..6bd2dcf1 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyAttestation.swift
@@ -33,7 +33,7 @@ struct AndroidKeyAttestation: AttestationProtocol {
         authenticatorData: AuthenticatorData,
         clientDataHash: Data,
         credentialPublicKey: CredentialPublicKey,
-        pemRootCertificates: [Data]
+        rootCertificates: [Certificate]
     ) async throws -> (AttestationResult.AttestationType, [Certificate]) {
         guard let sigCBOR = attStmt["sig"], case let .byteString(sig) = sigCBOR else {
             throw AndroidKeyAttestationError.invalidSig
@@ -52,9 +52,7 @@ struct AndroidKeyAttestation: AttestationProtocol {
 
         guard let leafCertificate = x5c.first else { throw AndroidKeyAttestationError.invalidX5C }
         let intermediates = CertificateStore(x5c[1...])
-        let rootCertificates = CertificateStore(
-            try pemRootCertificates.map { try Certificate(derEncoded: [UInt8]($0)) }
-        )
+        let rootCertificatesStore = CertificateStore(rootCertificates)
 
         let verificationData = authenticatorData.rawData + clientDataHash
         // Verify signature
@@ -74,7 +72,7 @@ struct AndroidKeyAttestation: AttestationProtocol {
             throw AndroidKeyAttestationError.credentialPublicKeyMismatch
         }
 
-        var verifier = Verifier(rootCertificates: rootCertificates) {
+        var verifier = Verifier(rootCertificates: rootCertificatesStore) {
             AndroidKeyVerificationPolicy(clientDataHash: clientDataHash)
         }
         let verifierResult: VerificationResult = await verifier.validate(
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/AttestationProtocol.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/AttestationProtocol.swift
index 87833c77..c770b4a6 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/AttestationProtocol.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/AttestationProtocol.swift
@@ -22,6 +22,6 @@ protocol AttestationProtocol {
         authenticatorData: AuthenticatorData,
         clientDataHash: Data,
         credentialPublicKey: CredentialPublicKey,
-        pemRootCertificates: [Data]
+        rootCertificates: [Certificate]
     ) async throws -> (AttestationResult.AttestationType, [Certificate])
 }
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FAttestation.swift
index 9ff94d1f..5814d35c 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FAttestation.swift
@@ -34,7 +34,7 @@ struct FidoU2FAttestation: AttestationProtocol {
         authenticatorData: AuthenticatorData,
         clientDataHash: Data,
         credentialPublicKey: CredentialPublicKey,
-        pemRootCertificates: [Data]
+        rootCertificates: [Certificate]
     ) async throws -> (AttestationResult.AttestationType, [Certificate]) {
         guard let sigCBOR = attStmt["sig"], case let .byteString(sig) = sigCBOR else {
             throw FidoU2FAttestationError.invalidSig
@@ -57,11 +57,9 @@ struct FidoU2FAttestation: AttestationProtocol {
 
         guard let leafCertificate = x5c.first else { throw FidoU2FAttestationError.invalidX5C }
         let intermediates = CertificateStore(x5c[1...])
-        let rootCertificates = CertificateStore(
-            try pemRootCertificates.map { try Certificate(derEncoded: [UInt8]($0)) }
-        )
+        let rootCertificatesStore = CertificateStore(rootCertificates)
 
-        var verifier = Verifier(rootCertificates: rootCertificates) {
+        var verifier = Verifier(rootCertificates: rootCertificatesStore) {
             FidoU2FVerificationPolicy()
         }
         let verifierResult: VerificationResult = await verifier.validate(
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedAttestation.swift
index 3adb74f4..8718434e 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedAttestation.swift
@@ -36,7 +36,7 @@ struct PackedAttestation: AttestationProtocol {
         authenticatorData: AuthenticatorData,
         clientDataHash: Data,
         credentialPublicKey: CredentialPublicKey,
-        pemRootCertificates: [Data]
+        rootCertificates: [Certificate]
     ) async throws -> (AttestationResult.AttestationType, [Certificate]) {
         guard let algCBOR = attStmt["alg"],
             case let .negativeInt(algorithmNegative) = algCBOR,
@@ -65,11 +65,9 @@ struct PackedAttestation: AttestationProtocol {
                 
             }
             let intermediates = CertificateStore(x5c[1...])
-            let rootCertificates = CertificateStore(
-                try pemRootCertificates.map { try Certificate(derEncoded: [UInt8]($0)) }
-            )
+            let rootCertificatesStore = CertificateStore(rootCertificates)
 
-            var verifier = Verifier(rootCertificates: rootCertificates) {
+            var verifier = Verifier(rootCertificates: rootCertificatesStore) {
                 RFC5280Policy(validationTime: Date())
                 PackedVerificationPolicy()
             }
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
index 052c42ff..abc53ee0 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
@@ -45,7 +45,7 @@ struct TPMAttestation: AttestationProtocol {
         authenticatorData: AuthenticatorData,
         clientDataHash: Data,
         credentialPublicKey: CredentialPublicKey,
-        pemRootCertificates: [Data]
+        rootCertificates: [Certificate]
     ) async throws -> (AttestationResult.AttestationType, [Certificate]) {
         // Verify version
         guard let verCBOR = attStmt["ver"],
@@ -69,11 +69,9 @@ struct TPMAttestation: AttestationProtocol {
         
         guard let aikCert = x5c.first else { throw TPMAttestationError.invalidX5c }
         let intermediates = CertificateStore(x5c[1...])
-        let rootCertificates = CertificateStore(
-            try pemRootCertificates.map { try Certificate(derEncoded: [UInt8]($0)) }
-        )
+        let rootCertificatesStore = CertificateStore(rootCertificates)
 
-        var verifier = Verifier(rootCertificates: rootCertificates) {
+        var verifier = Verifier(rootCertificates: rootCertificatesStore) {
             RFC5280Policy(validationTime: Date())
             TPMVerificationPolicy()
         }
diff --git a/Sources/WebAuthn/Ceremonies/Registration/RegistrationCredential.swift b/Sources/WebAuthn/Ceremonies/Registration/RegistrationCredential.swift
index 49b28770..151e8bb3 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/RegistrationCredential.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/RegistrationCredential.swift
@@ -14,6 +14,7 @@
 
 import Foundation
 import Crypto
+import X509
 
 /// The unprocessed response received from `navigator.credentials.create()`.
 ///
@@ -86,7 +87,7 @@ struct ParsedCredentialCreationResponse {
         relyingPartyID: String,
         relyingPartyOrigin: String,
         supportedPublicKeyAlgorithms: [PublicKeyCredentialParameters],
-        pemRootCertificatesByFormat: [AttestationFormat: [Data]]
+        rootCertificatesByFormat: [AttestationFormat: [Certificate]]
     ) async throws -> AttestationResult {
         // Step 7. - 9.
         try response.clientData.verify(
@@ -106,7 +107,7 @@ struct ParsedCredentialCreationResponse {
             verificationRequired: verifyUser,
             clientDataHash: hash,
             supportedPublicKeyAlgorithms: supportedPublicKeyAlgorithms,
-            pemRootCertificatesByFormat: pemRootCertificatesByFormat
+            rootCertificatesByFormat: rootCertificatesByFormat
         )
 
         // Step 23.
diff --git a/Sources/WebAuthn/WebAuthnManager.swift b/Sources/WebAuthn/WebAuthnManager.swift
index acb95e58..23bb69d5 100644
--- a/Sources/WebAuthn/WebAuthnManager.swift
+++ b/Sources/WebAuthn/WebAuthnManager.swift
@@ -13,6 +13,7 @@
 //===----------------------------------------------------------------------===//
 
 import Foundation
+import X509
 
 /// Main entrypoint for WebAuthn operations.
 ///
@@ -91,7 +92,7 @@ public struct WebAuthnManager {
         credentialCreationData: RegistrationCredential,
         requireUserVerification: Bool = false,
         supportedPublicKeyAlgorithms: [PublicKeyCredentialParameters] = .supported,
-        pemRootCertificatesByFormat: [AttestationFormat: [Data]] = [:],
+        rootCertificatesByFormat: [AttestationFormat: [Certificate]] = [:],
         confirmCredentialIDNotRegisteredYet: (String) async throws -> Bool
     ) async throws -> Credential {
         let parsedData = try ParsedCredentialCreationResponse(from: credentialCreationData)
@@ -101,7 +102,7 @@ public struct WebAuthnManager {
             relyingPartyID: configuration.relyingPartyID,
             relyingPartyOrigin: configuration.relyingPartyOrigin,
             supportedPublicKeyAlgorithms: supportedPublicKeyAlgorithms,
-            pemRootCertificatesByFormat: pemRootCertificatesByFormat
+            rootCertificatesByFormat: rootCertificatesByFormat
         )
 
         // TODO: Step 18. -> Verify client extensions

From 57d05adc418a712c6e426474917a7b0d2a6f1383 Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Sat, 4 May 2024 13:15:13 +1000
Subject: [PATCH 16/30] Throw proper WebAuthnErrors

---
 .../AndroidKey/AndroidKeyAttestation.swift    | 24 ++++-------
 .../Formats/FidoU2F/FidoU2FAttestation.swift  | 25 ++++--------
 .../Formats/Packed/PackedAttestation.swift    | 40 ++++++-------------
 .../Formats/TPM/TPMAttestation.swift          | 19 ++++-----
 Sources/WebAuthn/WebAuthnError.swift          | 26 ++++++++++++
 5 files changed, 60 insertions(+), 74 deletions(-)

diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyAttestation.swift
index 6bd2dcf1..a6000a38 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyAttestation.swift
@@ -18,16 +18,6 @@ import X509
 
 // https://www.w3.org/TR/webauthn-2/#sctn-android-key-attestation
 struct AndroidKeyAttestation: AttestationProtocol {
-    enum AndroidKeyAttestationError: Error {
-        case invalidSig
-        case invalidX5C
-        case invalidTrustPath
-        /// Authenticator data cannot be verified
-        case invalidVerificationData
-        /// The authenticator certificate public key does not match the attested data public key
-        case credentialPublicKeyMismatch
-    }
-
     static func verify(
         attStmt: CBOR,
         authenticatorData: AuthenticatorData,
@@ -36,21 +26,21 @@ struct AndroidKeyAttestation: AttestationProtocol {
         rootCertificates: [Certificate]
     ) async throws -> (AttestationResult.AttestationType, [Certificate]) {
         guard let sigCBOR = attStmt["sig"], case let .byteString(sig) = sigCBOR else {
-            throw AndroidKeyAttestationError.invalidSig
+            throw WebAuthnError.invalidSignature
         }
         
         guard let x5cCBOR = attStmt["x5c"], case let .array(x5cCBOR) = x5cCBOR else {
-                throw AndroidKeyAttestationError.invalidX5C
+            throw WebAuthnError.invalidAttestationCertificate
         }
 
         let x5c: [Certificate] = try x5cCBOR.map {
             guard case let .byteString(certificate) = $0 else {
-                throw AndroidKeyAttestationError.invalidX5C
+                throw WebAuthnError.invalidAttestationCertificate
             }
             return try Certificate(derEncoded: certificate)
         }
 
-        guard let leafCertificate = x5c.first else { throw AndroidKeyAttestationError.invalidX5C }
+        guard let leafCertificate = x5c.first else { throw WebAuthnError.invalidAttestationCertificate }
         let intermediates = CertificateStore(x5c[1...])
         let rootCertificatesStore = CertificateStore(rootCertificates)
 
@@ -61,7 +51,7 @@ struct AndroidKeyAttestation: AttestationProtocol {
             Data(sig),
             algorithm: leafCertificate.signatureAlgorithm,
             data: verificationData) else {
-            throw AndroidKeyAttestationError.invalidVerificationData
+            throw WebAuthnError.invalidVerificationData
         }
 
         // We need to verify that the authenticator certificate's public key matches the public key present in
@@ -69,7 +59,7 @@ struct AndroidKeyAttestation: AttestationProtocol {
         // We can't directly compare two public keys, so instead we verify the signature with both keys:
         // the authenticator cert (previous step above) and credentialPublicKey (below).
         guard let _ = try? credentialPublicKey.verify(signature: Data(sig), data: verificationData) else {
-            throw AndroidKeyAttestationError.credentialPublicKeyMismatch
+            throw WebAuthnError.attestationPublicKeyMismatch
         }
 
         var verifier = Verifier(rootCertificates: rootCertificatesStore) {
@@ -83,7 +73,7 @@ struct AndroidKeyAttestation: AttestationProtocol {
             }
         )
         guard case .validCertificate(let chain) = verifierResult else {
-            throw AndroidKeyAttestationError.invalidTrustPath
+            throw WebAuthnError.invalidTrustPath
         }
         
         return (.basicFull, chain)
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FAttestation.swift
index 5814d35c..16e6c8ff 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FAttestation.swift
@@ -18,17 +18,6 @@ import X509
 
 // https://www.w3.org/TR/webauthn-2/#sctn-fido-u2f-attestation
 struct FidoU2FAttestation: AttestationProtocol {
-    enum FidoU2FAttestationError: Error {
-        case invalidSig
-        case invalidX5C
-        case invalidTrustPath
-        // attestation cert can only have a ecdsaWithSHA256 signature
-        case invalidLeafCertificateSigType
-        case invalidAttestationKeyType
-        // Authenticator data cannot be verified
-        case invalidVerificationData
-    }
-
     static func verify(
         attStmt: CBOR,
         authenticatorData: AuthenticatorData,
@@ -37,25 +26,25 @@ struct FidoU2FAttestation: AttestationProtocol {
         rootCertificates: [Certificate]
     ) async throws -> (AttestationResult.AttestationType, [Certificate]) {
         guard let sigCBOR = attStmt["sig"], case let .byteString(sig) = sigCBOR else {
-            throw FidoU2FAttestationError.invalidSig
+            throw WebAuthnError.invalidSignature
         }
         
         guard case let .ec2(key) = credentialPublicKey, key.algorithm == .algES256 else {
-            throw FidoU2FAttestationError.invalidAttestationKeyType
+            throw WebAuthnError.invalidAttestationPublicKeyType
         }
 
         guard let x5cCBOR = attStmt["x5c"], case let .array(x5cCBOR) = x5cCBOR else {
-                throw FidoU2FAttestationError.invalidX5C
+                throw WebAuthnError.invalidAttestationCertificate
         }
 
         let x5c: [Certificate] = try x5cCBOR.map {
             guard case let .byteString(certificate) = $0 else {
-                throw FidoU2FAttestationError.invalidX5C
+                throw WebAuthnError.invalidAttestationCertificate
             }
             return try Certificate(derEncoded: certificate)
         }
 
-        guard let leafCertificate = x5c.first else { throw FidoU2FAttestationError.invalidX5C }
+        guard let leafCertificate = x5c.first else { throw WebAuthnError.invalidAttestationCertificate }
         let intermediates = CertificateStore(x5c[1...])
         let rootCertificatesStore = CertificateStore(rootCertificates)
 
@@ -67,7 +56,7 @@ struct FidoU2FAttestation: AttestationProtocol {
             intermediates: intermediates
         )
         guard case .validCertificate(let chain) = verifierResult else {
-            throw FidoU2FAttestationError.invalidTrustPath
+            throw WebAuthnError.invalidTrustPath
         }
 
         // With U2F, the public key used when calculating the signature (`sig`) was encoded in ANSI X9.62 format
@@ -89,7 +78,7 @@ struct FidoU2FAttestation: AttestationProtocol {
             Data(sig),
             algorithm: leafCertificate.signatureAlgorithm,
             data: verificationData) else {
-            throw FidoU2FAttestationError.invalidVerificationData
+            throw WebAuthnError.invalidVerificationData
         }
         
         return (.basicFull, chain)
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedAttestation.swift
index 8718434e..7eb81d7c 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedAttestation.swift
@@ -19,18 +19,6 @@ import SwiftASN1
 
 // https://www.w3.org/TR/webauthn-2/#sctn-packed-attestation
 struct PackedAttestation: AttestationProtocol {
-    enum PackedAttestationError: Error {
-        case invalidAlg
-        case invalidSig
-        case invalidX5C
-        case invalidTrustPath
-        case algDoesNotMatch
-        // Authenticator data cannot be verified
-        case invalidVerificationData
-        case invalidCertAaguid
-        case aaguidMismatch
-    }
-
     static func verify(
         attStmt: CBOR,
         authenticatorData: AuthenticatorData,
@@ -41,26 +29,26 @@ struct PackedAttestation: AttestationProtocol {
         guard let algCBOR = attStmt["alg"],
             case let .negativeInt(algorithmNegative) = algCBOR,
             let alg = COSEAlgorithmIdentifier(rawValue: -1 - Int(algorithmNegative)) else {
-            throw PackedAttestationError.invalidAlg
+            throw WebAuthnError.invalidAttestationSignatureAlgorithm
         }
         guard let sigCBOR = attStmt["sig"], case let .byteString(sig) = sigCBOR else {
-            throw PackedAttestationError.invalidSig
+            throw WebAuthnError.invalidSignature
         }
         
         let verificationData = authenticatorData.rawData + clientDataHash
 
         if let x5cCBOR = attStmt["x5c"] {
             guard case let .array(x5cCBOR) = x5cCBOR else {
-                throw PackedAttestationError.invalidX5C
+                throw WebAuthnError.invalidAttestationCertificate
             }
 
             let x5c: [Certificate] = try x5cCBOR.map {
                 guard case let .byteString(certificate) = $0 else {
-                    throw PackedAttestationError.invalidX5C
+                    throw WebAuthnError.invalidAttestationCertificate
                 }
                 return try Certificate(derEncoded: certificate)
             }
-            guard let attestnCert = x5c.first else { throw PackedAttestationError.invalidX5C }
+            guard let attestnCert = x5c.first else { throw WebAuthnError.invalidAttestationCertificate }
             if x5c.count > 1 {
                 
             }
@@ -76,7 +64,7 @@ struct PackedAttestation: AttestationProtocol {
                 intermediates: intermediates
             )
             guard case .validCertificate(let chain) = verifierResult else {
-                throw PackedAttestationError.invalidTrustPath
+                throw WebAuthnError.invalidTrustPath
             }
             
             // 2. Verify signature
@@ -88,7 +76,7 @@ struct PackedAttestation: AttestationProtocol {
                 Data(sig),
                 algorithm: attestnCert.signatureAlgorithm,
                 data: verificationData) else {
-                throw PackedAttestationError.invalidVerificationData
+                throw WebAuthnError.invalidVerificationData
             }
             
             // Verify that the value of the aaguid extension, if present, matches aaguid in authenticatorData
@@ -97,19 +85,17 @@ struct PackedAttestation: AttestationProtocol {
             ) {
                 // The AAGUID is wrapped in two OCTET STRINGS
                 let derValue = try DER.parse(certAAGUID.value)
-                guard case .primitive(let certAaguidValue) = derValue.content else {
-                    throw PackedAttestationError.invalidCertAaguid
-                }
-                
-                guard authenticatorData.attestedData?.aaguid == Array(certAaguidValue) else {
-                    throw PackedAttestationError.aaguidMismatch
+                guard case .primitive(let certAaguidValue) = derValue.content,
+                        authenticatorData.attestedData?.aaguid == Array(certAaguidValue) else {
+                    throw WebAuthnError.aaguidMismatch
                 }
             }
             
             return (.basicFull, chain)
-        } else { // self attestation is in use
+        }
+        else { // self attestation is in use
             guard credentialPublicKey.key.algorithm == alg else {
-                throw PackedAttestationError.algDoesNotMatch
+                throw WebAuthnError.attestationPublicKeyAlgorithmMismatch
             }
 
             try credentialPublicKey.verify(signature: Data(sig), data: verificationData)
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
index abc53ee0..2be3da91 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
@@ -22,19 +22,14 @@ struct TPMAttestation: AttestationProtocol {
     enum TPMAttestationError: Error {
         case pubAreaInvalid
         case certInfoInvalid
-        /// Invalid or unsupported attestation signature algorithm
-        case invalidAlg
         /// Unsupported TPM version
         case invalidVersion
-        case invalidX5c
         case invalidPublicKey
-        case invalidTrustPath
         case attestationCertificateSubjectNotEmpty
         case attestationCertificateMissingTcgKpAIKCertificate
-        /// A leaf (atte4station) cert must not have the CA flag set.
+        /// Leaf (attestation) cert must not have the CA flag set.
         case attestationCertificateIsCA
         case invalidCertAaguid
-        case aaguidMismatch
         case pubAreaExponentDoesNotMatchPubKeyExponent
         case invalidPubAreaCurve
         case extraDataDoesNotMatchAttToBeSignedHash
@@ -56,18 +51,18 @@ struct TPMAttestation: AttestationProtocol {
 
         guard let x5cCBOR = attStmt["x5c"],
             case let .array(x5cCBOR) = x5cCBOR else {
-                throw TPMAttestationError.invalidX5c
+                throw WebAuthnError.invalidAttestationCertificate
         }
         
         // Verify certificate chain
         let x5c: [Certificate] = try x5cCBOR.map {
             guard case let .byteString(certificate) = $0 else {
-                throw TPMAttestationError.invalidX5c
+                throw WebAuthnError.invalidAttestationCertificate
             }
             return try Certificate(derEncoded: certificate)
         }
         
-        guard let aikCert = x5c.first else { throw TPMAttestationError.invalidX5c }
+        guard let aikCert = x5c.first else { throw WebAuthnError.invalidAttestationCertificate }
         let intermediates = CertificateStore(x5c[1...])
         let rootCertificatesStore = CertificateStore(rootCertificates)
 
@@ -80,7 +75,7 @@ struct TPMAttestation: AttestationProtocol {
             intermediates: intermediates
         )
         guard case .validCertificate(let chain) = verifierResult else {
-            throw TPMAttestationError.invalidTrustPath
+            throw WebAuthnError.invalidTrustPath
         }
         
         // Verify that the value of the aaguid extension, if present, matches aaguid in authenticatorData
@@ -94,7 +89,7 @@ struct TPMAttestation: AttestationProtocol {
             }
             
             guard authenticatorData.attestedData?.aaguid == Array(certAaguidValue) else {
-                throw TPMAttestationError.aaguidMismatch
+                throw WebAuthnError.aaguidMismatch
             }
         }
 
@@ -143,7 +138,7 @@ struct TPMAttestation: AttestationProtocol {
         guard let algCBOR = attStmt["alg"],
             case let .negativeInt(algorithmNegative) = algCBOR,
             let alg = COSEAlgorithmIdentifier(rawValue: -1 - Int(algorithmNegative)) else {
-            throw TPMAttestationError.invalidAlg
+            throw WebAuthnError.invalidAttestationSignatureAlgorithm
         }
 
         // Verify that extraData is set to the hash of attToBeSigned using the hash algorithm employed in "alg"
diff --git a/Sources/WebAuthn/WebAuthnError.swift b/Sources/WebAuthn/WebAuthnError.swift
index 71d5142d..4c4b19c3 100644
--- a/Sources/WebAuthn/WebAuthnError.swift
+++ b/Sources/WebAuthn/WebAuthnError.swift
@@ -67,6 +67,16 @@ public struct WebAuthnError: Error, Hashable {
         case invalidExponent
         case unsupportedCOSEAlgorithmForRSAPublicKey
         case unsupported
+        
+        // MARK: Attestation
+        case invalidAttestationCertificate
+        case invalidTrustPath
+        case invalidAttestationSignatureAlgorithm
+        case invalidAttestationPublicKeyType
+        case invalidVerificationData
+        case attestationPublicKeyAlgorithmMismatch
+        case aaguidMismatch
+        case attestationPublicKeyMismatch
     }
     
     let reason: Reason
@@ -127,4 +137,20 @@ public struct WebAuthnError: Error, Hashable {
     public static let invalidExponent = Self(reason: .invalidExponent)
     public static let unsupportedCOSEAlgorithmForRSAPublicKey = Self(reason: .unsupportedCOSEAlgorithmForRSAPublicKey)
     public static let unsupported = Self(reason: .unsupported)
+    
+    // MARK: Attestation
+    /// Cannot read or parse attestation certificate from attestation statement
+    public static let invalidAttestationCertificate = Self(reason: .invalidAttestationCertificate)
+    /// Cannot authenticator attestation certificate trust chain up to root CA
+    public static let invalidTrustPath = Self(reason: .invalidTrustPath)
+    /// Attestation statement algorithm has invalid or unsupported COSE algorithm identifier
+    public static let invalidAttestationSignatureAlgorithm = Self(reason: .invalidAttestationSignatureAlgorithm)
+    public static let invalidAttestationPublicKeyType = Self(reason: .invalidAttestationPublicKeyType)
+    /// Authenticator verification data cannot be validated against attestation signature (authenticator data has been corrupted or tampered with?)
+    public static let invalidVerificationData = Self(reason: .invalidVerificationData)
+    public static let attestationPublicKeyAlgorithmMismatch = Self(reason: .attestationPublicKeyAlgorithmMismatch)
+    /// The authenticator certificate public key does not match the attested data public key
+    public static let attestationPublicKeyMismatch = Self(reason: .attestationPublicKeyMismatch)
+    /// Value of AAGUID in authenticator data doesn't match value in attestation certificate
+    public static let aaguidMismatch = Self(reason: .aaguidMismatch)
 }

From 60b92114c1ea539d531fcd1acac61854b7be998f Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Sun, 5 May 2024 09:59:51 +1000
Subject: [PATCH 17/30] Enable EdDSA/Ed25519 credential public keys

---
 .../Ceremonies/Shared/COSE/COSEAlgorithmIdentifier.swift   | 7 +++++--
 .../WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift   | 5 +----
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/Sources/WebAuthn/Ceremonies/Shared/COSE/COSEAlgorithmIdentifier.swift b/Sources/WebAuthn/Ceremonies/Shared/COSE/COSEAlgorithmIdentifier.swift
index eddaed08..cef64dd7 100644
--- a/Sources/WebAuthn/Ceremonies/Shared/COSE/COSEAlgorithmIdentifier.swift
+++ b/Sources/WebAuthn/Ceremonies/Shared/COSE/COSEAlgorithmIdentifier.swift
@@ -41,9 +41,10 @@ public enum COSEAlgorithmIdentifier: Int, RawRepresentable, CaseIterable, Encoda
 	/// AlgPS512 RSASSA-PSS with SHA-512
 	//case algPS512 = -39
 	// AlgEdDSA EdDSA
-	//case algEdDSA = -8
+	case algEdDSA = -8
 
-	func hashAndCompare(data: Data, to compareHash: Data) -> Bool {
+    // This is only called for TPM attestations.
+	func hashAndCompare(data: Data, to compareHash: Data) throws -> Bool {
 		switch self {
         case .algES256, .algRS256:
 			return SHA256.hash(data: data) == compareHash
@@ -53,6 +54,8 @@ public enum COSEAlgorithmIdentifier: Int, RawRepresentable, CaseIterable, Encoda
 			return SHA512.hash(data: data) == compareHash
         case .algRS1:
             return Insecure.SHA1.hash(data: data) == compareHash
+        case .algEdDSA:
+            throw WebAuthnError.unsupportedCOSEAlgorithm
 		}
 	}
 }
diff --git a/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift b/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
index 1981c529..44c81d7c 100644
--- a/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
+++ b/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
@@ -78,8 +78,7 @@ enum CredentialPublicKey {
         case .rsaKey:
             self = try .rsa(RSAPublicKeyData(publicKeyObject: publicKeyObject, algorithm: algorithm))
         case .octetKey:
-            throw WebAuthnError.unsupported
-            // self = try .okp(OKPPublicKey(publicKeyObject: publicKeyObject, algorithm: algorithm))
+            self = try .okp(OKPPublicKey(publicKeyObject: publicKeyObject, algorithm: algorithm))
         }
     }
 
@@ -185,7 +184,6 @@ struct RSAPublicKeyData: PublicKey {
     }
 
     func verify(signature: some DataProtocol, data: some DataProtocol) throws {
-        print("\n•••••• \(Self.self).verify() ")
         throw WebAuthnError.unsupported
         // let rsaSignature = _RSA.Signing.RSASignature(derRepresentation: signature)
 
@@ -209,7 +207,6 @@ struct RSAPublicKeyData: PublicKey {
     }
 }
 
-/// Currently not in use
 struct OKPPublicKey: PublicKey {
     let algorithm: COSEAlgorithmIdentifier
     let curve: UInt64

From 8d0d3c33634947ad9c78497b7b18fc96e7e4ae24 Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Sun, 5 May 2024 10:00:19 +1000
Subject: [PATCH 18/30] Throw proper WebAuthnErrors

---
 .../AndroidKeyVerificationPolicy.swift        |  2 +-
 .../FidoU2F/FidoU2FVerificationPolicy.swift   |  3 +--
 .../Packed/PackedVerificationPolicy.swift     |  7 +++----
 .../Formats/TPM/TPMAttestation.swift          | 20 ++++++-------------
 .../Formats/TPM/TPMVerificationPolicy.swift   |  6 +++---
 Sources/WebAuthn/WebAuthnError.swift          |  9 +++++++++
 6 files changed, 23 insertions(+), 24 deletions(-)

diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyVerificationPolicy.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyVerificationPolicy.swift
index 1e693744..3e09f9cf 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyVerificationPolicy.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyVerificationPolicy.swift
@@ -16,7 +16,7 @@ import Foundation
 import SwiftASN1
 import X509
 
-/// Based on https://www.w3.org/TR/webauthn-2/#sctn-android-key-attestation
+// Based on https://www.w3.org/TR/webauthn-2/#sctn-android-key-attestation
 struct AndroidKeyVerificationPolicy: VerifierPolicy {
     let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [
         .X509ExtensionID.basicConstraints,
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FVerificationPolicy.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FVerificationPolicy.swift
index 732cfb1b..38834911 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FVerificationPolicy.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FVerificationPolicy.swift
@@ -16,7 +16,7 @@ import Foundation
 import SwiftASN1
 import X509
 
-/// Based on https://www.w3.org/TR/webauthn-2/#sctn-fido-u2f-attestation
+// Based on https://www.w3.org/TR/webauthn-2/#sctn-fido-u2f-attestation
 struct FidoU2FVerificationPolicy: VerifierPolicy {
     let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [
         .X509ExtensionID.basicConstraints,
@@ -26,7 +26,6 @@ struct FidoU2FVerificationPolicy: VerifierPolicy {
     ]
 
     func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) -> PolicyEvaluationResult {
-        
         // Check that x5c has exactly one element
         guard chain.count == 1 else {
             return .failsToMeetPolicy(
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedVerificationPolicy.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedVerificationPolicy.swift
index 7f9591cc..4140be69 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedVerificationPolicy.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedVerificationPolicy.swift
@@ -16,8 +16,7 @@ import Foundation
 import SwiftASN1
 import X509
 
-/// Based on https://www.w3.org/TR/webauthn-2/#sctn-packed-attestation-cert-requirements
-/// Note: we are **not** validating the certificates dates.
+// Based on https://www.w3.org/TR/webauthn-2/#sctn-packed-attestation-cert-requirements
 struct PackedVerificationPolicy: VerifierPolicy {
     let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [
         .X509ExtensionID.basicConstraints,
@@ -32,14 +31,14 @@ struct PackedVerificationPolicy: VerifierPolicy {
         // Version MUST be set to 3
         guard leaf.version == .v3 else {
             return .failsToMeetPolicy(
-                reason: "Version MUST be set to 3: \(leaf)"
+                reason: "Version must be set to 3: \(leaf)"
             )
         }
         
         // The Basic Constraints extension MUST have the CA component set to false
         guard let basic = try? leaf.extensions.basicConstraints, case .notCertificateAuthority = basic else {
             return .failsToMeetPolicy(
-                reason: "The Basic Constraints extension MUST have CA set to false: \(leaf)"
+                reason: "The Basic Constraints extension must have CA set to false: \(leaf)"
             )
         }
         return .meetsPolicy
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
index 2be3da91..0be3a0c0 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
@@ -20,18 +20,10 @@ import SwiftASN1
 // https://www.w3.org/TR/webauthn-2/#sctn-tpm-attestation
 struct TPMAttestation: AttestationProtocol {
     enum TPMAttestationError: Error {
-        case pubAreaInvalid
         case certInfoInvalid
-        /// Unsupported TPM version
-        case invalidVersion
-        case invalidPublicKey
-        case attestationCertificateSubjectNotEmpty
         case attestationCertificateMissingTcgKpAIKCertificate
-        /// Leaf (attestation) cert must not have the CA flag set.
-        case attestationCertificateIsCA
         case invalidCertAaguid
         case pubAreaExponentDoesNotMatchPubKeyExponent
-        case invalidPubAreaCurve
         case extraDataDoesNotMatchAttToBeSignedHash
     }
 
@@ -46,7 +38,7 @@ struct TPMAttestation: AttestationProtocol {
         guard let verCBOR = attStmt["ver"],
             case let .utf8String(ver) = verCBOR,
             ver == "2.0" else {
-            throw TPMAttestationError.invalidVersion
+            throw WebAuthnError.tpmInvalidVersion
         }
 
         guard let x5cCBOR = attStmt["x5c"],
@@ -97,13 +89,13 @@ struct TPMAttestation: AttestationProtocol {
         guard let pubAreaCBOR = attStmt["pubArea"],
             case let .byteString(pubAreaRaw) = pubAreaCBOR,
             let pubArea = PubArea(from: Data(pubAreaRaw)) else {
-            throw TPMAttestationError.pubAreaInvalid
+            throw WebAuthnError.tpmInvalidPubArea
         }
         switch pubArea.parameters {
         case let .rsa(rsaParameters):
            guard case let .rsa(rsaPublicKeyData) = credentialPublicKey,
                Array(pubArea.unique.data) == rsaPublicKeyData.n else {
-               throw TPMAttestationError.invalidPublicKey
+               throw WebAuthnError.tpmInvalidPubAreaPublicKey
            }
            var pubAreaExponent: Int = rsaParameters.exponent.toInteger(endian: .big)
            if pubAreaExponent == 0 {
@@ -118,12 +110,12 @@ struct TPMAttestation: AttestationProtocol {
         case let .ecc(eccParameters):
            guard case let .ec2(ec2PublicKeyData) = credentialPublicKey,
                Array(pubArea.unique.data) == ec2PublicKeyData.rawRepresentation else {
-               throw TPMAttestationError.invalidPublicKey
+               throw WebAuthnError.tpmInvalidPubAreaPublicKey
            }
 
            guard let pubAreaCrv = COSECurve(from: eccParameters.curveID),
                pubAreaCrv == ec2PublicKeyData.curve else {
-               throw TPMAttestationError.invalidPubAreaCurve
+               throw WebAuthnError.tpmInvalidPubAreaCurve
            }
         }
         // Verify certInfo
@@ -143,7 +135,7 @@ struct TPMAttestation: AttestationProtocol {
 
         // Verify that extraData is set to the hash of attToBeSigned using the hash algorithm employed in "alg"
         let attToBeSigned = authenticatorData.rawData + clientDataHash
-        guard alg.hashAndCompare(data: attToBeSigned, to: parsedCertInfo.extraData) else {
+        guard try alg.hashAndCompare(data: attToBeSigned, to: parsedCertInfo.extraData) else {
             throw TPMAttestationError.extraDataDoesNotMatchAttToBeSignedHash
         }
         
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMVerificationPolicy.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMVerificationPolicy.swift
index 67cb2791..2307fb6a 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMVerificationPolicy.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMVerificationPolicy.swift
@@ -16,8 +16,7 @@ import Foundation
 import SwiftASN1
 import X509
 
-/// Based on https://www.w3.org/TR/webauthn-2/#sctn-tpm-cert-requirements
-/// Note: we are **not** validating the certificates dates.
+// Based on https://www.w3.org/TR/webauthn-2/#sctn-tpm-cert-requirements
 struct TPMVerificationPolicy: VerifierPolicy {
     let verifyingCriticalExtensions: [ASN1ObjectIdentifier] = [
         .X509ExtensionID.basicConstraints,
@@ -40,7 +39,8 @@ struct TPMVerificationPolicy: VerifierPolicy {
         
         // The Subject Alternative Name extension MUST be set as defined in [TPMv2-EK-Profile] section 3.2.9.
         // Note: looks like some TPM attestation certs signed by Microsoft have neither subject nor SAN.
-        /*guard let san = try? leaf.extensions.subjectAlternativeNames else {
+        // I'm unable to find sample TPM attestation payloads that actually pass this verification.
+        /*guard let _ = try? leaf.extensions.subjectAlternativeNames else {
             return .failsToMeetPolicy(
                 reason: "Subject Alternative Name extension MUST be set: \(leaf)"
             )
diff --git a/Sources/WebAuthn/WebAuthnError.swift b/Sources/WebAuthn/WebAuthnError.swift
index 4c4b19c3..b17964d4 100644
--- a/Sources/WebAuthn/WebAuthnError.swift
+++ b/Sources/WebAuthn/WebAuthnError.swift
@@ -77,6 +77,10 @@ public struct WebAuthnError: Error, Hashable {
         case attestationPublicKeyAlgorithmMismatch
         case aaguidMismatch
         case attestationPublicKeyMismatch
+        case tpmInvalidVersion
+        case tpmInvalidPubArea
+        case tpmInvalidPubAreaPublicKey
+        case tpmInvalidPubAreaCurve
     }
     
     let reason: Reason
@@ -153,4 +157,9 @@ public struct WebAuthnError: Error, Hashable {
     public static let attestationPublicKeyMismatch = Self(reason: .attestationPublicKeyMismatch)
     /// Value of AAGUID in authenticator data doesn't match value in attestation certificate
     public static let aaguidMismatch = Self(reason: .aaguidMismatch)
+    /// Invalid TPM version
+    public static let tpmInvalidVersion = Self(reason: .tpmInvalidVersion)
+    public static let tpmInvalidPubArea = Self(reason: .tpmInvalidPubArea)
+    public static let tpmInvalidPubAreaPublicKey = Self(reason: .tpmInvalidPubAreaPublicKey)
+    public static let tpmInvalidPubAreaCurve = Self(reason: .tpmInvalidPubAreaCurve)
 }

From ff94cd6d64a5881dc4eb0f36d1146b0594547cf4 Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Sun, 5 May 2024 10:16:32 +1000
Subject: [PATCH 19/30] Enable EdDSA/Ed25519 credential public keys

---
 Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift | 5 ++++-
 Tests/WebAuthnTests/WebAuthnManagerIntegrationTests.swift    | 2 +-
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift b/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
index 44c81d7c..af9b3d66 100644
--- a/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
+++ b/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
@@ -228,6 +228,9 @@ struct OKPPublicKey: PublicKey {
     }
 
     func verify(signature: some DataProtocol, data: some DataProtocol) throws {
-        throw WebAuthnError.unsupported
+        let pkey = try Curve25519.Signing.PublicKey(rawRepresentation: self.xCoordinate)
+        guard pkey.isValidSignature(signature, for: data) else {
+            throw WebAuthnError.invalidSignature
+        }
     }
 }
diff --git a/Tests/WebAuthnTests/WebAuthnManagerIntegrationTests.swift b/Tests/WebAuthnTests/WebAuthnManagerIntegrationTests.swift
index 345f4cd3..64003ff0 100644
--- a/Tests/WebAuthnTests/WebAuthnManagerIntegrationTests.swift
+++ b/Tests/WebAuthnTests/WebAuthnManagerIntegrationTests.swift
@@ -79,7 +79,7 @@ final class WebAuthnManagerIntegrationTests: XCTestCase {
             credentialCreationData: registrationResponse,
             requireUserVerification: true,
             supportedPublicKeyAlgorithms: publicKeyCredentialParameters,
-            pemRootCertificatesByFormat: [:],
+            rootCertificatesByFormat: [:],
             confirmCredentialIDNotRegisteredYet: { _ in true }
         )
 

From c63796ce41c604aecc1bd5028e01a617923d6ba2 Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Sun, 5 May 2024 10:49:15 +1000
Subject: [PATCH 20/30] Comment out public key verifySignature for RSA since
 unable to test it

---
 .../Registration/Formats/PublicKey+verifySignature.swift    | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
index 8a94948b..21cd1a05 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
@@ -41,14 +41,14 @@ extension Certificate.PublicKey {
             let signature = try P521.Signing.ECDSASignature(derRepresentation: signature)
             return key.isValidSignature(signature, for: data)
         // This hasn't been tested
-        case .sha1WithRSAEncryption, .sha256WithRSAEncryption, .sha384WithRSAEncryption, .sha512WithRSAEncryption:
+        /*case .sha1WithRSAEncryption, .sha256WithRSAEncryption, .sha384WithRSAEncryption, .sha512WithRSAEncryption:
             guard let key = _RSA.Signing.PublicKey(self) else {
                 return false
             }
             let signature = _RSA.Signing.RSASignature(rawRepresentation: signature)
-            return key.isValidSignature(signature, for: data)
+            return key.isValidSignature(signature, for: data)*/
         default: // Should we return more explicit info (signature alg not supported) in that case?
-            return false
+            throw WebAuthnError.unsupported
         }
     }
 }

From e6465cf3780c56d74bf8d0b6d62cd159d93e3fa1 Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Sun, 5 May 2024 11:53:44 +1000
Subject: [PATCH 21/30] Validate EDDSA algorithm

---
 .../Ceremonies/Shared/CredentialPublicKey.swift      | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift b/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
index af9b3d66..ef7e8b1c 100644
--- a/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
+++ b/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
@@ -228,9 +228,15 @@ struct OKPPublicKey: PublicKey {
     }
 
     func verify(signature: some DataProtocol, data: some DataProtocol) throws {
-        let pkey = try Curve25519.Signing.PublicKey(rawRepresentation: self.xCoordinate)
-        guard pkey.isValidSignature(signature, for: data) else {
-            throw WebAuthnError.invalidSignature
+        switch algorithm {
+        case .algEdDSA:
+            let pkey = try Curve25519.Signing.PublicKey(rawRepresentation: self.xCoordinate)
+            guard pkey.isValidSignature(signature, for: data) else {
+                throw WebAuthnError.invalidSignature
+            }
+        default:
+            throw WebAuthnError.unsupportedCOSEAlgorithm
         }
+        
     }
 }

From fe72a7fa7dd75e265d6011eb5e9aadc80eb5cfb3 Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Sun, 5 May 2024 11:53:57 +1000
Subject: [PATCH 22/30] Throw proper WebAuthnErrors

---
 .../Formats/TPM/TPMAttestation.swift             | 16 ++++------------
 Sources/WebAuthn/WebAuthnError.swift             |  8 ++++++++
 2 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
index 0be3a0c0..f05249b6 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
@@ -19,14 +19,6 @@ import SwiftASN1
 
 // https://www.w3.org/TR/webauthn-2/#sctn-tpm-attestation
 struct TPMAttestation: AttestationProtocol {
-    enum TPMAttestationError: Error {
-        case certInfoInvalid
-        case attestationCertificateMissingTcgKpAIKCertificate
-        case invalidCertAaguid
-        case pubAreaExponentDoesNotMatchPubKeyExponent
-        case extraDataDoesNotMatchAttToBeSignedHash
-    }
-
     static func verify(
         attStmt: CBOR,
         authenticatorData: AuthenticatorData,
@@ -77,7 +69,7 @@ struct TPMAttestation: AttestationProtocol {
             // The AAGUID is wrapped in two OCTET STRINGS
             let derValue = try DER.parse(certAAGUID.value)
             guard case .primitive(let certAaguidValue) = derValue.content else {
-                throw TPMAttestationError.invalidCertAaguid
+                throw WebAuthnError.tpmInvalidCertAaguid
             }
             
             guard authenticatorData.attestedData?.aaguid == Array(certAaguidValue) else {
@@ -105,7 +97,7 @@ struct TPMAttestation: AttestationProtocol {
 
            let pubKeyExponent: Int = rsaPublicKeyData.e.toInteger(endian: .big)
            guard pubAreaExponent == pubKeyExponent else {
-               throw TPMAttestationError.pubAreaExponentDoesNotMatchPubKeyExponent
+               throw WebAuthnError.tpmPubAreaExponentDoesNotMatchPubKeyExponent
            }
         case let .ecc(eccParameters):
            guard case let .ec2(ec2PublicKeyData) = credentialPublicKey,
@@ -122,7 +114,7 @@ struct TPMAttestation: AttestationProtocol {
         guard let certInfoCBOR = attStmt["certInfo"],
             case let .byteString(certInfo) = certInfoCBOR,
             let parsedCertInfo = CertInfo(fromBytes: Data(certInfo)) else {
-            throw TPMAttestationError.certInfoInvalid
+            throw WebAuthnError.tpmCertInfoInvalid
         }
 
         try parsedCertInfo.verify(pubArea: Data(pubAreaRaw))
@@ -136,7 +128,7 @@ struct TPMAttestation: AttestationProtocol {
         // Verify that extraData is set to the hash of attToBeSigned using the hash algorithm employed in "alg"
         let attToBeSigned = authenticatorData.rawData + clientDataHash
         guard try alg.hashAndCompare(data: attToBeSigned, to: parsedCertInfo.extraData) else {
-            throw TPMAttestationError.extraDataDoesNotMatchAttToBeSignedHash
+            throw WebAuthnError.tpmExtraDataDoesNotMatchAttToBeSignedHash
         }
         
         return (.attCA, chain)
diff --git a/Sources/WebAuthn/WebAuthnError.swift b/Sources/WebAuthn/WebAuthnError.swift
index b17964d4..603b364c 100644
--- a/Sources/WebAuthn/WebAuthnError.swift
+++ b/Sources/WebAuthn/WebAuthnError.swift
@@ -81,6 +81,10 @@ public struct WebAuthnError: Error, Hashable {
         case tpmInvalidPubArea
         case tpmInvalidPubAreaPublicKey
         case tpmInvalidPubAreaCurve
+        case tpmCertInfoInvalid
+        case tpmInvalidCertAaguid
+        case tpmPubAreaExponentDoesNotMatchPubKeyExponent
+        case tpmExtraDataDoesNotMatchAttToBeSignedHash
     }
     
     let reason: Reason
@@ -162,4 +166,8 @@ public struct WebAuthnError: Error, Hashable {
     public static let tpmInvalidPubArea = Self(reason: .tpmInvalidPubArea)
     public static let tpmInvalidPubAreaPublicKey = Self(reason: .tpmInvalidPubAreaPublicKey)
     public static let tpmInvalidPubAreaCurve = Self(reason: .tpmInvalidPubAreaCurve)
+    public static let tpmCertInfoInvalid = Self(reason: .tpmCertInfoInvalid)
+    public static let tpmInvalidCertAaguid = Self(reason: .tpmInvalidCertAaguid)
+    public static let tpmPubAreaExponentDoesNotMatchPubKeyExponent = Self(reason: .tpmPubAreaExponentDoesNotMatchPubKeyExponent)
+    public static let tpmExtraDataDoesNotMatchAttToBeSignedHash = (Self: reason: .tpmExtraDataDoesNotMatchAttToBeSignedHash)
 }

From e637a9c0279ee53278e31d538af15beb1fce6295 Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Sun, 5 May 2024 12:22:06 +1000
Subject: [PATCH 23/30] Throw proper WebAuthnErrors

---
 Sources/WebAuthn/WebAuthnError.swift | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Sources/WebAuthn/WebAuthnError.swift b/Sources/WebAuthn/WebAuthnError.swift
index 603b364c..240b7e0f 100644
--- a/Sources/WebAuthn/WebAuthnError.swift
+++ b/Sources/WebAuthn/WebAuthnError.swift
@@ -169,5 +169,5 @@ public struct WebAuthnError: Error, Hashable {
     public static let tpmCertInfoInvalid = Self(reason: .tpmCertInfoInvalid)
     public static let tpmInvalidCertAaguid = Self(reason: .tpmInvalidCertAaguid)
     public static let tpmPubAreaExponentDoesNotMatchPubKeyExponent = Self(reason: .tpmPubAreaExponentDoesNotMatchPubKeyExponent)
-    public static let tpmExtraDataDoesNotMatchAttToBeSignedHash = (Self: reason: .tpmExtraDataDoesNotMatchAttToBeSignedHash)
+    public static let tpmExtraDataDoesNotMatchAttToBeSignedHash = Self( reason: .tpmExtraDataDoesNotMatchAttToBeSignedHash)
 }

From f10b5da7a65372f4cdb9f8ba8de26548a02485bd Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Mon, 6 May 2024 07:44:31 +1000
Subject: [PATCH 24/30] Fix verifySignature

---
 .../Ceremonies/Registration/Credential.swift  |  2 +-
 .../AndroidKey/AndroidKeyAttestation.swift    | 12 +++---
 .../Formats/FidoU2F/FidoU2FAttestation.swift  |  3 +-
 .../Formats/Packed/PackedAttestation.swift    | 15 +++----
 .../Formats/PublicKey+verifySignature.swift   | 37 +++++++++++------
 .../Shared/COSE/COSEAlgorithmIdentifier.swift | 12 +++---
 .../Shared/CredentialPublicKey.swift          | 40 +++++++++----------
 .../TestModels/TestCredentialPublicKey.swift  |  4 +-
 8 files changed, 70 insertions(+), 55 deletions(-)

diff --git a/Sources/WebAuthn/Ceremonies/Registration/Credential.swift b/Sources/WebAuthn/Ceremonies/Registration/Credential.swift
index 125c7094..4ceaaffa 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Credential.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Credential.swift
@@ -40,7 +40,7 @@ public struct Credential {
     public let isBackedUp: Bool
 
     // MARK: Optional content
-    
+
     public let attestationResult: AttestationResult
 
     public let attestationClientDataJSON: CollectedClientData
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyAttestation.swift
index a6000a38..0744e492 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyAttestation.swift
@@ -25,6 +25,11 @@ struct AndroidKeyAttestation: AttestationProtocol {
         credentialPublicKey: CredentialPublicKey,
         rootCertificates: [Certificate]
     ) async throws -> (AttestationResult.AttestationType, [Certificate]) {
+        guard let algCBOR = attStmt["alg"],
+            case let .negativeInt(algorithmNegative) = algCBOR,
+            let alg = COSEAlgorithmIdentifier(rawValue: -1 - Int(algorithmNegative)) else {
+            throw WebAuthnError.invalidAttestationSignatureAlgorithm
+        }
         guard let sigCBOR = attStmt["sig"], case let .byteString(sig) = sigCBOR else {
             throw WebAuthnError.invalidSignature
         }
@@ -49,7 +54,7 @@ struct AndroidKeyAttestation: AttestationProtocol {
         let leafCertificatePublicKey: Certificate.PublicKey = leafCertificate.publicKey
         guard try leafCertificatePublicKey.verifySignature(
             Data(sig),
-            algorithm: leafCertificate.signatureAlgorithm,
+            algorithm: alg,
             data: verificationData) else {
             throw WebAuthnError.invalidVerificationData
         }
@@ -67,10 +72,7 @@ struct AndroidKeyAttestation: AttestationProtocol {
         }
         let verifierResult: VerificationResult = await verifier.validate(
             leafCertificate: leafCertificate,
-            intermediates: intermediates,
-            diagnosticCallback: { result in
-                print("\n •••• \(Self.self) result=\(result)")
-            }
+            intermediates: intermediates
         )
         guard case .validCertificate(let chain) = verifierResult else {
             throw WebAuthnError.invalidTrustPath
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FAttestation.swift
index 16e6c8ff..4ae0925b 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FAttestation.swift
@@ -49,6 +49,7 @@ struct FidoU2FAttestation: AttestationProtocol {
         let rootCertificatesStore = CertificateStore(rootCertificates)
 
         var verifier = Verifier(rootCertificates: rootCertificatesStore) {
+            RFC5280Policy(validationTime: Date())
             FidoU2FVerificationPolicy()
         }
         let verifierResult: VerificationResult = await verifier.validate(
@@ -76,7 +77,7 @@ struct FidoU2FAttestation: AttestationProtocol {
         let leafCertificatePublicKey: Certificate.PublicKey = leafCertificate.publicKey
         guard try leafCertificatePublicKey.verifySignature(
             Data(sig),
-            algorithm: leafCertificate.signatureAlgorithm,
+            algorithm: .algES256,
             data: verificationData) else {
             throw WebAuthnError.invalidVerificationData
         }
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedAttestation.swift
index 7eb81d7c..8d87faea 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedAttestation.swift
@@ -34,7 +34,7 @@ struct PackedAttestation: AttestationProtocol {
         guard let sigCBOR = attStmt["sig"], case let .byteString(sig) = sigCBOR else {
             throw WebAuthnError.invalidSignature
         }
-        
+
         let verificationData = authenticatorData.rawData + clientDataHash
 
         if let x5cCBOR = attStmt["x5c"] {
@@ -49,9 +49,7 @@ struct PackedAttestation: AttestationProtocol {
                 return try Certificate(derEncoded: certificate)
             }
             guard let attestnCert = x5c.first else { throw WebAuthnError.invalidAttestationCertificate }
-            if x5c.count > 1 {
-                
-            }
+
             let intermediates = CertificateStore(x5c[1...])
             let rootCertificatesStore = CertificateStore(rootCertificates)
 
@@ -62,11 +60,14 @@ struct PackedAttestation: AttestationProtocol {
             let verifierResult: VerificationResult = await verifier.validate(
                 leafCertificate: attestnCert,
                 intermediates: intermediates
+                /*diagnosticCallback: { result in
+                    print("\n •••• \(Self.self) result=\(result)")
+                }*/
             )
             guard case .validCertificate(let chain) = verifierResult else {
                 throw WebAuthnError.invalidTrustPath
             }
-            
+
             // 2. Verify signature
             // 2.1 Determine key type (with new Swift ASN.1/ Certificates library)
             // 2.2 Create corresponding public key object (EC2PublicKey/RSAPublicKey/OKPPublicKey)
@@ -74,11 +75,11 @@ struct PackedAttestation: AttestationProtocol {
             let leafCertificatePublicKey: Certificate.PublicKey = attestnCert.publicKey
             guard try leafCertificatePublicKey.verifySignature(
                 Data(sig),
-                algorithm: attestnCert.signatureAlgorithm,
+                algorithm: alg ,
                 data: verificationData) else {
                 throw WebAuthnError.invalidVerificationData
             }
-            
+
             // Verify that the value of the aaguid extension, if present, matches aaguid in authenticatorData
             if let certAAGUID = attestnCert.extensions.first(
                 where: {$0.oid == .idFidoGenCeAaguid}
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
index 21cd1a05..ea1d3624 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
@@ -19,35 +19,48 @@ import Crypto
 import _CryptoExtras
 
 extension Certificate.PublicKey {
-    func verifySignature(_ signature: Data, algorithm: Certificate.SignatureAlgorithm, data: Data) throws -> Bool {
+    func verifySignature(_ signature: Data, algorithm: COSEAlgorithmIdentifier, data: Data) throws -> Bool {
         switch algorithm {
-
-        case .ecdsaWithSHA256:
+        case .algES256:
             guard let key = P256.Signing.PublicKey(self) else {
                 return false
             }
             let signature = try P256.Signing.ECDSASignature(derRepresentation: signature)
             return key.isValidSignature(signature, for: data)
-        case .ecdsaWithSHA384:
+        case .algES384:
             guard let key = P384.Signing.PublicKey(self) else {
                 return false
             }
             let signature = try P384.Signing.ECDSASignature(derRepresentation: signature)
             return key.isValidSignature(signature, for: data)
-        case .ecdsaWithSHA512:
+        case .algES512:
             guard let key = P521.Signing.PublicKey(self) else {
                 return false
             }
             let signature = try P521.Signing.ECDSASignature(derRepresentation: signature)
             return key.isValidSignature(signature, for: data)
-        // This hasn't been tested
-        /*case .sha1WithRSAEncryption, .sha256WithRSAEncryption, .sha384WithRSAEncryption, .sha512WithRSAEncryption:
-            guard let key = _RSA.Signing.PublicKey(self) else {
-                return false
+        case .algPS256, .algPS384, .algPS512, .algRS1, .algRS256, .algRS384, .algRS512:
+            // We currently have no way to access the publickey `backing` so we try various possibilities
+            if let key = _RSA.Signing.PublicKey(self) {
+                let signature = _RSA.Signing.RSASignature(rawRepresentation: signature)
+                return key.isValidSignature(signature, for: data)
+            }
+            else if let key = P256.Signing.PublicKey(self) {
+                let signature = try P256.Signing.ECDSASignature(derRepresentation: signature)
+                return key.isValidSignature(signature, for: data)
+            }
+            else if let key = P384.Signing.PublicKey(self) {
+                let signature = try P384.Signing.ECDSASignature(derRepresentation: signature)
+                return key.isValidSignature(signature, for: data)
+            }
+            else if let key = P521.Signing.PublicKey(self) {
+                let signature = try P521.Signing.ECDSASignature(derRepresentation: signature)
+                return key.isValidSignature(signature, for: data)
+            }
+            else {
+                throw WebAuthnError.unsupported
             }
-            let signature = _RSA.Signing.RSASignature(rawRepresentation: signature)
-            return key.isValidSignature(signature, for: data)*/
-        default: // Should we return more explicit info (signature alg not supported) in that case?
+        default:
             throw WebAuthnError.unsupported
         }
     }
diff --git a/Sources/WebAuthn/Ceremonies/Shared/COSE/COSEAlgorithmIdentifier.swift b/Sources/WebAuthn/Ceremonies/Shared/COSE/COSEAlgorithmIdentifier.swift
index cef64dd7..04bef4ee 100644
--- a/Sources/WebAuthn/Ceremonies/Shared/COSE/COSEAlgorithmIdentifier.swift
+++ b/Sources/WebAuthn/Ceremonies/Shared/COSE/COSEAlgorithmIdentifier.swift
@@ -35,22 +35,22 @@ public enum COSEAlgorithmIdentifier: Int, RawRepresentable, CaseIterable, Encoda
 	/// AlgRS512 RSASSA-PKCS1-v1_5 with SHA-512
 	case algRS512 = -259
 	/// AlgPS256 RSASSA-PSS with SHA-256
-	//case algPS256 = -37
+	case algPS256 = -37
 	/// AlgPS384 RSASSA-PSS with SHA-384
-	//case algPS384 = -38
+	case algPS384 = -38
 	/// AlgPS512 RSASSA-PSS with SHA-512
-	//case algPS512 = -39
+	case algPS512 = -39
 	// AlgEdDSA EdDSA
 	case algEdDSA = -8
 
     // This is only called for TPM attestations.
 	func hashAndCompare(data: Data, to compareHash: Data) throws -> Bool {
 		switch self {
-        case .algES256, .algRS256:
+        case .algES256, .algRS256, .algPS256:
 			return SHA256.hash(data: data) == compareHash
-        case .algES384, .algRS384:
+        case .algES384, .algRS384, .algPS384:
 			return SHA384.hash(data: data) == compareHash
-        case .algES512, .algRS512:
+        case .algES512, .algRS512, .algPS512:
 			return SHA512.hash(data: data) == compareHash
         case .algRS1:
             return Insecure.SHA1.hash(data: data) == compareHash
diff --git a/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift b/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
index ef7e8b1c..b3eca0a6 100644
--- a/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
+++ b/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
@@ -157,7 +157,6 @@ struct EC2PublicKey: PublicKey {
     }
 }
 
-/// Currently not in use
 struct RSAPublicKeyData: PublicKey {
     let algorithm: COSEAlgorithmIdentifier
     // swiftlint:disable:next identifier_name
@@ -184,26 +183,25 @@ struct RSAPublicKeyData: PublicKey {
     }
 
     func verify(signature: some DataProtocol, data: some DataProtocol) throws {
-        throw WebAuthnError.unsupported
-        // let rsaSignature = _RSA.Signing.RSASignature(derRepresentation: signature)
-
-        // var rsaPadding: _RSA.Signing.Padding
-        // switch algorithm {
-        // case .algRS1, .algRS256, .algRS384, .algRS512:
-        //     rsaPadding = .insecurePKCS1v1_5
-        // case .algPS256, .algPS384, .algPS512:
-        //     rsaPadding = .PSS
-        // default:
-        //     throw WebAuthnError.unsupportedCOSEAlgorithmForRSAPublicKey
-        // }
-
-        // guard try _RSA.Signing.PublicKey(rawRepresentation: rawRepresentation).isValidSignature(
-        //     rsaSignature,
-        //     for: data,
-        //     padding: rsaPadding
-        // ) else {
-        //     throw WebAuthnError.invalidSignature
-        // }
+        let rsaSignature = _RSA.Signing.RSASignature(rawRepresentation: signature)
+
+        var rsaPadding: _RSA.Signing.Padding
+        switch algorithm {
+        case .algRS1, .algRS256, .algRS384, .algRS512:
+            rsaPadding = .insecurePKCS1v1_5
+        case .algPS256, .algPS384, .algPS512:
+            rsaPadding = .PSS
+        default:
+            throw WebAuthnError.unsupportedCOSEAlgorithmForRSAPublicKey
+        }
+
+        guard try _RSA.Signing.PublicKey(derRepresentation: rawRepresentation).isValidSignature(
+            rsaSignature,
+            for: data,
+            padding: rsaPadding
+        ) else {
+            throw WebAuthnError.invalidSignature
+        }
     }
 }
 
diff --git a/Tests/WebAuthnTests/Utils/TestModels/TestCredentialPublicKey.swift b/Tests/WebAuthnTests/Utils/TestModels/TestCredentialPublicKey.swift
index 91599228..13ac7c3e 100644
--- a/Tests/WebAuthnTests/Utils/TestModels/TestCredentialPublicKey.swift
+++ b/Tests/WebAuthnTests/Utils/TestModels/TestCredentialPublicKey.swift
@@ -60,7 +60,7 @@ struct TestCredentialPublicKeyBuilder {
             .crv(.p256)
             .alg(.algES256)
             .xCoordinate(TestECCKeyPair.publicKeyXCoordinate)
-            .yCoordiante(TestECCKeyPair.publicKeyYCoordinate)
+            .yCoordinate(TestECCKeyPair.publicKeyYCoordinate)
     }
 
     func kty(_ kty: COSEKeyType) -> Self {
@@ -87,7 +87,7 @@ struct TestCredentialPublicKeyBuilder {
         return temp
     }
 
-    func yCoordiante(_ yCoordinate: [UInt8]) -> Self {
+    func yCoordinate(_ yCoordinate: [UInt8]) -> Self {
         var temp = self
         temp.wrapped.yCoordinate = .byteString(yCoordinate)
         return temp

From d2c83ebae45d184e7d58b02f2d645176d6523d2e Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Fri, 10 May 2024 08:12:18 +1000
Subject: [PATCH 25/30] WIP tests

---
 .../Registration/AttestationObject.swift      |   1 +
 .../Formats/FidoU2F/FidoU2FAttestation.swift  |   8 +-
 .../FidoU2F/FidoU2FVerificationPolicy.swift   |   7 -
 .../Formats/Packed/PackedAttestation.swift    |  13 +-
 .../Formats/PublicKey+verifySignature.swift   |  35 ++-
 .../Formats/TPM/TPMAttestation.swift          |  55 ++--
 .../Shared/CredentialPublicKey.swift          |  25 +-
 .../Formats/FidoU2FAttestationTests.swift     | 168 +++++++++++
 .../Formats/PackedAttestationTests.swift      | 272 ++++++++++++++++++
 .../Formats/TPMAttestationTests.swift         | 110 +++++++
 .../TPMAttestationTests/CertInfoTests.swift   |   1 +
 .../TestModels/TestAttestationObject.swift    |   6 +
 .../Utils/TestModels/TestAuthData.swift       |   3 +-
 .../TestModels/TestCredentialPublicKey.swift  |  37 +++
 .../Utils/TestModels/TestECCKeyPair.swift     |  49 ++++
 .../Utils/TestModels/TestRSAKeyPair.swift     | 129 +++++++++
 16 files changed, 860 insertions(+), 59 deletions(-)
 create mode 100644 Tests/WebAuthnTests/Formats/FidoU2FAttestationTests.swift
 create mode 100644 Tests/WebAuthnTests/Formats/PackedAttestationTests.swift
 create mode 100644 Tests/WebAuthnTests/Formats/TPMAttestationTests.swift
 create mode 100644 Tests/WebAuthnTests/Utils/TestModels/TestRSAKeyPair.swift

diff --git a/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift b/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
index 4f5f1f5a..2b5279ac 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
@@ -61,6 +61,7 @@ public struct AttestationObject {
         let rootCertificates = rootCertificatesByFormat[format] ?? []
         var attestationType: AttestationResult.AttestationType = .none
         var trustedPath: [Certificate] = []
+        print("\n •••• \(Self.self).verify() format=\(format)")
         switch format {
         case .none:
             // if format is `none` statement must be empty
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FAttestation.swift
index 4ae0925b..5959e5c3 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FAttestation.swift
@@ -44,8 +44,12 @@ struct FidoU2FAttestation: AttestationProtocol {
             return try Certificate(derEncoded: certificate)
         }
 
+        // Check that x5c has exactly one element
+        guard x5c.count == 1 else {
+            throw WebAuthnError.invalidTrustPath
+        }
+        
         guard let leafCertificate = x5c.first else { throw WebAuthnError.invalidAttestationCertificate }
-        let intermediates = CertificateStore(x5c[1...])
         let rootCertificatesStore = CertificateStore(rootCertificates)
 
         var verifier = Verifier(rootCertificates: rootCertificatesStore) {
@@ -54,7 +58,7 @@ struct FidoU2FAttestation: AttestationProtocol {
         }
         let verifierResult: VerificationResult = await verifier.validate(
             leafCertificate: leafCertificate,
-            intermediates: intermediates
+            intermediates: .init()
         )
         guard case .validCertificate(let chain) = verifierResult else {
             throw WebAuthnError.invalidTrustPath
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FVerificationPolicy.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FVerificationPolicy.swift
index 38834911..197a3cd8 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FVerificationPolicy.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/FidoU2F/FidoU2FVerificationPolicy.swift
@@ -26,13 +26,6 @@ struct FidoU2FVerificationPolicy: VerifierPolicy {
     ]
 
     func chainMeetsPolicyRequirements(chain: UnverifiedCertificateChain) -> PolicyEvaluationResult {
-        // Check that x5c has exactly one element
-        guard chain.count == 1 else {
-            return .failsToMeetPolicy(
-                reason: "Authenticator attestation must return exactly 1 certificate, got \(chain.count)"
-            )
-        }
-        
         let leaf = chain.leaf
         // Certificate public key must be an Elliptic Curve (EC) public key over the P-256 curve,
         guard leaf.signatureAlgorithm == .ecdsaWithSHA256 else {
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedAttestation.swift
index 8d87faea..ff29f18d 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/Packed/PackedAttestation.swift
@@ -60,22 +60,16 @@ struct PackedAttestation: AttestationProtocol {
             let verifierResult: VerificationResult = await verifier.validate(
                 leafCertificate: attestnCert,
                 intermediates: intermediates
-                /*diagnosticCallback: { result in
-                    print("\n •••• \(Self.self) result=\(result)")
-                }*/
             )
             guard case .validCertificate(let chain) = verifierResult else {
                 throw WebAuthnError.invalidTrustPath
             }
 
             // 2. Verify signature
-            // 2.1 Determine key type (with new Swift ASN.1/ Certificates library)
-            // 2.2 Create corresponding public key object (EC2PublicKey/RSAPublicKey/OKPPublicKey)
-            // 2.3 Call verify method on public key with signature + data
             let leafCertificatePublicKey: Certificate.PublicKey = attestnCert.publicKey
             guard try leafCertificatePublicKey.verifySignature(
                 Data(sig),
-                algorithm: alg ,
+                algorithm: alg,
                 data: verificationData) else {
                 throw WebAuthnError.invalidVerificationData
             }
@@ -99,7 +93,10 @@ struct PackedAttestation: AttestationProtocol {
                 throw WebAuthnError.attestationPublicKeyAlgorithmMismatch
             }
 
-            try credentialPublicKey.verify(signature: Data(sig), data: verificationData)
+            guard (try? credentialPublicKey.verify(signature: Data(sig), data: verificationData)) != nil else {
+                throw WebAuthnError.invalidVerificationData
+            }
+            
             return (.`self`, [])
         }
     }
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
index ea1d3624..f8a50ee2 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
@@ -20,6 +20,7 @@ import _CryptoExtras
 
 extension Certificate.PublicKey {
     func verifySignature(_ signature: Data, algorithm: COSEAlgorithmIdentifier, data: Data) throws -> Bool {
+        print("\n •••• \(Self.self).verifySignature() 1, algorithm=\(algorithm)")
         switch algorithm {
         case .algES256:
             guard let key = P256.Signing.PublicKey(self) else {
@@ -27,39 +28,35 @@ extension Certificate.PublicKey {
             }
             let signature = try P256.Signing.ECDSASignature(derRepresentation: signature)
             return key.isValidSignature(signature, for: data)
+            
         case .algES384:
             guard let key = P384.Signing.PublicKey(self) else {
                 return false
             }
             let signature = try P384.Signing.ECDSASignature(derRepresentation: signature)
             return key.isValidSignature(signature, for: data)
+            
         case .algES512:
             guard let key = P521.Signing.PublicKey(self) else {
                 return false
             }
             let signature = try P521.Signing.ECDSASignature(derRepresentation: signature)
             return key.isValidSignature(signature, for: data)
-        case .algPS256, .algPS384, .algPS512, .algRS1, .algRS256, .algRS384, .algRS512:
-            // We currently have no way to access the publickey `backing` so we try various possibilities
-            if let key = _RSA.Signing.PublicKey(self) {
-                let signature = _RSA.Signing.RSASignature(rawRepresentation: signature)
-                return key.isValidSignature(signature, for: data)
-            }
-            else if let key = P256.Signing.PublicKey(self) {
-                let signature = try P256.Signing.ECDSASignature(derRepresentation: signature)
-                return key.isValidSignature(signature, for: data)
-            }
-            else if let key = P384.Signing.PublicKey(self) {
-                let signature = try P384.Signing.ECDSASignature(derRepresentation: signature)
-                return key.isValidSignature(signature, for: data)
-            }
-            else if let key = P521.Signing.PublicKey(self) {
-                let signature = try P521.Signing.ECDSASignature(derRepresentation: signature)
-                return key.isValidSignature(signature, for: data)
+            
+        case .algRS1, .algRS256, .algRS384, .algRS512:
+            guard let key = _RSA.Signing.PublicKey(self) else {
+                return false
             }
-            else {
-                throw WebAuthnError.unsupported
+            let signature = _RSA.Signing.RSASignature(rawRepresentation: signature)
+            return key.isValidSignature(signature, for: data, padding: .insecurePKCS1v1_5)
+            
+        case .algPS256, .algPS384, .algPS512:
+            guard let key = _RSA.Signing.PublicKey(self) else {
+                return false
             }
+            let signature = _RSA.Signing.RSASignature(rawRepresentation: signature)
+            return key.isValidSignature(signature, for: data, padding: .PSS)
+            
         default:
             throw WebAuthnError.unsupported
         }
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
index f05249b6..5c86382d 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
@@ -77,6 +77,10 @@ struct TPMAttestation: AttestationProtocol {
             }
         }
 
+        if let pubAreaCBOR = attStmt["pubArea"], case let .byteString(pubAreaRaw) = pubAreaCBOR {
+            let pubArea = PubArea(from: Data(pubAreaRaw))
+            print("\n••• \(Self.self) pubAreaRaw64=\(Data(pubAreaRaw).base64EncodedString())\npubArea=\(pubArea!)")
+        }
         // Verify pubArea
         guard let pubAreaCBOR = attStmt["pubArea"],
             case let .byteString(pubAreaRaw) = pubAreaCBOR,
@@ -85,31 +89,40 @@ struct TPMAttestation: AttestationProtocol {
         }
         switch pubArea.parameters {
         case let .rsa(rsaParameters):
-           guard case let .rsa(rsaPublicKeyData) = credentialPublicKey,
-               Array(pubArea.unique.data) == rsaPublicKeyData.n else {
-               throw WebAuthnError.tpmInvalidPubAreaPublicKey
-           }
-           var pubAreaExponent: Int = rsaParameters.exponent.toInteger(endian: .big)
-           if pubAreaExponent == 0 {
-               // "When zero, indicates that the exponent is the default of 2^16 + 1"
-               pubAreaExponent = 65537
-           }
+            if case let .rsa(rsaPublicKeyData) = credentialPublicKey {
+                print("\n •••• \(Self.self) pubArea.unique.data=\(Array(pubArea.unique.data)), rsaPublicKeyData.n=\(rsaPublicKeyData.n)")
+            }
+            guard case let .rsa(rsaPublicKeyData) = credentialPublicKey,
+                Array(pubArea.unique.data) == rsaPublicKeyData.n else {
+                throw WebAuthnError.tpmInvalidPubAreaPublicKey
+            }
+            var pubAreaExponent: Int = rsaParameters.exponent.toInteger(endian: .big)
+            if pubAreaExponent == 0 {
+                // "When zero, indicates that the exponent is the default of 2^16 + 1"
+                pubAreaExponent = 65537
+            }
 
-           let pubKeyExponent: Int = rsaPublicKeyData.e.toInteger(endian: .big)
-           guard pubAreaExponent == pubKeyExponent else {
-               throw WebAuthnError.tpmPubAreaExponentDoesNotMatchPubKeyExponent
-           }
+            let pubKeyExponent: Int = rsaPublicKeyData.e.toInteger(endian: .big)
+            guard pubAreaExponent == pubKeyExponent else {
+                throw WebAuthnError.tpmPubAreaExponentDoesNotMatchPubKeyExponent
+            }
         case let .ecc(eccParameters):
-           guard case let .ec2(ec2PublicKeyData) = credentialPublicKey,
-               Array(pubArea.unique.data) == ec2PublicKeyData.rawRepresentation else {
-               throw WebAuthnError.tpmInvalidPubAreaPublicKey
-           }
+            guard case let .ec2(ec2PublicKeyData) = credentialPublicKey,
+                Array(pubArea.unique.data) == ec2PublicKeyData.rawRepresentation else {
+                throw WebAuthnError.tpmInvalidPubAreaPublicKey
+            }
 
-           guard let pubAreaCrv = COSECurve(from: eccParameters.curveID),
-               pubAreaCrv == ec2PublicKeyData.curve else {
-               throw WebAuthnError.tpmInvalidPubAreaCurve
-           }
+            guard let pubAreaCrv = COSECurve(from: eccParameters.curveID),
+                pubAreaCrv == ec2PublicKeyData.curve else {
+                throw WebAuthnError.tpmInvalidPubAreaCurve
+            }
         }
+        
+        /*if let certInfoCBOR = attStmt["certInfo"],
+           case let .byteString(certInfo) = certInfoCBOR {
+            let parsedCertInfo = CertInfo(fromBytes: Data(certInfo))
+            print("\n••• \(Self.self) certInfo64=\(Data(certInfo).base64EncodedString())\nparsedCertInfo=\(parsedCertInfo!)")
+        }*/
         // Verify certInfo
         guard let certInfoCBOR = attStmt["certInfo"],
             case let .byteString(certInfo) = certInfoCBOR,
diff --git a/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift b/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
index b3eca0a6..9c144715 100644
--- a/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
+++ b/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
@@ -16,6 +16,7 @@ import Crypto
 import _CryptoExtras
 import Foundation
 import SwiftCBOR
+import SwiftASN1
 
 protocol PublicKey {
     var algorithm: COSEAlgorithmIdentifier { get }
@@ -72,6 +73,7 @@ enum CredentialPublicKey {
             throw WebAuthnError.unsupportedCOSEAlgorithm
         }
 
+        print("\n•••• \(Self.self).init() keyType=\(keyType), algorithm=\(algorithm)")
         switch keyType {
         case .ellipticKey:
             self = try .ec2(EC2PublicKey(publicKeyObject: publicKeyObject, algorithm: algorithm))
@@ -182,6 +184,24 @@ struct RSAPublicKeyData: PublicKey {
         e = eBytes
     }
 
+    // We receive a "raw" public key but the RSA PublicKey constructor requires a DER-encoded value
+    private struct RSAPublicKeyDER: DERSerializable {
+        var n: ArraySlice<UInt8>
+        var e: ArraySlice<UInt8>
+
+        init(n: [UInt8], e: [UInt8]) {
+            self.n = ArraySlice(n)
+            self.e = ArraySlice(e)
+        }
+
+        func serialize(into coder: inout SwiftASN1.DER.Serializer) throws {
+            try coder.appendConstructedNode(identifier: .sequence) { coder in
+                try coder.serialize(self.n)
+                try coder.serialize(self.e)
+            }
+        }
+    }
+    
     func verify(signature: some DataProtocol, data: some DataProtocol) throws {
         let rsaSignature = _RSA.Signing.RSASignature(rawRepresentation: signature)
 
@@ -195,7 +215,10 @@ struct RSAPublicKeyData: PublicKey {
             throw WebAuthnError.unsupportedCOSEAlgorithmForRSAPublicKey
         }
 
-        guard try _RSA.Signing.PublicKey(derRepresentation: rawRepresentation).isValidSignature(
+        var serializer = DER.Serializer()
+        let keyDER = RSAPublicKeyDER(n: self.n, e: self.e)
+        try serializer.serialize(keyDER)
+        guard try _RSA.Signing.PublicKey(derRepresentation: serializer.serializedBytes).isValidSignature(
             rsaSignature,
             for: data,
             padding: rsaPadding
diff --git a/Tests/WebAuthnTests/Formats/FidoU2FAttestationTests.swift b/Tests/WebAuthnTests/Formats/FidoU2FAttestationTests.swift
new file mode 100644
index 00000000..9a2bca59
--- /dev/null
+++ b/Tests/WebAuthnTests/Formats/FidoU2FAttestationTests.swift
@@ -0,0 +1,168 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the WebAuthn Swift open source project
+//
+// Copyright (c) 2023 the WebAuthn Swift project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of WebAuthn Swift project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+@testable import WebAuthn
+import XCTest
+import SwiftCBOR
+import Crypto
+import X509
+
+// swiftlint:disable:next type_body_length
+final class RegistrationFidoU2FAttestationTests: XCTestCase {
+    var webAuthnManager: WebAuthnManager!
+
+    let challenge: [UInt8] = [1, 0, 1]
+    let relyingPartyDisplayName = "Testy test"
+    let relyingPartyID = "example.com"
+    let relyingPartyOrigin = "https://example.com"
+    static let credentialId = "e0fac9350509f71748d83782ccaf6b4c1462c615c70e255da1344e40887c8fcd".hexadecimal!
+    let mockClientDataJSONBytes = TestClientDataJSON(challenge: TestConstants.mockChallenge.base64URLEncodedString()).jsonBytes
+
+    override func setUp() {
+        let configuration = WebAuthnManager.Configuration(
+            relyingPartyID: relyingPartyID,
+            relyingPartyName: relyingPartyDisplayName,
+            relyingPartyOrigin: relyingPartyOrigin
+        )
+        webAuthnManager = .init(configuration: configuration, challengeGenerator: .mock(generate: challenge))
+    }
+
+    func testAttestationInvalidVerifData() async throws {
+        let authData = TestAuthDataBuilder().validMock()
+        // invalid verification data
+        let verificationData: [UInt8] = [0x00, 0x01]
+        let mockCerts = try TestECCKeyPair.certificates()
+        
+        let mockAttestationObject = TestAttestationObjectBuilder()
+            .fmt(.fidoU2F)
+            .authData(authData)
+            .attStmt(
+                .map([
+                    .utf8String("sig"): .byteString(Array(
+                        try TestECCKeyPair.signature(data: Data(verificationData))
+                            .derRepresentation
+                    )),
+                    .utf8String("x5c"): .array([.byteString(Array(mockCerts.leaf))])
+                ])
+            )
+            .build()
+            .cborEncoded
+
+        await assertThrowsError(
+            try await finishRegistration(
+                attestationObject: mockAttestationObject,
+                rootCertificatesByFormat: [.fidoU2F: [mockCerts.ca]]
+            ),
+            expect: WebAuthnError.invalidVerificationData
+        )
+    }
+    
+    func testAttestationMissingx5c() async throws {
+        let authData = TestAuthDataBuilder().validMock()
+        
+        let mockAttestationObject = TestAttestationObjectBuilder()
+            .fmt(.fidoU2F)
+            .authData(authData)
+            .attStmt(
+                .map([
+                    .utf8String("sig"): .byteString(Array(
+                        try TestECCKeyPair.signature(data: Data([0x00, 0x01]))
+                            .derRepresentation
+                    )),
+                ])
+            )
+            .build()
+            .cborEncoded
+
+        await assertThrowsError(
+            try await finishRegistration(
+                attestationObject: mockAttestationObject,
+                rootCertificatesByFormat: [:]
+            ),
+            expect: WebAuthnError.invalidAttestationCertificate
+        )
+    }
+
+    func testBasicAttestationSucceeds() async throws {
+        let mockCerts = try TestECCKeyPair.certificates()
+        let credentialId: [UInt8] = [0b00000001]
+        let authData = TestAuthDataBuilder()
+            .relyingPartyIDHash(fromRelyingPartyID: "example.com")
+            .flags(0b11000101)
+            .counter([0b00000000, 0b00000000, 0b00000000, 0b00000000])
+            .attestedCredData(
+                aaguid: [UInt8](repeating: 0, count: 16),
+                credentialIDLength: [0b00000000, 0b00000001],
+                credentialID: credentialId,
+                credentialPublicKey: TestCredentialPublicKeyBuilder().validMock().buildAsByteArray()
+            )
+            .extensions([UInt8](repeating: 0, count: 20))
+       
+        let rpIdHash = SHA256.hash(data: Data(self.relyingPartyID.utf8))
+        let clientDataHash = SHA256.hash(data: mockClientDataJSONBytes)
+        // With U2F, the public key used when calculating the signature (`sig`) is encoded in ANSI X9.62 format
+        let publicKeyU2F: [UInt8] = [0x04] + TestECCKeyPair.publicKeyXCoordinate + TestECCKeyPair.publicKeyYCoordinate
+        // Let verificationData be the concatenation of (0x00 || rpIdHash || clientDataHash || credentialId || publicKeyU2F)
+        let verificationData: [UInt8] = [0x00] + rpIdHash + clientDataHash + credentialId + publicKeyU2F
+        
+        let mockAttestationObject = TestAttestationObjectBuilder()
+            .fmt(.fidoU2F)
+            .authData(authData)
+            .attStmt(
+                .map([
+                    .utf8String("sig"): .byteString(Array(
+                        try TestECCKeyPair.signature(data: Data(verificationData))
+                            .derRepresentation
+                    )),
+                    .utf8String("x5c"): .array([.byteString(Array(mockCerts.leaf))])
+                ])
+            )
+            .build()
+            .cborEncoded
+
+        let credential = try await finishRegistration(
+            attestationObject: mockAttestationObject,
+            rootCertificatesByFormat: [.fidoU2F: [mockCerts.ca]]
+        )
+        XCTAssertEqual(credential.attestationResult.format, .fidoU2F)
+        XCTAssertEqual(credential.attestationResult.type, .basicFull)
+        XCTAssertEqual(credential.attestationResult.trustChain.count, 2)
+    }
+
+    private func finishRegistration(
+        challenge: [UInt8] = TestConstants.mockChallenge,
+        type: CredentialType = .publicKey,
+        rawID: [UInt8] = credentialId,
+        attestationObject: [UInt8],
+        requireUserVerification: Bool = false,
+        rootCertificatesByFormat: [AttestationFormat: [Certificate]] = [:],
+        confirmCredentialIDNotRegisteredYet: (String) async throws -> Bool = { _ in true }
+    ) async throws -> Credential {
+        try await webAuthnManager.finishRegistration(
+            challenge: challenge,
+            credentialCreationData: RegistrationCredential(
+                id: rawID.base64URLEncodedString(),
+                type: type,
+                rawID: rawID,
+                attestationResponse: AuthenticatorAttestationResponse(
+                    clientDataJSON: mockClientDataJSONBytes,
+                    attestationObject: attestationObject
+                )
+            ),
+            requireUserVerification: requireUserVerification,
+            rootCertificatesByFormat: rootCertificatesByFormat,
+            confirmCredentialIDNotRegisteredYet: confirmCredentialIDNotRegisteredYet
+        )
+    }
+}
diff --git a/Tests/WebAuthnTests/Formats/PackedAttestationTests.swift b/Tests/WebAuthnTests/Formats/PackedAttestationTests.swift
new file mode 100644
index 00000000..e0407a77
--- /dev/null
+++ b/Tests/WebAuthnTests/Formats/PackedAttestationTests.swift
@@ -0,0 +1,272 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the WebAuthn Swift open source project
+//
+// Copyright (c) 2023 the WebAuthn Swift project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of WebAuthn Swift project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+@testable import WebAuthn
+import XCTest
+import SwiftCBOR
+import Crypto
+import _CryptoExtras
+import X509
+
+// swiftlint:disable:next type_body_length
+final class RegistrationPackedAttestationTests: XCTestCase {
+    var webAuthnManager: WebAuthnManager!
+    var authDataECC: TestAuthDataBuilder!
+    var authDataRSA: TestAuthDataBuilder!
+    var clientDataHash: SHA256.Digest!
+    
+    let challenge: [UInt8] = [1, 0, 1]
+    let relyingPartyDisplayName = "Testy test"
+    let relyingPartyID = "example.com"
+    let relyingPartyOrigin = "https://example.com"
+    let mockClientDataJSONBytes = TestClientDataJSON(challenge: TestConstants.mockChallenge.base64URLEncodedString()).jsonBytes
+
+    override func setUp() {
+        let configuration = WebAuthnManager.Configuration(
+            relyingPartyID: relyingPartyID,
+            relyingPartyName: relyingPartyDisplayName,
+            relyingPartyOrigin: relyingPartyOrigin
+        )
+        webAuthnManager = .init(configuration: configuration, challengeGenerator: .mock(generate: challenge))
+        let mockCredentialPublicKeyECC = TestCredentialPublicKeyBuilder().validMock().buildAsByteArray()
+        authDataECC = TestAuthDataBuilder().validMock()
+            .attestedCredData(credentialPublicKey: mockCredentialPublicKeyECC)
+            .noExtensionData()
+        let mockCredentialPublicKeyRSA = TestCredentialPublicKeyBuilder().validMockRSA().buildAsByteArray()
+        authDataRSA = TestAuthDataBuilder().validMock()
+            .attestedCredData(credentialPublicKey: mockCredentialPublicKeyRSA)
+            .noExtensionData()
+        
+        clientDataHash = SHA256.hash(data: Data(mockClientDataJSONBytes))
+    }
+
+    func testSelfAttestationAlgMismatch() async throws {
+        let mockAttestationObject = TestAttestationObjectBuilder()
+            .fmt(.packed)
+            .authData(authDataECC)
+            .attStmt(
+                .map([
+                    .utf8String("alg"): .negativeInt(UInt64(abs(COSEAlgorithmIdentifier.algES384.rawValue) - 1)),
+                    .utf8String("sig"): .byteString(Array(
+                        try TestECCKeyPair
+                            .signature(data: Data([0x01])).derRepresentation
+                    )),
+                ])
+            )
+            .build()
+            .cborEncoded
+        
+        await assertThrowsError(
+            try await finishRegistration(
+                attestationObject: mockAttestationObject,
+                rootCertificatesByFormat: [:]
+            ),
+            expect: WebAuthnError.attestationPublicKeyAlgorithmMismatch
+        )
+    }
+    
+    func testBasicAttestationInvalidVerifData() async throws {
+        let verificationData: [UInt8] = [0x01]
+        let mockCerts = try TestECCKeyPair.certificates()
+        
+        let mockAttestationObject = TestAttestationObjectBuilder()
+            .fmt(.packed)
+            .authData(authDataECC)
+            .attStmt(
+                .map([
+                    .utf8String("alg"): .negativeInt(UInt64(abs(COSEAlgorithmIdentifier.algES256.rawValue) - 1)),
+                    .utf8String("sig"): .byteString(Array(
+                        try TestECCKeyPair
+                            .signature(data: Data(verificationData))
+                            .derRepresentation
+                    )),
+                    .utf8String("x5c"): .array([.byteString(Array(mockCerts.leaf))])
+                ])
+            )
+            .build()
+            .cborEncoded
+        
+        await assertThrowsError(
+            try await finishRegistration(
+                attestationObject: mockAttestationObject,
+                rootCertificatesByFormat: [.packed: [mockCerts.ca]]
+            ),
+            expect: WebAuthnError.invalidVerificationData
+        )
+    }
+    
+    func testBasicAttestationInvalidTrustPath() async throws {
+        let mockCerts = try TestECCKeyPair.certificates()
+        let mockAttestationObject = TestAttestationObjectBuilder()
+            .fmt(.packed)
+            .authData(authDataECC)
+            .attStmt(
+                .map([
+                    .utf8String("alg"): .negativeInt(UInt64(abs(COSEAlgorithmIdentifier.algES256.rawValue) - 1)),
+                    .utf8String("sig"): .byteString(Array(
+                        try TestECCKeyPair
+                            .signature(data: Data(authDataECC.build().byteArrayRepresentation) + clientDataHash)
+                            .derRepresentation
+                    )),
+                    .utf8String("x5c"): .array([.byteString(Array(mockCerts.leaf))])
+                ])
+            )
+            .build()
+            .cborEncoded
+        
+        await assertThrowsError(
+            try await finishRegistration(
+                attestationObject: mockAttestationObject,
+                rootCertificatesByFormat: [.packed: []]
+            ),
+            expect: WebAuthnError.invalidTrustPath
+        )
+    }
+
+    func testSelfAttestationECCSucceeds() async throws {
+        let mockAttestationObject = TestAttestationObjectBuilder()
+            .validMock()
+            .fmt(.packed)
+            .authData(authDataECC)
+            .attStmt(
+                .map([
+                    .utf8String("alg"): .negativeInt(UInt64(abs(COSEAlgorithmIdentifier.algES256.rawValue) - 1)),
+                    .utf8String("sig"): .byteString(Array(
+                        try TestECCKeyPair
+                            .signature(data: Data(authDataECC.build().byteArrayRepresentation) + clientDataHash)
+                            .derRepresentation
+                    ))
+                ])
+            )
+            .build()
+            .cborEncoded
+
+        let credential = try await finishRegistration(attestationObject: mockAttestationObject)
+        XCTAssertEqual(credential.attestationResult.format, .packed)
+        XCTAssertEqual(credential.attestationResult.type, .`self`)
+        XCTAssertEqual(credential.attestationResult.trustChain, [])
+    }
+    
+    func testBasicAttestationECCSucceeds() async throws {
+        let mockCerts = try TestECCKeyPair.certificates()
+        let mockAttestationObject = TestAttestationObjectBuilder()
+            .validMock()
+            .fmt(.packed)
+            .authData(authDataECC)
+            .attStmt(
+                .map([
+                    .utf8String("alg"): .negativeInt(UInt64(abs(COSEAlgorithmIdentifier.algES256.rawValue) - 1)),
+                    .utf8String("sig"): .byteString(Array(
+                        try TestECCKeyPair
+                            .signature(data: Data(authDataECC.build().byteArrayRepresentation) + clientDataHash)
+                            .derRepresentation
+                    )),
+                    .utf8String("x5c"): .array([.byteString(Array(mockCerts.leaf))])
+                ])
+            )
+            .build()
+            .cborEncoded
+
+        let credential = try await finishRegistration(
+            attestationObject: mockAttestationObject,
+            rootCertificatesByFormat: [.packed: [mockCerts.ca]]
+        )
+        XCTAssertEqual(credential.attestationResult.format, .packed)
+        XCTAssertEqual(credential.attestationResult.type, .basicFull)
+        XCTAssertEqual(credential.attestationResult.trustChain.count, 2)
+    }
+    
+    func testSelfPackedAttestationRSASucceeds() async throws {
+        let mockAttestationObject = TestAttestationObjectBuilder()
+            .validMock()
+            .fmt(.packed)
+            .authData(authDataRSA)
+            .attStmt(
+                .map([
+                    .utf8String("alg"): .negativeInt(UInt64(abs(COSEAlgorithmIdentifier.algRS256.rawValue) - 1)),
+                    .utf8String("sig"): .byteString(Array(
+                        try TestRSAKeyPair
+                            .signature(data: Data(authDataRSA.build().byteArrayRepresentation) + clientDataHash)
+                            .rawRepresentation
+                    ))
+                ])
+            )
+            .build()
+            .cborEncoded
+        
+        let credential = try await finishRegistration(attestationObject: mockAttestationObject)
+
+        XCTAssertEqual(credential.attestationResult.format, .packed)
+        XCTAssertEqual(credential.attestationResult.type, .`self`)
+        XCTAssertEqual(credential.attestationResult.trustChain, [])
+    }
+    
+    func testBasicPackedAttestationRSASucceeds() async throws {
+        let mockCerts = try TestRSAKeyPair.certificates()
+        let mockAttestationObject = TestAttestationObjectBuilder()
+            .validMock()
+            .fmt(.packed)
+            .authData(authDataRSA)
+            .attStmt(
+                .map([
+                    .utf8String("alg"): .negativeInt(UInt64(abs(COSEAlgorithmIdentifier.algRS256.rawValue) - 1)),
+                    .utf8String("sig"): .byteString(Array(
+                        try TestRSAKeyPair
+                            .signature(data: Data(authDataRSA.build().byteArrayRepresentation) + clientDataHash)
+                            .rawRepresentation
+                    )),
+                    .utf8String("x5c"): .array([
+                        .byteString(Array(mockCerts.leaf))
+                    ])
+                ])
+            )
+            .build()
+            .cborEncoded
+        
+        let credential = try await finishRegistration(
+            attestationObject: mockAttestationObject,
+            rootCertificatesByFormat: [.packed: [mockCerts.ca]]
+        )
+
+        XCTAssertEqual(credential.attestationResult.format, .packed)
+        XCTAssertEqual(credential.attestationResult.type, .basicFull)
+        XCTAssertEqual(credential.attestationResult.trustChain.count, 2)
+    }
+    
+    private func finishRegistration(
+        challenge: [UInt8] = TestConstants.mockChallenge,
+        type: CredentialType = .publicKey,
+        rawID: [UInt8] = "e0fac9350509f71748d83782ccaf6b4c1462c615c70e255da1344e40887c8fcd".hexadecimal!,
+        attestationObject: [UInt8],
+        requireUserVerification: Bool = false,
+        rootCertificatesByFormat: [AttestationFormat: [Certificate]] = [:],
+        confirmCredentialIDNotRegisteredYet: (String) async throws -> Bool = { _ in true }
+    ) async throws -> Credential {
+        try await webAuthnManager.finishRegistration(
+            challenge: challenge,
+            credentialCreationData: RegistrationCredential(
+                id: rawID.base64URLEncodedString(),
+                type: type,
+                rawID: rawID,
+                attestationResponse: AuthenticatorAttestationResponse(
+                    clientDataJSON: mockClientDataJSONBytes,
+                    attestationObject: attestationObject
+                )
+            ),
+            requireUserVerification: requireUserVerification,
+            rootCertificatesByFormat: rootCertificatesByFormat,
+            confirmCredentialIDNotRegisteredYet: confirmCredentialIDNotRegisteredYet
+        )
+    }
+}
diff --git a/Tests/WebAuthnTests/Formats/TPMAttestationTests.swift b/Tests/WebAuthnTests/Formats/TPMAttestationTests.swift
new file mode 100644
index 00000000..0f6b1d3c
--- /dev/null
+++ b/Tests/WebAuthnTests/Formats/TPMAttestationTests.swift
@@ -0,0 +1,110 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the WebAuthn Swift open source project
+//
+// Copyright (c) 2023 the WebAuthn Swift project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of WebAuthn Swift project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+@testable import WebAuthn
+import XCTest
+import SwiftCBOR
+import Crypto
+import _CryptoExtras
+import X509
+
+// swiftlint:disable:next type_body_length
+final class RegistrationTPMAttestationTests: XCTestCase {
+    var webAuthnManager: WebAuthnManager!
+
+    let challenge: [UInt8] = [1, 0, 1]
+    let relyingPartyDisplayName = "Testy test"
+    let relyingPartyID = "example.com"
+    let relyingPartyOrigin = "https://example.com"
+    let mockClientDataJSONBytes = TestClientDataJSON(challenge: TestConstants.mockChallenge.base64URLEncodedString()).jsonBytes
+
+    override func setUp() {
+        let configuration = WebAuthnManager.Configuration(
+            relyingPartyID: relyingPartyID,
+            relyingPartyName: relyingPartyDisplayName,
+            relyingPartyOrigin: relyingPartyOrigin
+        )
+        webAuthnManager = .init(configuration: configuration, challengeGenerator: .mock(generate: challenge))
+    }
+
+    func testAttCAAttestationRSASucceeds() async throws {
+        let mockCredentialPublicKey = TestCredentialPublicKeyBuilder().validMockRSA().buildAsByteArray()
+        let authData = TestAuthDataBuilder().validMock()
+            .attestedCredData(credentialPublicKey: mockCredentialPublicKey)
+            .noExtensionData()
+        let hash = SHA256.hash(data: Data(mockClientDataJSONBytes))
+        let mockCerts = try TestECCKeyPair.certificates()
+        
+        //let certInfoBytes: [UInt8] = [0xFF, 0x54, 0x43, 0x47] + [UInt8](repeating: 0, count: 80)
+        // RSA PubArea
+        let pubArea = Data(base64Encoded: "AAEACwAGBHIAIJ3/y/NsODrmmfuYaNxty4nXFTiEvigDkiwSQVi/rSKuABAAEAgAAAAAAAEAus2NNibjf6n9vIlQiEmunemlDObEtj7Cr2TDtD//tvJS1//fsW5mxHEz7wo+WiBKlVHwm9O1OKggQVHWlsoAG4QHJL82KwApuSPIYzKBpMTJGS8OZF9Eo7R8elX4JLBJcZ7uA3AtoPaai/zHIHXWVdMzRq2DY9Ymps6MU8jnGMr2Y0L/+IFPrfZhHNLqhx7/h+pNt6eJnp7MmhgIZBk1fKHcgcbDaXZ0fCD511jzu7QQ025OJoN1bXJai4UtPkwof0J2epXBJdu8ExPBY8KlXUBvRdTrsp/njQAKtBLn288I0jabg65Y/io+cWP5UuQTBI0FF6j/lOZ81ttk3oV/FQ==")!
+        let certInfo = Data(base64Encoded: "/1RDR4AXACIAC7fjlRE/X84oQtXc8hucRu9DFXUZD6UhJFkNJ57OM2mJABRqOl417tyWPsLqfhByWFhLi6W+OQAAAAjIGcaOvjsE9fnCGcEBo17KdHVMLNYAIgAL8wrHq55UwHsBdEMDTgPolqcoRsQvpP8QUY07Rjc/ZuoAIgALOeLcnM1NWggIfzd1ct6nAJwvcxnjsbUECgnvAgGp22w=")!
+        let mockAttestationObject = TestAttestationObjectBuilder()
+            .validMock()
+            .fmt(.tpm)
+            .authData(authData)
+            .attStmt(
+                .map([
+                    .utf8String("ver"): .utf8String("2.0"),
+                    .utf8String("alg"): .negativeInt(UInt64(abs(COSEAlgorithmIdentifier.algRS256.rawValue) - 1)),
+                    .utf8String("sig"): .byteString(Array(
+                        try TestECCKeyPair
+                            .signature(data: Data(authData.build().byteArrayRepresentation) + hash)
+                            .derRepresentation
+                    )),
+                    .utf8String("x5c"): .array([.byteString(Array(mockCerts.leaf))]),
+                    .utf8String("aikCert"): .byteString(Array(mockCerts.leaf)),
+                    .utf8String("pubArea"): .byteString(Array(pubArea)),
+                    .utf8String("certInfo"): .byteString(Array(certInfo)),
+                ])
+            )
+            .build()
+            .cborEncoded
+
+        let credential = try await finishRegistration(
+            attestationObject: mockAttestationObject,
+            rootCertificatesByFormat: [.tpm: [mockCerts.ca]]
+        )
+        XCTAssertEqual(credential.attestationResult.format, .tpm)
+        XCTAssertEqual(credential.attestationResult.type, .attCA)
+        XCTAssertEqual(credential.attestationResult.trustChain.count, 2)
+    }
+
+    private func finishRegistration(
+        challenge: [UInt8] = TestConstants.mockChallenge,
+        type: CredentialType = .publicKey,
+        rawID: [UInt8] = "e0fac9350509f71748d83782ccaf6b4c1462c615c70e255da1344e40887c8fcd".hexadecimal!,
+        attestationObject: [UInt8],
+        requireUserVerification: Bool = false,
+        rootCertificatesByFormat: [AttestationFormat: [Certificate]] = [:],
+        confirmCredentialIDNotRegisteredYet: (String) async throws -> Bool = { _ in true }
+    ) async throws -> Credential {
+        try await webAuthnManager.finishRegistration(
+            challenge: challenge,
+            credentialCreationData: RegistrationCredential(
+                id: rawID.base64URLEncodedString(),
+                type: type,
+                rawID: rawID,
+                attestationResponse: AuthenticatorAttestationResponse(
+                    clientDataJSON: mockClientDataJSONBytes,
+                    attestationObject: attestationObject
+                )
+            ),
+            requireUserVerification: requireUserVerification,
+            rootCertificatesByFormat: rootCertificatesByFormat,
+            confirmCredentialIDNotRegisteredYet: confirmCredentialIDNotRegisteredYet
+        )
+    }
+    
+}
diff --git a/Tests/WebAuthnTests/Formats/TPMAttestationTests/CertInfoTests.swift b/Tests/WebAuthnTests/Formats/TPMAttestationTests/CertInfoTests.swift
index 1486f129..a4f1da04 100644
--- a/Tests/WebAuthnTests/Formats/TPMAttestationTests/CertInfoTests.swift
+++ b/Tests/WebAuthnTests/Formats/TPMAttestationTests/CertInfoTests.swift
@@ -28,6 +28,7 @@ final class CertInfoTests: XCTestCase {
 
     func testVerifyThrowsIfTypeIsInvalid() throws {
         let certInfoBytes: [UInt8] = [0xFF, 0x54, 0x43, 0x47] + [UInt8](repeating: 0, count: 80)
+       
         let certInfo = TPMAttestation.CertInfo(fromBytes: Data(certInfoBytes))!
         try assertThrowsError(certInfo.verify(pubArea: Data()), expect: TPMAttestation.CertInfoError.typeInvalid)
     }
diff --git a/Tests/WebAuthnTests/Utils/TestModels/TestAttestationObject.swift b/Tests/WebAuthnTests/Utils/TestModels/TestAttestationObject.swift
index 6abdacaa..f3b5a44c 100644
--- a/Tests/WebAuthnTests/Utils/TestModels/TestAttestationObject.swift
+++ b/Tests/WebAuthnTests/Utils/TestModels/TestAttestationObject.swift
@@ -74,6 +74,12 @@ struct TestAttestationObjectBuilder {
         temp.wrapped.fmt = .utf8String(utf8String)
         return temp
     }
+    
+    func fmt(_ format: AttestationFormat) -> Self {
+        var temp = self
+        temp.wrapped.fmt = .utf8String(format.rawValue)
+        return temp
+    }
 
     // MARK: attStmt
 
diff --git a/Tests/WebAuthnTests/Utils/TestModels/TestAuthData.swift b/Tests/WebAuthnTests/Utils/TestModels/TestAuthData.swift
index 899c79ba..d0566a47 100644
--- a/Tests/WebAuthnTests/Utils/TestModels/TestAuthData.swift
+++ b/Tests/WebAuthnTests/Utils/TestModels/TestAuthData.swift
@@ -46,7 +46,7 @@ struct TestAuthData {
 
 struct TestAuthDataBuilder {
     private var wrapped: TestAuthData
-
+    
     init(wrapped: TestAuthData = TestAuthData()) {
         self.wrapped = wrapped
     }
@@ -72,6 +72,7 @@ struct TestAuthDataBuilder {
             )
             .extensions([UInt8](repeating: 0, count: 20))
     }
+    
 
     /// Creates a valid authData
     ///
diff --git a/Tests/WebAuthnTests/Utils/TestModels/TestCredentialPublicKey.swift b/Tests/WebAuthnTests/Utils/TestModels/TestCredentialPublicKey.swift
index 13ac7c3e..8f556de8 100644
--- a/Tests/WebAuthnTests/Utils/TestModels/TestCredentialPublicKey.swift
+++ b/Tests/WebAuthnTests/Utils/TestModels/TestCredentialPublicKey.swift
@@ -21,6 +21,8 @@ struct TestCredentialPublicKey {
     var crv: CBOR?
     var xCoordinate: CBOR?
     var yCoordinate: CBOR?
+    var e: CBOR?
+    var n: CBOR?
 
     var byteArrayRepresentation: [UInt8] {
         var value: [CBOR: CBOR] = [:]
@@ -39,6 +41,12 @@ struct TestCredentialPublicKey {
         if let yCoordinate {
             value[COSEKey.y.cbor] = yCoordinate
         }
+        if let n {
+            value[COSEKey.n.cbor] = n
+        }
+        if let e {
+            value[COSEKey.e.cbor] = e
+        }
         return CBOR.map(value).encode()
     }
 }
@@ -62,7 +70,24 @@ struct TestCredentialPublicKeyBuilder {
             .xCoordinate(TestECCKeyPair.publicKeyXCoordinate)
             .yCoordinate(TestECCKeyPair.publicKeyYCoordinate)
     }
+    
+    func validMockRSA() -> Self {
+        return self
+            .kty(.rsaKey)
+            .alg(.algRS256)
+            .modulus(TestRSAKeyPair.publicKeyModulus)
+            .exponent(TestRSAKeyPair.publicKeyExponent)
+    }
 
+    func validMockEdDSA() -> Self {
+        return self
+            .kty(.octetKey)
+            .crv(.ed25519)
+            .alg(.algEdDSA)
+            .xCoordinate(TestECCKeyPair.publicKeyXCoordinate)
+            //.yCoordinate(TestECCKeyPair.publicKeyYCoordinate)
+    }
+    
     func kty(_ kty: COSEKeyType) -> Self {
         var temp = self
         temp.wrapped.kty = .unsignedInt(kty.rawValue)
@@ -92,4 +117,16 @@ struct TestCredentialPublicKeyBuilder {
         temp.wrapped.yCoordinate = .byteString(yCoordinate)
         return temp
     }
+    
+    func modulus(_ modulus: [UInt8]) -> Self {
+        var temp = self
+        temp.wrapped.n = .byteString(modulus)
+        return temp
+    }
+    
+    func exponent(_ exponent: [UInt8]) -> Self {
+        var temp = self
+        temp.wrapped.e = .byteString(exponent)
+        return temp
+    }
 }
diff --git a/Tests/WebAuthnTests/Utils/TestModels/TestECCKeyPair.swift b/Tests/WebAuthnTests/Utils/TestModels/TestECCKeyPair.swift
index a227db92..3b383609 100644
--- a/Tests/WebAuthnTests/Utils/TestModels/TestECCKeyPair.swift
+++ b/Tests/WebAuthnTests/Utils/TestModels/TestECCKeyPair.swift
@@ -15,6 +15,8 @@
 import Foundation
 import Crypto
 import WebAuthn
+import X509
+import SwiftASN1
 
 struct TestECCKeyPair {
     static let privateKeyPEM = """
@@ -55,4 +57,51 @@ struct TestECCKeyPair {
 
         return [UInt8](signature)
     }
+    
+    static func certificates() throws -> (leaf: Data, ca: Certificate) {
+        let caPrivateKey = P256.KeyAgreement.PrivateKey()
+        let ca = try Certificate.init(
+            version: .v3,
+            serialNumber: .init(),
+            publicKey: .init(pemEncoded: caPrivateKey.publicKey.pemRepresentation),
+            notValidBefore: Date(),
+            notValidAfter: Date().advanced(by: 3600),
+            issuer: DistinguishedName { CommonName("Example CA") },
+            subject: DistinguishedName { CommonName("Example CA") },
+            signatureAlgorithm: .ecdsaWithSHA256,
+            extensions: try .init{
+                Critical(BasicConstraints.isCertificateAuthority(maxPathLength: 1))
+            },
+            issuerPrivateKey: .init(pemEncoded: caPrivateKey.pemRepresentation)
+        )
+        
+        let privateKey = try P256.KeyAgreement.PrivateKey(pemRepresentation: privateKeyPEM)
+        let leaf = try Certificate.init(
+            version: .v3,
+            serialNumber: .init(),
+            publicKey: .init(pemEncoded: privateKey.publicKey.pemRepresentation),
+            notValidBefore: Date(),
+            notValidAfter: Date().advanced(by: 3600),
+            issuer: ca.subject,
+            subject: DistinguishedName {
+                CommonName("Example leaf certificate")
+                OrganizationalUnitName("Authenticator Attestation")
+                OrganizationName("Example vendor")
+                CountryName("US")
+            },
+            signatureAlgorithm: .ecdsaWithSHA256,
+            extensions: try Certificate.Extensions {
+                Critical(BasicConstraints.notCertificateAuthority)
+                try ExtendedKeyUsage([
+                    .init(oid: .init(arrayLiteral: 2, 23, 133, 8, 3))
+                ])
+            },
+            issuerPrivateKey: .init(pemEncoded: caPrivateKey.pemRepresentation)
+        )
+        var leafSerializer = DER.Serializer()
+        try leafSerializer.serialize(leaf)
+        let leafDER = leafSerializer.serializedBytes
+        
+        return (leaf: Data(leafDER), ca: ca)
+    }
 }
diff --git a/Tests/WebAuthnTests/Utils/TestModels/TestRSAKeyPair.swift b/Tests/WebAuthnTests/Utils/TestModels/TestRSAKeyPair.swift
new file mode 100644
index 00000000..7caf89ef
--- /dev/null
+++ b/Tests/WebAuthnTests/Utils/TestModels/TestRSAKeyPair.swift
@@ -0,0 +1,129 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the WebAuthn Swift open source project
+//
+// Copyright (c) 2023 the WebAuthn Swift project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of WebAuthn Swift project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+import Foundation
+import Crypto
+import _CryptoExtras
+import WebAuthn
+import X509
+import SwiftASN1
+
+struct TestRSAKeyPair {
+    static let privateKeyPEM = """
+    -----BEGIN RSA PRIVATE KEY-----
+    MIIEowIBAAKCAQEA2VnofJn24NHyyGDU4tV1rGsuiI9FBSR7KKU7vkvxqA3GIWO1
+    3Wx8J3Nmcf+U/SXdgs+z9HdiHblvsMQSQxTwLyXxHCB6bYSSOLC+2nHGVxQqDEc2
+    LwZ3gQnaGhwLuHCrffdB6tTCrblDDuCb3agMyRMFz8R0kOiu9+GGj0tLspA62uLx
+    etPSFNsjqdxK5YZEWnGULz/MNgqTR4LUVRaUM6F6o3JVi6UKy4dlXHEpxjTLr7y3
+    1W4AbQVC5M5FElwxMYmTAQrodhtRyGwqdzMxrWjxA7RckBGmMjXhk4ls0v0IAvyB
+    RUCR1zw3c6Swk2q5Sy1itNC0Y62d1Ru2jdLhxQIDAQABAoIBAAgmB8JMH2ZUWK7M
+    eo66g/vf1NHH1UWZFYjzkObUgA3V3ly4GURg4dK0z91sQJCxD7nswYljxGjq39YX
+    s7uSGGMcIAr26MAcXUME1VLpOw9esSjerphavLY4wVWDQak7iCJj17PPIDFVJb90
+    CkPoHfqX3PrqGZipMI4YhWvv3bmm/uzvdMvNB1bWmYLg/zCYATPugZ+XthqiLGDI
+    lohsrN2S06uJYBc+nEGI8PU2OST00PK/qemhR/SNRfukaqzJbkEC23lG1s3U7pgj
+    ucLxb2Ss4I6naJboUFybTAGp/yJf4FYaThiW9v9KDfPpvilj2LNhnVFTNflevMVG
+    bQXktwECgYEA8QJ/Y/BBIGSrlbco16jFXelR4kpG6z5xZ2MQP1i8ktE/9N47TtlP
+    S4cU+JH0X1bx0516IUw0ib2+IH6ogl7AWM4tjEPzVeLwHds9sgtKZS8ZO/MwFlkl
+    TVYBrx6sTaF2OV/6TAlFfNzVmpPeXfn4E0GzLG+FM2cxSxwOOS7kLwUCgYEA5t60
+    bxclfHW9iCrpahxvBr+BrozKcNIPbaRfe1pqVVZTo71lU4aa+5RXffd6xiTfXw73
+    ZYj+82uHeChslACYgcIppqjYodI7hEBLIi9ab/WVSIuSNe16VTjDFbQbqHrgcUhm
+    G4KDMyrYQtsDecMFB27SzoBrxbFJ+9NJ49zS48ECgYEA0vQwtTVSjBwR5FYRtdLQ
+    DsdvGPeS484gTTJ0wj3VsVze8mKi3v1vXti6DmkS0XC081lf0U12nyoqBR9YN+Tf
+    z9uIGsJPd9nP+xIwCmu/jpmPKN5QNP+KmwqxJqtefgTaCpZr66oh3I0fmtHbTb7C
+    2XgkcLychsXIa8n+2SamLFECgYA13qlDcqcwj1iWOU0VkWTmsjDURc3G3Xz0HHKb
+    GdHN78K8ZikKgFIRed+gaOqg6WGlkJxxeLHkoqaNhwEu16S+Qkvts2A5AhEZHtdp
+    NptnnGok70xCgRMWZ5Q9sDTz7xgH1tjcemuauNiVYP1CoBrATT+rJ5P+IQweUoLf
+    RFuBAQKBgCD7w9XOEvCuZNvMM+LtaxUQI5hProiVo8cke/rvu/cxM5EOTOXZGyzW
+    E7gerNu2WpoRLty1ps6XkwLUTcQ8UblceuYtqa2URFKig0HJRuIw5iqSxrlpVjfZ
+    Y2dC2Bo/X4j0M0bKwt/IbGFKNuyTKAtCDgQPUfmzHFhWKAb1Pd4R
+    -----END RSA PRIVATE KEY-----
+    """
+
+    static let publicKeyPEM = """
+    -----BEGIN RSA PUBLIC KEY-----
+    MIIBCgKCAQEA2VnofJn24NHyyGDU4tV1rGsuiI9FBSR7KKU7vkvxqA3GIWO13Wx8
+    J3Nmcf+U/SXdgs+z9HdiHblvsMQSQxTwLyXxHCB6bYSSOLC+2nHGVxQqDEc2LwZ3
+    gQnaGhwLuHCrffdB6tTCrblDDuCb3agMyRMFz8R0kOiu9+GGj0tLspA62uLxetPS
+    FNsjqdxK5YZEWnGULz/MNgqTR4LUVRaUM6F6o3JVi6UKy4dlXHEpxjTLr7y31W4A
+    bQVC5M5FElwxMYmTAQrodhtRyGwqdzMxrWjxA7RckBGmMjXhk4ls0v0IAvyBRUCR
+    1zw3c6Swk2q5Sy1itNC0Y62d1Ru2jdLhxQIDAQAB
+    -----END RSA PUBLIC KEY-----
+    """
+    
+    static let publicKeyExponent = withUnsafeBytes(of: UInt32(65537).bigEndian, Array.init)
+    static let publicKeyModulus = "d959e87c99f6e0d1f2c860d4e2d575ac6b2e888f4505247b28a53bbe4bf1a80dc62163b5dd6c7c27736671ff94fd25dd82cfb3f477621db96fb0c4124314f02f25f11c207a6d849238b0beda71c657142a0c47362f06778109da1a1c0bb870ab7df741ead4c2adb9430ee09bdda80cc91305cfc47490e8aef7e1868f4b4bb2903adae2f17ad3d214db23a9dc4ae586445a71942f3fcc360a934782d455169433a17aa372558ba50acb87655c7129c634cbafbcb7d56e006d0542e4ce45125c31318993010ae8761b51c86c2a773331ad68f103b45c9011a63235e193896cd2fd0802fc81454091d73c3773a4b0936ab94b2d62b4d0b463ad9dd51bb68dd2e1c5".hexadecimal!
+
+    static func signature(data: Data) throws -> _RSA.Signing.RSASignature {
+        let privateKey = try _RSA.Signing.PrivateKey(pemRepresentation: privateKeyPEM)
+        return try privateKey.signature(for: data, padding: .insecurePKCS1v1_5)
+    }
+
+    static var signature: [UInt8] {
+        let authenticatorData = TestAuthDataBuilder()
+            .validAuthenticationMock()
+            .buildAsBase64URLEncoded()
+
+        // Create a signature. This part is usually performed by the authenticator
+        let clientData: Data = TestClientDataJSON(type: "webauthn.get").jsonData
+        let clientDataHash = SHA256.hash(data: clientData)
+        let rawAuthenticatorData = authenticatorData.urlDecoded.decoded!
+        let signatureBase = rawAuthenticatorData + clientDataHash
+        // swiftlint:disable:next force_try
+        let signature = try! TestRSAKeyPair.signature(data: signatureBase).rawRepresentation
+
+        return [UInt8](signature)
+    }
+    
+    static func certificates() throws -> (leaf: Data, ca: Certificate) {
+        let caPrivateKey = try _RSA.Encryption.PrivateKey.init(keySize: .bits2048)
+        let ca = try Certificate.init(
+            version: .v3,
+            serialNumber: .init(),
+            publicKey: .init(pemEncoded: caPrivateKey.publicKey.pemRepresentation),
+            notValidBefore: Date(),
+            notValidAfter: Date().advanced(by: 3600),
+            issuer: DistinguishedName { CommonName("Example CA") },
+            subject: DistinguishedName { CommonName("Example CA") },
+            signatureAlgorithm: .sha256WithRSAEncryption,
+            extensions: try .init{
+                Critical(BasicConstraints.isCertificateAuthority(maxPathLength: 1))
+            },
+            issuerPrivateKey: .init(pemEncoded: caPrivateKey.pemRepresentation)
+        )
+        
+        let privateKey = try _RSA.Encryption.PrivateKey(pemRepresentation: privateKeyPEM)
+        let leaf = try Certificate.init(
+            version: .v3,
+            serialNumber: .init(),
+            publicKey: .init(pemEncoded: privateKey.publicKey.pemRepresentation),
+            notValidBefore: Date(),
+            notValidAfter: Date().advanced(by: 3600),
+            issuer: ca.subject,
+            subject: DistinguishedName {
+                CommonName("Example leaf certificate")
+                OrganizationalUnitName("Authenticator Attestation")
+                OrganizationName("Example vendor")
+                CountryName("US")
+            },
+            signatureAlgorithm: .sha256WithRSAEncryption,
+            extensions: try Certificate.Extensions {Critical(BasicConstraints.notCertificateAuthority)},
+            issuerPrivateKey: .init(pemEncoded: caPrivateKey.pemRepresentation)
+        )
+        var leafSerializer = DER.Serializer()
+        try leafSerializer.serialize(leaf)
+        let leafDER = leafSerializer.serializedBytes
+        
+        return (leaf: Data(leafDER), ca: ca)
+    }
+}

From 1f9233d04fdd9e53b34836b61c73ca400d197653 Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Sat, 11 May 2024 08:45:34 +1000
Subject: [PATCH 26/30] Android attetsation tests

---
 .../Registration/AttestationObject.swift      |   2 +-
 .../AndroidKey/AndroidKeyAttestation.swift    |   5 +-
 .../Formats/TPM/TPMAttestation.swift          |  12 +-
 .../Shared/CredentialPublicKey.swift          |   1 -
 .../AndroidKeyAttestationTests.swift          | 212 ++++++++++++++++++
 .../FidoU2FAttestationTests.swift             |   5 +-
 .../PackedAttestationTests.swift              |  53 ++++-
 .../Formats/TPMAttestationTests.swift         | 110 ---------
 .../TPMAttestationTests.swift                 | 145 ++++++++++++
 .../Utils/TestModels/TestAuthData.swift       |  14 ++
 .../Utils/TestModels/TestRSAKeyPair.swift     |   7 +-
 11 files changed, 438 insertions(+), 128 deletions(-)
 create mode 100644 Tests/WebAuthnTests/Formats/AndroidKeyAttestationTests/AndroidKeyAttestationTests.swift
 rename Tests/WebAuthnTests/Formats/{ => FidoU2FAttestationTests}/FidoU2FAttestationTests.swift (98%)
 rename Tests/WebAuthnTests/Formats/{ => PackedAttestationTests}/PackedAttestationTests.swift (86%)
 delete mode 100644 Tests/WebAuthnTests/Formats/TPMAttestationTests.swift
 create mode 100644 Tests/WebAuthnTests/Formats/TPMAttestationTests/TPMAttestationTests.swift

diff --git a/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift b/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
index 2b5279ac..08157a87 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
@@ -61,7 +61,7 @@ public struct AttestationObject {
         let rootCertificates = rootCertificatesByFormat[format] ?? []
         var attestationType: AttestationResult.AttestationType = .none
         var trustedPath: [Certificate] = []
-        print("\n •••• \(Self.self).verify() format=\(format)")
+
         switch format {
         case .none:
             // if format is `none` statement must be empty
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyAttestation.swift
index 0744e492..20276ae1 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/AndroidKey/AndroidKeyAttestation.swift
@@ -46,9 +46,6 @@ struct AndroidKeyAttestation: AttestationProtocol {
         }
 
         guard let leafCertificate = x5c.first else { throw WebAuthnError.invalidAttestationCertificate }
-        let intermediates = CertificateStore(x5c[1...])
-        let rootCertificatesStore = CertificateStore(rootCertificates)
-
         let verificationData = authenticatorData.rawData + clientDataHash
         // Verify signature
         let leafCertificatePublicKey: Certificate.PublicKey = leafCertificate.publicKey
@@ -67,6 +64,8 @@ struct AndroidKeyAttestation: AttestationProtocol {
             throw WebAuthnError.attestationPublicKeyMismatch
         }
 
+        let intermediates = CertificateStore(x5c[1...])
+        let rootCertificatesStore = CertificateStore(rootCertificates)
         var verifier = Verifier(rootCertificates: rootCertificatesStore) {
             AndroidKeyVerificationPolicy(clientDataHash: clientDataHash)
         }
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
index 5c86382d..239492f0 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
@@ -57,6 +57,9 @@ struct TPMAttestation: AttestationProtocol {
         let verifierResult: VerificationResult = await verifier.validate(
             leafCertificate: aikCert,
             intermediates: intermediates
+            /*diagnosticCallback: { result in
+                print("\n •••• \(Self.self) result=\(result)")
+            }*/
         )
         guard case .validCertificate(let chain) = verifierResult else {
             throw WebAuthnError.invalidTrustPath
@@ -79,7 +82,6 @@ struct TPMAttestation: AttestationProtocol {
 
         if let pubAreaCBOR = attStmt["pubArea"], case let .byteString(pubAreaRaw) = pubAreaCBOR {
             let pubArea = PubArea(from: Data(pubAreaRaw))
-            print("\n••• \(Self.self) pubAreaRaw64=\(Data(pubAreaRaw).base64EncodedString())\npubArea=\(pubArea!)")
         }
         // Verify pubArea
         guard let pubAreaCBOR = attStmt["pubArea"],
@@ -90,7 +92,6 @@ struct TPMAttestation: AttestationProtocol {
         switch pubArea.parameters {
         case let .rsa(rsaParameters):
             if case let .rsa(rsaPublicKeyData) = credentialPublicKey {
-                print("\n •••• \(Self.self) pubArea.unique.data=\(Array(pubArea.unique.data)), rsaPublicKeyData.n=\(rsaPublicKeyData.n)")
             }
             guard case let .rsa(rsaPublicKeyData) = credentialPublicKey,
                 Array(pubArea.unique.data) == rsaPublicKeyData.n else {
@@ -117,12 +118,7 @@ struct TPMAttestation: AttestationProtocol {
                 throw WebAuthnError.tpmInvalidPubAreaCurve
             }
         }
-        
-        /*if let certInfoCBOR = attStmt["certInfo"],
-           case let .byteString(certInfo) = certInfoCBOR {
-            let parsedCertInfo = CertInfo(fromBytes: Data(certInfo))
-            print("\n••• \(Self.self) certInfo64=\(Data(certInfo).base64EncodedString())\nparsedCertInfo=\(parsedCertInfo!)")
-        }*/
+
         // Verify certInfo
         guard let certInfoCBOR = attStmt["certInfo"],
             case let .byteString(certInfo) = certInfoCBOR,
diff --git a/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift b/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
index 9c144715..6691e2ed 100644
--- a/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
+++ b/Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift
@@ -73,7 +73,6 @@ enum CredentialPublicKey {
             throw WebAuthnError.unsupportedCOSEAlgorithm
         }
 
-        print("\n•••• \(Self.self).init() keyType=\(keyType), algorithm=\(algorithm)")
         switch keyType {
         case .ellipticKey:
             self = try .ec2(EC2PublicKey(publicKeyObject: publicKeyObject, algorithm: algorithm))
diff --git a/Tests/WebAuthnTests/Formats/AndroidKeyAttestationTests/AndroidKeyAttestationTests.swift b/Tests/WebAuthnTests/Formats/AndroidKeyAttestationTests/AndroidKeyAttestationTests.swift
new file mode 100644
index 00000000..9597b56c
--- /dev/null
+++ b/Tests/WebAuthnTests/Formats/AndroidKeyAttestationTests/AndroidKeyAttestationTests.swift
@@ -0,0 +1,212 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the WebAuthn Swift open source project
+//
+// Copyright (c) 2023 the WebAuthn Swift project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of WebAuthn Swift project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+@testable import WebAuthn
+import XCTest
+import SwiftCBOR
+import Crypto
+import X509
+
+// swiftlint:disable:next type_body_length
+final class RegistrationAndroidKeyAttestationTests: XCTestCase {
+    var webAuthnManager: WebAuthnManager!
+
+    //let challenge: [UInt8] = Array(Data(base64Encoded: "kIgdIQmaAms56UNzw0DH8uOz3BDF2UJYaJP6zIQX1a8=")!)
+    
+    let relyingPartyDisplayName = "Testy test"
+    let relyingPartyID = "example.com"
+    let relyingPartyOrigin = "https://example.com"
+    let mockClientDataJSONBytes = TestClientDataJSON(challenge: TestConstants.mockChallenge.base64URLEncodedString()).jsonBytes
+    let mockCredentialPublicKeyECC = TestCredentialPublicKeyBuilder().validMock().buildAsByteArray()
+    let challenge: [UInt8] = [1, 0, 1]
+
+    override func setUp() {
+        let configuration = WebAuthnManager.Configuration(
+            relyingPartyID: relyingPartyID,
+            relyingPartyName: relyingPartyDisplayName,
+            relyingPartyOrigin: relyingPartyOrigin
+        )
+        webAuthnManager = .init(configuration: configuration, challengeGenerator: .mock(generate: challenge))
+    }
+
+    func testInvalidAlg() async throws {
+        //let authData = TestAuthDataBuilder().validMock()
+        let authData = TestAuthDataBuilder().validMock()
+            .attestedCredData(credentialPublicKey: mockCredentialPublicKeyECC)
+            .noExtensionData()
+        let mockAttestationObject = TestAttestationObjectBuilder()
+            .fmt(.androidKey)
+            .authData(authData)
+            .attStmt(
+                .map([.utf8String("alg"): .negativeInt(999)])
+            )
+            .build()
+            .cborEncoded
+        
+        await assertThrowsError(
+            try await finishRegistration(
+                attestationObject: mockAttestationObject,
+                rootCertificatesByFormat: [:]
+            ),
+            expect: WebAuthnError.invalidAttestationSignatureAlgorithm
+        )
+    }
+
+    func testInvalidSig() async throws {
+        let authData = TestAuthDataBuilder().validMock()
+        let mockAttestationObject = TestAttestationObjectBuilder()
+            .fmt(.androidKey)
+            .authData(authData)
+            .attStmt(
+                .map([
+                    .utf8String("alg"): .negativeInt(UInt64(abs(COSEAlgorithmIdentifier.algES256.rawValue) - 1)),
+                    .utf8String("sig"): .negativeInt(999)
+                ])
+            )
+            .build()
+            .cborEncoded
+        
+        await assertThrowsError(
+            try await finishRegistration(
+                attestationObject: mockAttestationObject,
+                rootCertificatesByFormat: [:]
+            ),
+            expect: WebAuthnError.invalidSignature
+        )
+    }
+    
+    func testInvalidCert() async throws {
+        let authData = TestAuthDataBuilder().validMock()
+        let mockAttestationObject = TestAttestationObjectBuilder()
+            .fmt(.androidKey)
+            .authData(authData)
+            .attStmt(
+                .map([
+                    .utf8String("alg"): .negativeInt(UInt64(abs(COSEAlgorithmIdentifier.algES256.rawValue) - 1)),
+                    .utf8String("sig"): .byteString([0x00]),
+                    .utf8String("x5c"): .byteString([0x00])
+                ])
+            )
+            .build()
+            .cborEncoded
+        
+        await assertThrowsError(
+            try await finishRegistration(
+                attestationObject: mockAttestationObject,
+                rootCertificatesByFormat: [:]
+            ),
+            expect: WebAuthnError.invalidAttestationCertificate
+        )
+    }
+    
+    func testInvalidVerificationData() async throws {
+        let mockCerts = try TestECCKeyPair.certificates()
+        let verificationData: [UInt8] = [0x01]
+        let authData = TestAuthDataBuilder().validMock()
+        let mockAttestationObject = TestAttestationObjectBuilder()
+            .fmt(.androidKey)
+            .authData(authData)
+            .attStmt(
+                .map([
+                    .utf8String("alg"): .negativeInt(UInt64(abs(COSEAlgorithmIdentifier.algES256.rawValue) - 1)),
+                    .utf8String("sig"): .byteString(Array(
+                        try TestECCKeyPair
+                            .signature(data: Data(verificationData))
+                            .derRepresentation
+                    )),
+                    .utf8String("x5c"): .array([.byteString(Array(mockCerts.leaf))])
+                ])
+            )
+            .build()
+            .cborEncoded
+        
+        await assertThrowsError(
+            try await finishRegistration(
+                attestationObject: mockAttestationObject,
+                rootCertificatesByFormat: [:]
+            ),
+            expect: WebAuthnError.invalidVerificationData
+        )
+    }
+    
+    func testPublicKeysMismatch() async throws {
+        let mockCerts = try TestECCKeyPair.certificates()
+        let verificationData: [UInt8] = [0x01]
+        let authData = TestAuthDataBuilder().validMockRSA()
+        let clientDataHash = SHA256.hash(data: Data(mockClientDataJSONBytes))
+        let mockAttestationObject = TestAttestationObjectBuilder()
+            .fmt(.androidKey)
+            .authData(authData)
+            .attStmt(
+                .map([
+                    .utf8String("alg"): .negativeInt(UInt64(abs(COSEAlgorithmIdentifier.algES256.rawValue) - 1)),
+                    .utf8String("sig"): .byteString(Array(
+                        try TestECCKeyPair
+                            .signature(data: Data(authData.build().byteArrayRepresentation) + clientDataHash)
+                            .derRepresentation
+                        
+                    )),
+                    .utf8String("x5c"): .array([.byteString(Array(mockCerts.leaf))])
+                ])
+            )
+            .build()
+            .cborEncoded
+        
+        await assertThrowsError(
+            try await finishRegistration(
+                attestationObject: mockAttestationObject,
+                rootCertificatesByFormat: [:]
+            ),
+            expect: WebAuthnError.attestationPublicKeyMismatch
+        )
+    }
+
+    /*func testAttCAAttestationRSASucceeds() async throws {
+        let credential = try await finishRegistration(
+            challenge: challenge,
+            attestationObject: Array(Data(base64Encoded: attestationObjectBase64.urlDecoded.asString())!),
+            rootCertificatesByFormat: [.tpm: [caCert]]
+        )
+        XCTAssertEqual(credential.attestationResult.format, .tpm)
+        XCTAssertEqual(credential.attestationResult.type, .attCA)
+        XCTAssertEqual(credential.attestationResult.trustChain.count, 3)
+    }*/
+
+    private func finishRegistration(
+        challenge: [UInt8] = TestConstants.mockChallenge,
+        type: CredentialType = .publicKey,
+        rawID: [UInt8] = "e0fac9350509f71748d83782ccaf6b4c1462c615c70e255da1344e40887c8fcd".hexadecimal!,
+        attestationObject: [UInt8],
+        requireUserVerification: Bool = false,
+        rootCertificatesByFormat: [AttestationFormat: [Certificate]] = [:],
+        confirmCredentialIDNotRegisteredYet: (String) async throws -> Bool = { _ in true }
+    ) async throws -> Credential {
+        try await webAuthnManager.finishRegistration(
+            challenge: challenge,
+            credentialCreationData: RegistrationCredential(
+                id: rawID.base64URLEncodedString(),
+                type: type,
+                rawID: rawID,
+                attestationResponse: AuthenticatorAttestationResponse(
+                    clientDataJSON: mockClientDataJSONBytes,
+                    attestationObject: attestationObject
+                )
+            ),
+            requireUserVerification: requireUserVerification,
+            rootCertificatesByFormat: rootCertificatesByFormat,
+            confirmCredentialIDNotRegisteredYet: confirmCredentialIDNotRegisteredYet
+        )
+    }
+    
+}
diff --git a/Tests/WebAuthnTests/Formats/FidoU2FAttestationTests.swift b/Tests/WebAuthnTests/Formats/FidoU2FAttestationTests/FidoU2FAttestationTests.swift
similarity index 98%
rename from Tests/WebAuthnTests/Formats/FidoU2FAttestationTests.swift
rename to Tests/WebAuthnTests/Formats/FidoU2FAttestationTests/FidoU2FAttestationTests.swift
index 9a2bca59..597e549b 100644
--- a/Tests/WebAuthnTests/Formats/FidoU2FAttestationTests.swift
+++ b/Tests/WebAuthnTests/Formats/FidoU2FAttestationTests/FidoU2FAttestationTests.swift
@@ -69,8 +69,7 @@ final class RegistrationFidoU2FAttestationTests: XCTestCase {
     }
     
     func testAttestationMissingx5c() async throws {
-        let authData = TestAuthDataBuilder().validMock()
-        
+        let authData = TestAuthDataBuilder().validMock()        
         let mockAttestationObject = TestAttestationObjectBuilder()
             .fmt(.fidoU2F)
             .authData(authData)
@@ -98,7 +97,7 @@ final class RegistrationFidoU2FAttestationTests: XCTestCase {
         let mockCerts = try TestECCKeyPair.certificates()
         let credentialId: [UInt8] = [0b00000001]
         let authData = TestAuthDataBuilder()
-            .relyingPartyIDHash(fromRelyingPartyID: "example.com")
+            .relyingPartyIDHash(fromRelyingPartyID: relyingPartyID)
             .flags(0b11000101)
             .counter([0b00000000, 0b00000000, 0b00000000, 0b00000000])
             .attestedCredData(
diff --git a/Tests/WebAuthnTests/Formats/PackedAttestationTests.swift b/Tests/WebAuthnTests/Formats/PackedAttestationTests/PackedAttestationTests.swift
similarity index 86%
rename from Tests/WebAuthnTests/Formats/PackedAttestationTests.swift
rename to Tests/WebAuthnTests/Formats/PackedAttestationTests/PackedAttestationTests.swift
index e0407a77..a262898d 100644
--- a/Tests/WebAuthnTests/Formats/PackedAttestationTests.swift
+++ b/Tests/WebAuthnTests/Formats/PackedAttestationTests/PackedAttestationTests.swift
@@ -51,6 +51,33 @@ final class RegistrationPackedAttestationTests: XCTestCase {
         clientDataHash = SHA256.hash(data: Data(mockClientDataJSONBytes))
     }
 
+    func testInvalidAlg() async throws {
+        let mockAttestationObject = TestAttestationObjectBuilder()
+            .fmt(.packed)
+            .authData(authDataECC)
+            .attStmt(
+                .map([
+                    .utf8String("alg"): .negativeInt(999),
+                    .utf8String("sig"): .byteString(Array(
+                        try TestECCKeyPair
+                            .signature(data: Data(authDataECC.build().byteArrayRepresentation) + clientDataHash)
+                            .derRepresentation
+                    )),
+                ])
+                
+            )
+            .build()
+            .cborEncoded
+        
+        await assertThrowsError(
+            try await finishRegistration(
+                attestationObject: mockAttestationObject,
+                rootCertificatesByFormat: [:]
+            ),
+            expect: WebAuthnError.invalidAttestationSignatureAlgorithm
+        )
+    }
+    
     func testSelfAttestationAlgMismatch() async throws {
         let mockAttestationObject = TestAttestationObjectBuilder()
             .fmt(.packed)
@@ -75,7 +102,31 @@ final class RegistrationPackedAttestationTests: XCTestCase {
             expect: WebAuthnError.attestationPublicKeyAlgorithmMismatch
         )
     }
-    
+
+    func testInvalidCert() async throws {
+        let authData = TestAuthDataBuilder().validMock()
+        let mockAttestationObject = TestAttestationObjectBuilder()
+            .fmt(.packed)
+            .authData(authData)
+            .attStmt(
+                .map([
+                    .utf8String("alg"): .negativeInt(UInt64(abs(COSEAlgorithmIdentifier.algES256.rawValue) - 1)),
+                    .utf8String("sig"): .byteString([0x00]),
+                    .utf8String("x5c"): .byteString([0x00])
+                ])
+            )
+            .build()
+            .cborEncoded
+        
+        await assertThrowsError(
+            try await finishRegistration(
+                attestationObject: mockAttestationObject,
+                rootCertificatesByFormat: [:]
+            ),
+            expect: WebAuthnError.invalidAttestationCertificate
+        )
+    }
+
     func testBasicAttestationInvalidVerifData() async throws {
         let verificationData: [UInt8] = [0x01]
         let mockCerts = try TestECCKeyPair.certificates()
diff --git a/Tests/WebAuthnTests/Formats/TPMAttestationTests.swift b/Tests/WebAuthnTests/Formats/TPMAttestationTests.swift
deleted file mode 100644
index 0f6b1d3c..00000000
--- a/Tests/WebAuthnTests/Formats/TPMAttestationTests.swift
+++ /dev/null
@@ -1,110 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// This source file is part of the WebAuthn Swift open source project
-//
-// Copyright (c) 2023 the WebAuthn Swift project authors
-// Licensed under Apache License v2.0
-//
-// See LICENSE.txt for license information
-// See CONTRIBUTORS.txt for the list of WebAuthn Swift project authors
-//
-// SPDX-License-Identifier: Apache-2.0
-//
-//===----------------------------------------------------------------------===//
-
-@testable import WebAuthn
-import XCTest
-import SwiftCBOR
-import Crypto
-import _CryptoExtras
-import X509
-
-// swiftlint:disable:next type_body_length
-final class RegistrationTPMAttestationTests: XCTestCase {
-    var webAuthnManager: WebAuthnManager!
-
-    let challenge: [UInt8] = [1, 0, 1]
-    let relyingPartyDisplayName = "Testy test"
-    let relyingPartyID = "example.com"
-    let relyingPartyOrigin = "https://example.com"
-    let mockClientDataJSONBytes = TestClientDataJSON(challenge: TestConstants.mockChallenge.base64URLEncodedString()).jsonBytes
-
-    override func setUp() {
-        let configuration = WebAuthnManager.Configuration(
-            relyingPartyID: relyingPartyID,
-            relyingPartyName: relyingPartyDisplayName,
-            relyingPartyOrigin: relyingPartyOrigin
-        )
-        webAuthnManager = .init(configuration: configuration, challengeGenerator: .mock(generate: challenge))
-    }
-
-    func testAttCAAttestationRSASucceeds() async throws {
-        let mockCredentialPublicKey = TestCredentialPublicKeyBuilder().validMockRSA().buildAsByteArray()
-        let authData = TestAuthDataBuilder().validMock()
-            .attestedCredData(credentialPublicKey: mockCredentialPublicKey)
-            .noExtensionData()
-        let hash = SHA256.hash(data: Data(mockClientDataJSONBytes))
-        let mockCerts = try TestECCKeyPair.certificates()
-        
-        //let certInfoBytes: [UInt8] = [0xFF, 0x54, 0x43, 0x47] + [UInt8](repeating: 0, count: 80)
-        // RSA PubArea
-        let pubArea = Data(base64Encoded: "AAEACwAGBHIAIJ3/y/NsODrmmfuYaNxty4nXFTiEvigDkiwSQVi/rSKuABAAEAgAAAAAAAEAus2NNibjf6n9vIlQiEmunemlDObEtj7Cr2TDtD//tvJS1//fsW5mxHEz7wo+WiBKlVHwm9O1OKggQVHWlsoAG4QHJL82KwApuSPIYzKBpMTJGS8OZF9Eo7R8elX4JLBJcZ7uA3AtoPaai/zHIHXWVdMzRq2DY9Ymps6MU8jnGMr2Y0L/+IFPrfZhHNLqhx7/h+pNt6eJnp7MmhgIZBk1fKHcgcbDaXZ0fCD511jzu7QQ025OJoN1bXJai4UtPkwof0J2epXBJdu8ExPBY8KlXUBvRdTrsp/njQAKtBLn288I0jabg65Y/io+cWP5UuQTBI0FF6j/lOZ81ttk3oV/FQ==")!
-        let certInfo = Data(base64Encoded: "/1RDR4AXACIAC7fjlRE/X84oQtXc8hucRu9DFXUZD6UhJFkNJ57OM2mJABRqOl417tyWPsLqfhByWFhLi6W+OQAAAAjIGcaOvjsE9fnCGcEBo17KdHVMLNYAIgAL8wrHq55UwHsBdEMDTgPolqcoRsQvpP8QUY07Rjc/ZuoAIgALOeLcnM1NWggIfzd1ct6nAJwvcxnjsbUECgnvAgGp22w=")!
-        let mockAttestationObject = TestAttestationObjectBuilder()
-            .validMock()
-            .fmt(.tpm)
-            .authData(authData)
-            .attStmt(
-                .map([
-                    .utf8String("ver"): .utf8String("2.0"),
-                    .utf8String("alg"): .negativeInt(UInt64(abs(COSEAlgorithmIdentifier.algRS256.rawValue) - 1)),
-                    .utf8String("sig"): .byteString(Array(
-                        try TestECCKeyPair
-                            .signature(data: Data(authData.build().byteArrayRepresentation) + hash)
-                            .derRepresentation
-                    )),
-                    .utf8String("x5c"): .array([.byteString(Array(mockCerts.leaf))]),
-                    .utf8String("aikCert"): .byteString(Array(mockCerts.leaf)),
-                    .utf8String("pubArea"): .byteString(Array(pubArea)),
-                    .utf8String("certInfo"): .byteString(Array(certInfo)),
-                ])
-            )
-            .build()
-            .cborEncoded
-
-        let credential = try await finishRegistration(
-            attestationObject: mockAttestationObject,
-            rootCertificatesByFormat: [.tpm: [mockCerts.ca]]
-        )
-        XCTAssertEqual(credential.attestationResult.format, .tpm)
-        XCTAssertEqual(credential.attestationResult.type, .attCA)
-        XCTAssertEqual(credential.attestationResult.trustChain.count, 2)
-    }
-
-    private func finishRegistration(
-        challenge: [UInt8] = TestConstants.mockChallenge,
-        type: CredentialType = .publicKey,
-        rawID: [UInt8] = "e0fac9350509f71748d83782ccaf6b4c1462c615c70e255da1344e40887c8fcd".hexadecimal!,
-        attestationObject: [UInt8],
-        requireUserVerification: Bool = false,
-        rootCertificatesByFormat: [AttestationFormat: [Certificate]] = [:],
-        confirmCredentialIDNotRegisteredYet: (String) async throws -> Bool = { _ in true }
-    ) async throws -> Credential {
-        try await webAuthnManager.finishRegistration(
-            challenge: challenge,
-            credentialCreationData: RegistrationCredential(
-                id: rawID.base64URLEncodedString(),
-                type: type,
-                rawID: rawID,
-                attestationResponse: AuthenticatorAttestationResponse(
-                    clientDataJSON: mockClientDataJSONBytes,
-                    attestationObject: attestationObject
-                )
-            ),
-            requireUserVerification: requireUserVerification,
-            rootCertificatesByFormat: rootCertificatesByFormat,
-            confirmCredentialIDNotRegisteredYet: confirmCredentialIDNotRegisteredYet
-        )
-    }
-    
-}
diff --git a/Tests/WebAuthnTests/Formats/TPMAttestationTests/TPMAttestationTests.swift b/Tests/WebAuthnTests/Formats/TPMAttestationTests/TPMAttestationTests.swift
new file mode 100644
index 00000000..7b009449
--- /dev/null
+++ b/Tests/WebAuthnTests/Formats/TPMAttestationTests/TPMAttestationTests.swift
@@ -0,0 +1,145 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the WebAuthn Swift open source project
+//
+// Copyright (c) 2023 the WebAuthn Swift project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of WebAuthn Swift project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+@testable import WebAuthn
+import XCTest
+import SwiftCBOR
+import Crypto
+import X509
+
+// swiftlint:disable:next type_body_length
+final class RegistrationTPMAttestationTests: XCTestCase {
+    var webAuthnManager: WebAuthnManager!
+
+    let challenge: [UInt8] = Array(Data(base64Encoded: "kIgdIQmaAms56UNzw0DH8uOz3BDF2UJYaJP6zIQX1a8=")!)
+    let relyingPartyDisplayName = "Testy test"
+    let relyingPartyID = "d2urpypvrhb05x.amplifyapp.com"
+    let relyingPartyOrigin = "https://dev.d2urpypvrhb05x.amplifyapp.com"
+
+    // Generating data, or mocking it, for a TPM registration, would be excruciatingly painful.
+    // Here we're using a "known good payload" for a Windows Hello authenticator.
+    let clientDataJSONBase64 = "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoia0lnZElRbWFBbXM1NlVOencwREg4dU96M0JERjJVSllhSlA2eklRWDFhOCIsIm9yaWdpbiI6Imh0dHBzOi8vZGV2LmQydXJweXB2cmhiMDV4LmFtcGxpZnlhcHAuY29tIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ=="
+    let attestationObjectBase64 = URLEncodedBase64("o2NmbXRjdHBtZ2F0dFN0bXSmY2FsZzn__mNzaWdZAQBFEaTe-uZvbZBNsIMtJa26eigMUxEM1mBtddR7gdEBH5Hyeo9hFCqiJwYVKUq_iP9hvFaiLzoGbAWDgiG-fa3F-S71c8w83756dyRBMXNHYEvYjfv0TqGyky73V4xyKpf1iHiO_g4t31UjQiyTfypdP_rRcm42KVKgVyRPZzx_AKweN9XKEFfT2Ym3fmqD_scaIeKSyGs9qwH1MbILLUVnRK6fKK6sAA4ZaDVz4gUiSUoK9ZycCC2hfLBq5GjiTLgQF_Q2O3gRTqmU8VfwVsmtN5OMaGOyaFrUk97-RvZVrARXhNzrUAJT7KjTLDZeIA96F3pB_F_q3xd_dgvwVpWHY3ZlcmMyLjBjeDVjglkFuzCCBbcwggOfoAMCAQICEHHcna7VCE3QpRyKgi2uvXYwDQYJKoZIhvcNAQELBQAwQTE_MD0GA1UEAxM2RVVTLU5UQy1LRVlJRC04ODJGMDQ3Qjg3MTIxQ0Y5ODg1RjMxMTYwQkM3QkI1NTg2QUY0NzFCMB4XDTIyMDEyMDE5NTQxNloXDTI3MDYwMzE3NTE0OFowADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALtT5frDB-WUq6N0VYnlYEalJzSut0JQx3vP29_ub-kZ7csJrm8uGXQGUkPlf4EFehFTnQ1jX_oZ8jNPw1m3rV5ijcuCe3r5GICFD6gpbuErmGS2mDVfe3fl_p0gPvhtulqatb1uYkWfW5SIKix1XWRvm92s3lRQvd-6vX_ExPIP-pEf0tkeINpBNNWgdtx3VdW4KVFTcv-q2FKhqfqXiAdOMHmwmWyXYulppYqW2XC7Pw9QmHZR_C5Urpc5UMmABz4zWSAYOyBMkKsX8koAsk8RgLtus07wW3FhJqi-BYczIe0IxG0q9UL295lkaxreTkfWYZHMMcU4M-Tm1w7QvKsCAwEAAaOCAeowggHmMA4GA1UdDwEB_wQEAwIHgDAMBgNVHRMBAf8EAjAAMG0GA1UdIAEB_wRjMGEwXwYJKwYBBAGCNxUfMFIwUAYIKwYBBQUHAgIwRB5CAFQAQwBQAEEAIAAgAFQAcgB1AHMAdABlAGQAIAAgAFAAbABhAHQAZgBvAHIAbQAgACAASQBkAGUAbgB0AGkAdAB5MBAGA1UdJQQJMAcGBWeBBQgDMFAGA1UdEQEB_wRGMESkQjBAMT4wEAYFZ4EFAgIMB05QQ1Q3NXgwFAYFZ4EFAgEMC2lkOjRFNTQ0MzAwMBQGBWeBBQIDDAtpZDowMDA3MDAwMjAfBgNVHSMEGDAWgBSMmnF_AA0xD8rW7i0pqjSXJYwSHjAdBgNVHQ4EFgQUFmUMIda76eb7Whi8CweaWhe7yNMwgbIGCCsGAQUFBwEBBIGlMIGiMIGfBggrBgEFBQcwAoaBkmh0dHA6Ly9hemNzcHJvZGV1c2Fpa3B1Ymxpc2guYmxvYi5jb3JlLndpbmRvd3MubmV0L2V1cy1udGMta2V5aWQtODgyZjA0N2I4NzEyMWNmOTg4NWYzMTE2MGJjN2JiNTU4NmFmNDcxYi84ODIzMGNhMi0yN2U1LTQxNTEtOWJhMi01OWI1ODJjMzlhYWEuY2VyMA0GCSqGSIb3DQEBCwUAA4ICAQCxsTbR5V8qnw6H6HEWJvrqcRy8fkY_vFUSjUq27hRl0t9D6LuS20l65FFm48yLwCkQbIf-aOBjwWafAbSVnEMig3KP-2Ml8IFtH63Msq9lwDlnXx2PNi7ISOemHNzBNeOG7pd_Zs69XUTq9zCriw9gAILCVCYllBluycdT7wZdjf0Bb5QJtTMuhwNXnOWmjv0VBOfsclWo-SEnnufaIDi0Vcf_TzbgmNn408Ej7R4Njy4qLnhPk64ruuWNJt3xlLMjbJXe_VKdO3lhM7JVFWSNAn8zfvEIwrrgCPhp1k2mFUGxJEvpSTnuZtNF35z4_54K6cEqZiqO-qd4FKt4KYs1GYJDyxttuUySGtnYyZg2aYB6hamg3asRDjBMPqoURsdVJcWQh3dFnD88cbs7Qt4_ytqAY61qfPE7bJ6E33o0X7OtxmECPd3aBJk6nsyXEXNF2vIww1UCrRC0OEr1HsTqA4bQU8KCWV6kduUnvkUWPT8CF0d2ER4wnszb053Tlcf2ebcytTMf_Nd95g520Hhqb2FZALCErijBi04Bu6SNeND1NQ3nxDSKC-CamOYW0ODch05Xzi1V0_sq0zmdKTxMSpg1jOZ1Q9924D4lJkruCB3zcsIBTUxV0EgAM1zGuoqwWjwYXr_8tO4_kEO1Lw8DckZIrk1s3ySsMVC89TRrIVkG7zCCBuswggTToAMCAQICEzMAAAQI5W53M7IUDf4AAAAABAgwDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xNjA0BgNVBAMTLU1pY3Jvc29mdCBUUE0gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxNDAeFw0yMTA2MDMxNzUxNDhaFw0yNzA2MDMxNzUxNDhaMEExPzA9BgNVBAMTNkVVUy1OVEMtS0VZSUQtODgyRjA0N0I4NzEyMUNGOTg4NUYzMTE2MEJDN0JCNTU4NkFGNDcxQjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMEye3QdCUOGeseHj_QjLMJVbHBEJKkRbXFsmZi6Mob_3IsVfxO-mQQC-xfY9tDyB1jhxfFAG-rjUfRVBwYYPfaVo2W58Q09Kcpf-43Iw9SRxx2ThP-KPFJPFBofZroloNaTNz3DRaWZ2ha-_PUG2nwTXR7LoIpqMVW1PDzGxb47SNpRmKJxVZhQ2_wRhZZvHRHJpZmrCmHRpTRqWSzQT1jn7Zo9VuMYvp_OFj7-LFpkqi4BYyhi0kTBPDQTpYrBi7RtmF1MhZBmm1HGDhoXHcPSZkN5vq5at4g03R15KWyRDgBcckCAgtewd6Dtd_Zwaejlm57xyGqP6T-AE-N8udh1NPv_PZVlSc4CnCayUTPORuaJ7N-v7Y4wpNSIdipq29hw19WVuO_z7q6GpQbn17arYf6LSoDZfwO8GHXPrtBOYYSZCNKuZ_IK8nomBLJPtN5AzwEZNyLCZIkg0U0sJ-oVr2UEYxlwwZQm5RSDxProaKU-OXq4f_j_0pEu5_DbJx9syR3Nsv6Lt9Zkf3JSJTVtWXoM0-R_82vAJ669PX0LLr603PKWBZbW7zQvtGojT_Pc1FDGfwhcdckxd3MGpEjZwh_1D8elYcxj3Ndw5jClWosZKr33pUcjqeFtSZSur0lbm6vyCfS16XzSMn8IkHmbbXcpgGKHumUCFD8CHJIBAgMBAAGjggGOMIIBijAOBgNVHQ8BAf8EBAMCAoQwGwYDVR0lBBQwEgYJKwYBBAGCNxUkBgVngQUIAzAWBgNVHSAEDzANMAsGCSsGAQQBgjcVHzASBgNVHRMBAf8ECDAGAQH_AgEAMB0GA1UdDgQWBBSMmnF_AA0xD8rW7i0pqjSXJYwSHjAfBgNVHSMEGDAWgBR6jArOL0hiF-KU0a5VwVLscXSkVjBwBgNVHR8EaTBnMGWgY6Bhhl9odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUUE0lMjBSb290JTIwQ2VydGlmaWNhdGUlMjBBdXRob3JpdHklMjAyMDE0LmNybDB9BggrBgEFBQcBAQRxMG8wbQYIKwYBBQUHMAKGYWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVFBNJTIwUm9vdCUyMENlcnRpZmljYXRlJTIwQXV0aG9yaXR5JTIwMjAxNC5jcnQwDQYJKoZIhvcNAQELBQADggIBAHG-1grb-6xpObMtxfFScl8PRLd_GjLFaeAd0kVPls0jzKplG2Am4O87Qg0OUY0VhQ-uD39590gGrWEWnOmdrVJ-R1lJc1yrIZFEASBEedJSvxw9YTNknD59uXtznIP_4Glk-4NpqpcYov2OkBdV59V4dTL5oWFH0vzkZQfvGFxEHwtB9O6Bh0Lk142zXAh5_vf_-hSw3t9adloBAnA0AtPUVkzmgNRGeTpfPm-Iud-MAoUXaccFn2EChjKb9ApbS1ww8ZvX4x2kFU6qctu32g7Vf6CgACc1i-UYDT_E-h6c1O4n7JK2OxVS-DVwybps-cALU0gj-ZMauNMej0_x_NerzvDuQ77eFMTDMY4ZTzYOzlg4Nj0K8y1Bx_KqeTBO0N9CdEG3dBxWCUUFQzAx-i38xJL-dtYTCCFATVhc9FFJ0CQgU07JAGAeuNm_GL8kN46bMXd_ApQYFzDQUWYYXvRIt9mCw0Zd45lpAMuiDKT9TgjUVDNu8LQ8FPK0KeiQVrGHFMhHgg2pbVH9Pvc1jNEeRpCo0BLpZQwuIgEt90mepkt6Va-C9krHsU4y2oalG2LUu-jOC3NWNK8LssYVUCFWtaKh5d-xdTQmjx4uO-1sq9GFntVJ94QnEDhldz0XMopQ8srTGLMqR3MT-GkSNb5X1UFC-X1udXI8YvB3ADr_Z3B1YkFyZWFZATYAAQALAAYEcgAgnf_L82w4OuaZ-5ho3G3LidcVOIS-KAOSLBJBWL-tIq4AEAAQCAAAAAAAAQC6zY02JuN_qf28iVCISa6d6aUM5sS2PsKvZMO0P_-28lLX_9-xbmbEcTPvCj5aIEqVUfCb07U4qCBBUdaWygAbhAckvzYrACm5I8hjMoGkxMkZLw5kX0SjtHx6VfgksElxnu4DcC2g9pqL_McgddZV0zNGrYNj1iamzoxTyOcYyvZjQv_4gU-t9mEc0uqHHv-H6k23p4mensyaGAhkGTV8odyBxsNpdnR8IPnXWPO7tBDTbk4mg3VtclqLhS0-TCh_QnZ6lcEl27wTE8FjwqVdQG9F1Ouyn-eNAAq0EufbzwjSNpuDrlj-Kj5xY_lS5BMEjQUXqP-U5nzW22TehX8VaGNlcnRJbmZvWKH_VENHgBcAIgALt-OVET9fzihC1dzyG5xG70MVdRkPpSEkWQ0nns4zaYkAFGo6XjXu3JY-wup-EHJYWEuLpb45AAAACMgZxo6-OwT1-cIZwQGjXsp0dUws1gAiAAvzCsernlTAewF0QwNOA-iWpyhGxC-k_xBRjTtGNz9m6gAiAAs54tyczU1aCAh_N3Vy3qcAnC9zGeOxtQQKCe8CAanbbGhhdXRoRGF0YVkBZ-MWwK8fdtoeGn4DEn0TAUu4IUP_PMiBiJd4lDRznbBDRQAAAAAImHBYytxLgbbhMN5Q3L6WACBxLUIzn9ngKAM11_UwWG7kCiAvVyO1mYGSsEhfWeyhDaQBAwM5AQAgWQEAus2NNibjf6n9vIlQiEmunemlDObEtj7Cr2TDtD__tvJS1__fsW5mxHEz7wo-WiBKlVHwm9O1OKggQVHWlsoAG4QHJL82KwApuSPIYzKBpMTJGS8OZF9Eo7R8elX4JLBJcZ7uA3AtoPaai_zHIHXWVdMzRq2DY9Ymps6MU8jnGMr2Y0L_-IFPrfZhHNLqhx7_h-pNt6eJnp7MmhgIZBk1fKHcgcbDaXZ0fCD511jzu7QQ025OJoN1bXJai4UtPkwof0J2epXBJdu8ExPBY8KlXUBvRdTrsp_njQAKtBLn288I0jabg65Y_io-cWP5UuQTBI0FF6j_lOZ81ttk3oV_FSFDAQAB")
+  
+    // Windows Hello CA
+    let caCert = try! Certificate(
+        derEncoded: Array(Data(base64Encoded: "MIIF9TCCA92gAwIBAgIQXbYwTgy/J79JuMhpUB5dyzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjE2MDQGA1UEAxMtTWljcm9zb2Z0IFRQTSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDE0MB4XDTE0MTIxMDIxMzExOVoXDTM5MTIxMDIxMzkyOFowgYwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xNjA0BgNVBAMTLU1pY3Jvc29mdCBUUE0gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJ+n+bnKt/JHIRC/oI/xgkgsYdPzP0gpvduDA2GbRtth+L4WUyoZKGBw7uz5bjjP8Aql4YExyjR3EZQ4LqnZChMpoCofbeDR4MjCE1TGwWghGpS0mM3GtWD9XiME4rE2K0VW3pdN0CLzkYbvZbs2wQTFfE62yNQiDjyHFWAZ4BQH4eWa8wrDMUxIAneUCpU6zCwM+l6Qh4ohX063BHzXlTSTc1fDsiPaKuMMjWjK9vp5UHFPa+dMAWr6OljQZPFIg3aZ4cUfzS9y+n77Hs1NXPBn6E4Db679z4DThIXyoKeZTv1aaWOWl/exsDLGt2mTMTyykVV8uD1eRjYriFpmoRDwJKAEMOfaURarzp7hka9TOElGyD2gOV4Fscr2MxAYCywLmOLzA4VDSYLuKAhPSp7yawET30AvY1HRfMwBxetSqWP2+yZRNYJlHpor5QTuRDgzR+Zej+aWx6rWNYx43kLthozeVJ3QCsD5iEI/OZlmWn5WYf7O8LB/1A7scrYv44FD8ck3Z+hxXpkklAsjJMsHZa9mBqh+VR1AicX4uZG8m16x65ZU2uUpBa3rn8CTNmw17ZHOiuSWJtS9+PrZVA8ljgf4QgA1g6NPOEiLG2fn8Gm+r5Ak+9tqv72KDd2FPBJ7Xx4stYj/WjNPtEUhW4rcLK3ktLfcy6ea7Rocw5y5AgMBAAGjUTBPMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR6jArOL0hiF+KU0a5VwVLscXSkVjAQBgkrBgEEAYI3FQEEAwIBADANBgkqhkiG9w0BAQsFAAOCAgEAW4ioo1+J9VWC0UntSBXcXRm1ePTVamtsxVy/GpP4EmJd3Ub53JzNBfYdgfUL51CppS3ZY6BoagB+DqoA2GbSL+7sFGHBl5ka6FNelrwsH6VVw4xV/8klIjmqOyfatPYsz0sUdZev+reeiGpKVoXrK6BDnUU27/mgPtem5YKWvHB/soofUrLKzZV3WfGdx9zBr8V0xW6vO3CKaqkqU9y6EsQw34n7eJCbEVVQ8VdFd9iV1pmXwaBAfBwkviPTKEP9Cm+zbFIOLr3V3CL9hJj+gkTUuXWlJJ6wVXEG5i4rIbLAV59UrW4LonP+seqvWMJYUFxu/niF0R3fSGM+NU11DtBVkhRZt1u0kFhZqjDz1dWyfT/N7Hke3WsDqUFsBi+8SEw90rWx2aUkLvKo83oU4Mx4na+2I3l9F2a2VNGk4K7l3a00g51miPiq0Da0jqw30PaLluTMTGY5+RnZVh50JD6nk+Ea3wRkU8aiYFnpIxfKBZ72whmYYa/egj9IKeqpR0vuLebbU0fJBf880K1jWD3Z5SFyJXo057Mv0OPw5mttytE585ZIy5JsaRXlsOoWGRXE3kUT/MKR1UoAgR54c8Bsh+9Dq2wqIK9mRn15zvBDeyHG6+czurLopziOUeWokxZN1syrEdKlhFoPYavm6t+PzIcpdxZwHA+V3jLJPfI=")!)
+        )
+    
+    override func setUp() {
+        let configuration = WebAuthnManager.Configuration(
+            relyingPartyID: relyingPartyID,
+            relyingPartyName: relyingPartyDisplayName,
+            relyingPartyOrigin: relyingPartyOrigin
+        )
+        webAuthnManager = .init(configuration: configuration, challengeGenerator: .mock(generate: challenge))
+    }
+
+    func testInvalidVersion() async throws {
+        let mockCredentialPublicKey = TestCredentialPublicKeyBuilder().validMockRSA().buildAsByteArray()
+        let authData = TestAuthDataBuilder()
+            .relyingPartyIDHash(fromRelyingPartyID: relyingPartyID)
+            .flags(0b11000101)
+            .counter([0b00000000, 0b00000000, 0b00000000, 0b00000000])
+            .attestedCredData(credentialPublicKey: mockCredentialPublicKey)
+            .noExtensionData()
+        let mockAttestationObject = TestAttestationObjectBuilder()
+            .fmt(.tpm)
+            .authData(authData)
+            .attStmt(
+                .map([.utf8String("ver"): .utf8String("1.0")])
+            )
+            .build()
+            .cborEncoded
+        
+        await assertThrowsError(
+            try await finishRegistration(
+                challenge: challenge,
+                attestationObject: mockAttestationObject,
+                rootCertificatesByFormat: [.tpm: [caCert]]
+            ),
+            expect: WebAuthnError.tpmInvalidVersion
+        )
+    }
+    
+    func testInvalidPubArea() async throws {
+        let mockCredentialPublicKey = TestCredentialPublicKeyBuilder().validMockRSA().buildAsByteArray()
+        let mockCerts = try TestECCKeyPair.certificates()
+        let authData = TestAuthDataBuilder()
+            .relyingPartyIDHash(fromRelyingPartyID: relyingPartyID)
+            .flags(0b11000101)
+            .counter([0b00000000, 0b00000000, 0b00000000, 0b00000000])
+            .attestedCredData(credentialPublicKey: mockCredentialPublicKey)
+            .noExtensionData()
+        let mockAttestationObject = TestAttestationObjectBuilder()
+            .fmt(.tpm)
+            .authData(authData)
+            .attStmt(
+                .map([
+                    .utf8String("ver"): .utf8String("2.0"),
+                    .utf8String("x5c"): .array([.byteString(Array(mockCerts.leaf))]),
+                    .utf8String("pubArea"): .byteString([0x01])
+                ])
+            )
+            .build()
+            .cborEncoded
+        
+        await assertThrowsError(
+            try await finishRegistration(
+                challenge: challenge,
+                attestationObject: mockAttestationObject,
+                rootCertificatesByFormat: [.tpm: [mockCerts.ca]]
+            ),
+            expect: WebAuthnError.tpmInvalidPubArea
+        )
+    }
+
+    func testAttCAAttestationRSASucceeds() async throws {
+        let credential = try await finishRegistration(
+            challenge: challenge,
+            attestationObject: Array(Data(base64Encoded: attestationObjectBase64.urlDecoded.asString())!),
+            rootCertificatesByFormat: [.tpm: [caCert]]
+        )
+        XCTAssertEqual(credential.attestationResult.format, .tpm)
+        XCTAssertEqual(credential.attestationResult.type, .attCA)
+        XCTAssertEqual(credential.attestationResult.trustChain.count, 3)
+    }
+
+    private func finishRegistration(
+        challenge: [UInt8],
+        type: CredentialType = .publicKey,
+        rawID: [UInt8] = "e0fac9350509f71748d83782ccaf6b4c1462c615c70e255da1344e40887c8fcd".hexadecimal!,
+        attestationObject: [UInt8],
+        requireUserVerification: Bool = false,
+        rootCertificatesByFormat: [AttestationFormat: [Certificate]] = [:],
+        confirmCredentialIDNotRegisteredYet: (String) async throws -> Bool = { _ in true }
+    ) async throws -> Credential {
+        try await webAuthnManager.finishRegistration(
+            challenge: challenge,
+            credentialCreationData: RegistrationCredential(
+                id: rawID.base64URLEncodedString(),
+                type: type,
+                rawID: rawID,
+                attestationResponse: AuthenticatorAttestationResponse(
+                    clientDataJSON: Array(Data(base64Encoded: clientDataJSONBase64)!),
+                    attestationObject: attestationObject
+                )
+            ),
+            requireUserVerification: requireUserVerification,
+            rootCertificatesByFormat: rootCertificatesByFormat,
+            confirmCredentialIDNotRegisteredYet: confirmCredentialIDNotRegisteredYet
+        )
+    }
+    
+}
diff --git a/Tests/WebAuthnTests/Utils/TestModels/TestAuthData.swift b/Tests/WebAuthnTests/Utils/TestModels/TestAuthData.swift
index d0566a47..2ab55522 100644
--- a/Tests/WebAuthnTests/Utils/TestModels/TestAuthData.swift
+++ b/Tests/WebAuthnTests/Utils/TestModels/TestAuthData.swift
@@ -73,6 +73,20 @@ struct TestAuthDataBuilder {
             .extensions([UInt8](repeating: 0, count: 20))
     }
     
+    func validMockRSA() -> Self {
+        self
+            .relyingPartyIDHash(fromRelyingPartyID: "example.com")
+            .flags(0b11000101)
+            .counter([0b00000000, 0b00000000, 0b00000000, 0b00000000])
+            .attestedCredData(
+                aaguid: [UInt8](repeating: 0, count: 16),
+                credentialIDLength: [0b00000000, 0b00000001],
+                credentialID: [0b00000001],
+                credentialPublicKey: TestCredentialPublicKeyBuilder().validMockRSA().buildAsByteArray()
+            )
+            .extensions([UInt8](repeating: 0, count: 20))
+    }
+    
 
     /// Creates a valid authData
     ///
diff --git a/Tests/WebAuthnTests/Utils/TestModels/TestRSAKeyPair.swift b/Tests/WebAuthnTests/Utils/TestModels/TestRSAKeyPair.swift
index 7caf89ef..da8e25c2 100644
--- a/Tests/WebAuthnTests/Utils/TestModels/TestRSAKeyPair.swift
+++ b/Tests/WebAuthnTests/Utils/TestModels/TestRSAKeyPair.swift
@@ -117,7 +117,12 @@ struct TestRSAKeyPair {
                 CountryName("US")
             },
             signatureAlgorithm: .sha256WithRSAEncryption,
-            extensions: try Certificate.Extensions {Critical(BasicConstraints.notCertificateAuthority)},
+            extensions: try Certificate.Extensions {
+                Critical(BasicConstraints.notCertificateAuthority)
+                try ExtendedKeyUsage([
+                    .init(oid: .init(arrayLiteral: 2, 23, 133, 8, 3))
+                ])
+            },
             issuerPrivateKey: .init(pemEncoded: caPrivateKey.pemRepresentation)
         )
         var leafSerializer = DER.Serializer()

From 7e298de2c6aec91b09cf56d45a887eb9cc3a744f Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Sat, 11 May 2024 09:49:09 +1000
Subject: [PATCH 27/30] Cleanup

---
 .../PackedAttestationTests/PackedAttestationTests.swift     | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/Tests/WebAuthnTests/Formats/PackedAttestationTests/PackedAttestationTests.swift b/Tests/WebAuthnTests/Formats/PackedAttestationTests/PackedAttestationTests.swift
index a262898d..3d779846 100644
--- a/Tests/WebAuthnTests/Formats/PackedAttestationTests/PackedAttestationTests.swift
+++ b/Tests/WebAuthnTests/Formats/PackedAttestationTests/PackedAttestationTests.swift
@@ -255,7 +255,7 @@ final class RegistrationPackedAttestationTests: XCTestCase {
             )
             .build()
             .cborEncoded
-        
+
         let credential = try await finishRegistration(attestationObject: mockAttestationObject)
 
         XCTAssertEqual(credential.attestationResult.format, .packed)
@@ -284,7 +284,7 @@ final class RegistrationPackedAttestationTests: XCTestCase {
             )
             .build()
             .cborEncoded
-        
+
         let credential = try await finishRegistration(
             attestationObject: mockAttestationObject,
             rootCertificatesByFormat: [.packed: [mockCerts.ca]]
@@ -294,7 +294,7 @@ final class RegistrationPackedAttestationTests: XCTestCase {
         XCTAssertEqual(credential.attestationResult.type, .basicFull)
         XCTAssertEqual(credential.attestationResult.trustChain.count, 2)
     }
-    
+
     private func finishRegistration(
         challenge: [UInt8] = TestConstants.mockChallenge,
         type: CredentialType = .publicKey,

From 0966ce1421fc5d33d9cc3df6b8496f009df582c4 Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Sat, 11 May 2024 10:12:54 +1000
Subject: [PATCH 28/30] Address warnings

---
 .../Ceremonies/Registration/AttestedCredentialData.swift     | 2 +-
 .../Registration/Formats/PublicKey+verifySignature.swift     | 1 -
 .../Ceremonies/Registration/Formats/TPM/TPMAttestation.swift | 5 -----
 .../AndroidKeyAttestationTests.swift                         | 1 -
 4 files changed, 1 insertion(+), 8 deletions(-)

diff --git a/Sources/WebAuthn/Ceremonies/Registration/AttestedCredentialData.swift b/Sources/WebAuthn/Ceremonies/Registration/AttestedCredentialData.swift
index 432ab03c..44f783e5 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/AttestedCredentialData.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/AttestedCredentialData.swift
@@ -13,7 +13,7 @@
 //===----------------------------------------------------------------------===//
 
 // Contains the new public key created by the authenticator.
-public struct AttestedCredentialData: Equatable {
+public struct AttestedCredentialData: Equatable, Sendable {
     public let aaguid: [UInt8]
     public let credentialID: [UInt8]
     public let publicKey: [UInt8]
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
index f8a50ee2..1089417a 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
@@ -20,7 +20,6 @@ import _CryptoExtras
 
 extension Certificate.PublicKey {
     func verifySignature(_ signature: Data, algorithm: COSEAlgorithmIdentifier, data: Data) throws -> Bool {
-        print("\n •••• \(Self.self).verifySignature() 1, algorithm=\(algorithm)")
         switch algorithm {
         case .algES256:
             guard let key = P256.Signing.PublicKey(self) else {
diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
index 239492f0..0463ea70 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/TPM/TPMAttestation.swift
@@ -80,9 +80,6 @@ struct TPMAttestation: AttestationProtocol {
             }
         }
 
-        if let pubAreaCBOR = attStmt["pubArea"], case let .byteString(pubAreaRaw) = pubAreaCBOR {
-            let pubArea = PubArea(from: Data(pubAreaRaw))
-        }
         // Verify pubArea
         guard let pubAreaCBOR = attStmt["pubArea"],
             case let .byteString(pubAreaRaw) = pubAreaCBOR,
@@ -91,8 +88,6 @@ struct TPMAttestation: AttestationProtocol {
         }
         switch pubArea.parameters {
         case let .rsa(rsaParameters):
-            if case let .rsa(rsaPublicKeyData) = credentialPublicKey {
-            }
             guard case let .rsa(rsaPublicKeyData) = credentialPublicKey,
                 Array(pubArea.unique.data) == rsaPublicKeyData.n else {
                 throw WebAuthnError.tpmInvalidPubAreaPublicKey
diff --git a/Tests/WebAuthnTests/Formats/AndroidKeyAttestationTests/AndroidKeyAttestationTests.swift b/Tests/WebAuthnTests/Formats/AndroidKeyAttestationTests/AndroidKeyAttestationTests.swift
index 9597b56c..aa738673 100644
--- a/Tests/WebAuthnTests/Formats/AndroidKeyAttestationTests/AndroidKeyAttestationTests.swift
+++ b/Tests/WebAuthnTests/Formats/AndroidKeyAttestationTests/AndroidKeyAttestationTests.swift
@@ -142,7 +142,6 @@ final class RegistrationAndroidKeyAttestationTests: XCTestCase {
     
     func testPublicKeysMismatch() async throws {
         let mockCerts = try TestECCKeyPair.certificates()
-        let verificationData: [UInt8] = [0x01]
         let authData = TestAuthDataBuilder().validMockRSA()
         let clientDataHash = SHA256.hash(data: Data(mockClientDataJSONBytes))
         let mockAttestationObject = TestAttestationObjectBuilder()

From 17bbee9fd009ac7519768060fc850ecc2abfe234 Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Sat, 11 May 2024 11:03:34 +1000
Subject: [PATCH 29/30] Cleanup

---
 .../AttestationConveyancePreference.swift             |  2 +-
 .../AndroidKeyAttestationTests.swift                  | 11 +----------
 .../Utils/TestModels/TestCredentialPublicKey.swift    |  9 ---------
 3 files changed, 2 insertions(+), 20 deletions(-)

diff --git a/Sources/WebAuthn/Ceremonies/Registration/AttestationConveyancePreference.swift b/Sources/WebAuthn/Ceremonies/Registration/AttestationConveyancePreference.swift
index 69de1e0d..765c4f14 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/AttestationConveyancePreference.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/AttestationConveyancePreference.swift
@@ -18,7 +18,7 @@
 public enum AttestationConveyancePreference: String, Encodable {
     /// Indicates the Relying Party is not interested in authenticator attestation.
     case none
-    //case indirect
+    // case indirect
     case direct
     // case enterprise
 }
diff --git a/Tests/WebAuthnTests/Formats/AndroidKeyAttestationTests/AndroidKeyAttestationTests.swift b/Tests/WebAuthnTests/Formats/AndroidKeyAttestationTests/AndroidKeyAttestationTests.swift
index aa738673..9efb0dc3 100644
--- a/Tests/WebAuthnTests/Formats/AndroidKeyAttestationTests/AndroidKeyAttestationTests.swift
+++ b/Tests/WebAuthnTests/Formats/AndroidKeyAttestationTests/AndroidKeyAttestationTests.swift
@@ -171,16 +171,7 @@ final class RegistrationAndroidKeyAttestationTests: XCTestCase {
         )
     }
 
-    /*func testAttCAAttestationRSASucceeds() async throws {
-        let credential = try await finishRegistration(
-            challenge: challenge,
-            attestationObject: Array(Data(base64Encoded: attestationObjectBase64.urlDecoded.asString())!),
-            rootCertificatesByFormat: [.tpm: [caCert]]
-        )
-        XCTAssertEqual(credential.attestationResult.format, .tpm)
-        XCTAssertEqual(credential.attestationResult.type, .attCA)
-        XCTAssertEqual(credential.attestationResult.trustChain.count, 3)
-    }*/
+    // TODO: add test for successful attestation verification
 
     private func finishRegistration(
         challenge: [UInt8] = TestConstants.mockChallenge,
diff --git a/Tests/WebAuthnTests/Utils/TestModels/TestCredentialPublicKey.swift b/Tests/WebAuthnTests/Utils/TestModels/TestCredentialPublicKey.swift
index 8f556de8..1156ac7a 100644
--- a/Tests/WebAuthnTests/Utils/TestModels/TestCredentialPublicKey.swift
+++ b/Tests/WebAuthnTests/Utils/TestModels/TestCredentialPublicKey.swift
@@ -78,15 +78,6 @@ struct TestCredentialPublicKeyBuilder {
             .modulus(TestRSAKeyPair.publicKeyModulus)
             .exponent(TestRSAKeyPair.publicKeyExponent)
     }
-
-    func validMockEdDSA() -> Self {
-        return self
-            .kty(.octetKey)
-            .crv(.ed25519)
-            .alg(.algEdDSA)
-            .xCoordinate(TestECCKeyPair.publicKeyXCoordinate)
-            //.yCoordinate(TestECCKeyPair.publicKeyYCoordinate)
-    }
     
     func kty(_ kty: COSEKeyType) -> Self {
         var temp = self

From db54c7a175320b82d7f4184581c3d343cdca089b Mon Sep 17 00:00:00 2001
From: Matthieu Barthelemy <bonsouere@gmail.com>
Date: Sun, 12 May 2024 09:20:58 +1000
Subject: [PATCH 30/30] error type

---
 .../Registration/Formats/PublicKey+verifySignature.swift        | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift b/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
index 1089417a..b178c371 100644
--- a/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
+++ b/Sources/WebAuthn/Ceremonies/Registration/Formats/PublicKey+verifySignature.swift
@@ -57,7 +57,7 @@ extension Certificate.PublicKey {
             return key.isValidSignature(signature, for: data, padding: .PSS)
             
         default:
-            throw WebAuthnError.unsupported
+            throw WebAuthnError.unsupportedCOSEAlgorithm
         }
     }
 }