Section 9/161 menit

9. Attestation dan Verification

9. Attestation dan Verification

Attestation Format

Saat registrasi dengan attestationPreference = .direct, server mendapat bukti autentikator yang digunakan.

swift
// Request dengan attestation
let request = provider.createCredentialRegistrationRequest(
    challenge: challenge,
    name: username,
    userID: userID
)
request.attestationPreference = .direct  // Minta attestation lengkap
// .none — tidak perlu attestation (default, lebih privat)
// .indirect — gunakan anonymization CA (privacy-preserving)
// .direct — attestation penuh dari manufacturer

// Response berisi:
// - rawAttestationObject: Data (CBOR-encoded)
// - rawClientDataJSON: Data (JSON)
// - credentialID: Data

Parsing Attestation Object (Ilustrasi)

swift
// AttestationObject structure (CBOR):
// {
//   "fmt": "packed" | "tpm" | "apple" | "none" | ...
//   "attStmt": { ... format-specific statement ... }
//   "authData": bytes (authenticator data)
// }

struct AttestationVerifier {
    func verify(
        attestationObject: Data,
        clientDataJSON: Data,
        expectedChallenge: Data,
        expectedRPID: String
    ) throws -> VerifiedRegistration {
        // 1. Decode CBOR attestation object
        let attestation = try CBORDecoder().decode(AttestationObject.self, from: attestationObject)

        // 2. Verify clientDataJSON
        let clientData = try JSONDecoder().decode(ClientData.self, from: clientDataJSON)
        guard clientData.type == "webauthn.create" else { throw VerifyError.invalidType }

        let challengeBase64 = expectedChallenge.base64URLEncodedString()
        guard clientData.challenge == challengeBase64 else { throw VerifyError.challengeMismatch }

        // 3. Parse authenticatorData dari attestation
        let authData = try AuthenticatorData(from: attestation.authData)

        // 4. Verify RP ID hash
        let expectedRPIdHash = SHA256.hash(data: Data(expectedRPID.utf8))
        guard authData.rpIdHash == Data(expectedRPIdHash) else { throw VerifyError.rpIdMismatch }

        // 5. Verify UP flag (user was present)
        guard authData.flags.contains(.userPresent) else { throw VerifyError.noUserPresence }

        // 6. Verify UV flag jika diperlukan
        // guard authData.flags.contains(.userVerified) else { throw VerifyError.noUserVerification }

        // 7. Verifikasi format-spesifik attestation statement
        // (Apple attestation, packed, dsb)
        try verifyAttestationStatement(
            fmt: attestation.fmt,
            attStmt: attestation.attStmt,
            authData: attestation.authData,
            clientDataHash: SHA256.hash(data: clientDataJSON)
        )

        return VerifiedRegistration(
            credentialId: authData.credentialId,
            publicKey: authData.credentialPublicKey,
            signCount: authData.signCount,
            aaguid: authData.aaguid
        )
    }
}

Assertion Verification

swift
// Verification assertion di server
struct AssertionVerifier {
    func verify(
        assertion: AssertionResponse,
        storedCredential: StoredCredential,
        expectedChallenge: Data,
        expectedRPID: String
    ) throws {
        // 1. Verify clientData
        let clientData = try JSONDecoder().decode(ClientData.self, from: assertion.clientDataJSON)
        guard clientData.type == "webauthn.get" else { throw VerifyError.invalidType }

        let challengeBase64 = expectedChallenge.base64URLEncodedString()
        guard clientData.challenge == challengeBase64 else { throw VerifyError.challengeMismatch }

        // 2. Verify authenticatorData
        let authData = try AuthenticatorData(from: assertion.authenticatorData)
        let expectedRPIdHash = SHA256.hash(data: Data(expectedRPID.utf8))
        guard authData.rpIdHash == Data(expectedRPIdHash) else { throw VerifyError.rpIdMismatch }
        guard authData.flags.contains(.userPresent) else { throw VerifyError.noUserPresence }

        // 3. Verify signature
        let clientDataHash = SHA256.hash(data: assertion.clientDataJSON)
        let signedData = assertion.authenticatorData + Data(clientDataHash)

        guard verifySignature(
            signature: assertion.signature,
            data: signedData,
            publicKey: storedCredential.publicKey
        ) else {
            throw VerifyError.invalidSignature
        }

        // 4. Verify sign count (anti-cloning)
        if storedCredential.signCount > 0 || authData.signCount > 0 {
            guard authData.signCount > storedCredential.signCount else {
                throw VerifyError.signCountDecreased  // Possible cloned credential!
            }
        }
    }
}