Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attestation verification #69

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
fb89843
WIP attestation
m-barthelemy Apr 25, 2024
d40d26f
WIP
m-barthelemy Apr 27, 2024
f399a3f
FidoU2FAttestation
m-barthelemy Apr 27, 2024
bce79f0
Cleanuo
m-barthelemy Apr 27, 2024
a316f13
Cleanuo
m-barthelemy Apr 27, 2024
c0b9c7d
Move verifySignature to dedicated extension file
m-barthelemy Apr 27, 2024
9733363
AttestationResult.swift
m-barthelemy Apr 28, 2024
7275dc3
Update TPM attestation; validate cert extension for .packed and .tpm
m-barthelemy Apr 28, 2024
5d10ea5
WIP TPM attestation
m-barthelemy Apr 29, 2024
3fafcf3
Certs veerifications
m-barthelemy Apr 30, 2024
1804502
Protocol for attestation verify(); add WIP AndroidKey attestation sup…
m-barthelemy May 1, 2024
c987f42
AndroidKey attestation support
m-barthelemy May 3, 2024
760f7a6
1 folder per attestation format
m-barthelemy May 3, 2024
124e615
Return attestation type
m-barthelemy May 3, 2024
1488ec0
Use X509 Certificate from swift-certificates
m-barthelemy May 4, 2024
57d05ad
Throw proper WebAuthnErrors
m-barthelemy May 4, 2024
ff2174d
Merge branch 'swift-server:main' into feature/attestation-improvements
m-barthelemy May 4, 2024
60b9211
Enable EdDSA/Ed25519 credential public keys
m-barthelemy May 4, 2024
8d0d3c3
Throw proper WebAuthnErrors
m-barthelemy May 5, 2024
609eb69
Merge branch 'feature/attestation-improvements' of github.com:nuw-run…
m-barthelemy May 5, 2024
ff94cd6
Enable EdDSA/Ed25519 credential public keys
m-barthelemy May 5, 2024
c63796c
Comment out public key verifySignature for RSA since unable to test it
m-barthelemy May 5, 2024
e6465cf
Validate EDDSA algorithm
m-barthelemy May 5, 2024
fe72a7f
Throw proper WebAuthnErrors
m-barthelemy May 5, 2024
e637a9c
Throw proper WebAuthnErrors
m-barthelemy May 5, 2024
f10b5da
Fix verifySignature
m-barthelemy May 5, 2024
d2c83eb
WIP tests
m-barthelemy May 9, 2024
1f9233d
Android attetsation tests
m-barthelemy May 10, 2024
7e298de
Cleanup
m-barthelemy May 10, 2024
6979837
Merge branch 'main' into feature/attestation-improvements
m-barthelemy May 10, 2024
0966ce1
Address warnings
m-barthelemy May 11, 2024
17bbee9
Cleanup
m-barthelemy May 11, 2024
db54c7a
error type
m-barthelemy May 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
],
Expand All @@ -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"),
],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency=complete")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ public enum AttestationConveyancePreference: String, Encodable {
/// Indicates the Relying Party is not interested in authenticator attestation.
case none
// case indirect
// case direct
case direct
// case enterprise
}
70 changes: 48 additions & 22 deletions Sources/WebAuthn/Ceremonies/Registration/AttestationObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,23 @@
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 {
let authenticatorData: AuthenticatorData
let rawAuthenticatorData: [UInt8]
let format: AttestationFormat
let attestationStatement: CBOR
var trustPath: [Certificate] = []

func verify(
relyingPartyID: String,
verificationRequired: Bool,
clientDataHash: SHA256.Digest,
supportedPublicKeyAlgorithms: [PublicKeyCredentialParameters],
pemRootCertificatesByFormat: [AttestationFormat: [Data]] = [:]
) async throws -> AttestedCredentialData {
rootCertificatesByFormat: [AttestationFormat: [Certificate]] = [:]
) async throws -> AttestationResult {
let relyingPartyIDHash = SHA256.hash(data: relyingPartyID.data(using: .utf8)!)

guard relyingPartyIDHash == authenticatorData.relyingPartyIDHash else {
Expand All @@ -56,34 +58,58 @@ public struct AttestationObject {
throw WebAuthnError.unsupportedCredentialPublicKeyAlgorithm
}

// let pemRootCertificates = pemRootCertificatesByFormat[format] ?? []
let rootCertificates = rootCertificatesByFormat[format] ?? []
var attestationType: AttestationResult.AttestationType = .none
var trustedPath: [Certificate] = []

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 .tpm:
// try TPMAttestation.verify(
// attStmt: attestationStatement,
// authenticatorData: rawAuthenticatorData,
// attestedCredentialData: attestedCredentialData,
// clientDataHash: Data(clientDataHash),
// credentialPublicKey: credentialPublicKey,
// pemRootCertificates: pemRootCertificates
// )
case .packed:
(attestationType, trustedPath) = try await PackedAttestation.verify(
attStmt: attestationStatement,
authenticatorData: authenticatorData,
clientDataHash: Data(clientDataHash),
credentialPublicKey: credentialPublicKey,
rootCertificates: rootCertificates
)
case .tpm:
(attestationType, trustedPath) = try await TPMAttestation.verify(
attStmt: attestationStatement,
authenticatorData: authenticatorData,
clientDataHash: Data(clientDataHash),
credentialPublicKey: credentialPublicKey,
rootCertificates: rootCertificates
)
case .androidKey:
(attestationType, trustedPath) = try await AndroidKeyAttestation.verify(
attStmt: attestationStatement,
authenticatorData: authenticatorData,
clientDataHash: Data(clientDataHash),
credentialPublicKey: credentialPublicKey,
rootCertificates: rootCertificates
)
// Legacy format used mostly by older authenticators
case .fidoU2F:
(attestationType, trustedPath) = try await FidoU2FAttestation.verify(
attStmt: attestationStatement,
authenticatorData: authenticatorData,
clientDataHash: Data(clientDataHash),
credentialPublicKey: credentialPublicKey,
rootCertificates: rootCertificates
)
default:
throw WebAuthnError.attestationVerificationNotSupported
}

return attestedCredentialData

return AttestationResult(
format: format,
type: attestationType,
trustChain: trustedPath,
attestedCredentialData: attestedCredentialData
)
}
}
33 changes: 33 additions & 0 deletions Sources/WebAuthn/Ceremonies/Registration/AttestationResult.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//===----------------------------------------------------------------------===//
//
// 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 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 format: AttestationFormat
public let type: AttestationType
public let trustChain: [Certificate]

