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!
}
}
}
}