public let attestedCredentialData: AttestedCredentialData
}
Original file line number Diff line number Diff line change
Expand Up @@ -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, Sendable {
public let aaguid: [UInt8]
public let credentialID: [UInt8]
public let publicKey: [UInt8]
}
2 changes: 1 addition & 1 deletion Sources/WebAuthn/Ceremonies/Registration/Credential.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public struct Credential {

// MARK: Optional content

public let attestationObject: AttestationObject
public let attestationResult: AttestationResult

public let attestationClientDataJSON: CollectedClientData
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//===----------------------------------------------------------------------===//
//
// 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

// https://www.w3.org/TR/webauthn-2/#sctn-android-key-attestation
struct AndroidKeyAttestation: AttestationProtocol {
static func verify(
attStmt: CBOR,
authenticatorData: AuthenticatorData,
clientDataHash: Data,
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
}

guard let x5cCBOR = attStmt["x5c"], case let .array(x5cCBOR) = x5cCBOR else {
throw WebAuthnError.invalidAttestationCertificate
}

let x5c: [Certificate] = try x5cCBOR.map {
guard case let .byteString(certificate) = $0 else {
throw WebAuthnError.invalidAttestationCertificate
}
return try Certificate(derEncoded: certificate)
}

guard let leafCertificate = x5c.first else { throw WebAuthnError.invalidAttestationCertificate }
let verificationData = authenticatorData.rawData + clientDataHash
// Verify signature
let leafCertificatePublicKey: Certificate.PublicKey = leafCertificate.publicKey
guard try leafCertificatePublicKey.verifySignature(
Data(sig),
algorithm: alg,
data: verificationData) else {
throw WebAuthnError.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 WebAuthnError.attestationPublicKeyMismatch
}

let intermediates = CertificateStore(x5c[1...])
let rootCertificatesStore = CertificateStore(rootCertificates)
var verifier = Verifier(rootCertificates: rootCertificatesStore) {
AndroidKeyVerificationPolicy(clientDataHash: clientDataHash)
}
let verifierResult: VerificationResult = await verifier.validate(
leafCertificate: leafCertificate,
intermediates: intermediates
)
guard case .validCertificate(let chain) = verifierResult else {
throw WebAuthnError.invalidTrustPath
}

return (.basicFull, chain)
}
}

Loading