Skip to content

Commit

Permalink
Initial implementation for Apple anonymous attestation
Browse files Browse the repository at this point in the history
  • Loading branch information
aseigler committed Oct 7, 2020
1 parent d4ff95c commit 0d6410c
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 3 deletions.
92 changes: 92 additions & 0 deletions Src/Fido2/AttestationFormat/Apple.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Asn1;
using Fido2NetLib.Objects;
using PeterO.Cbor;

namespace Fido2NetLib
{
internal class Apple : AttestationVerifier
{
public static byte[] AppleAttestationExtensionBytes(X509ExtensionCollection exts)
{
foreach (var ext in exts)
{
if (ext.Oid.Value.Equals("1.2.840.113635.100.8.2")) // AppleAttestationRecordOid
{
var appleAttestationASN = AsnElt.Decode(ext.RawData);
appleAttestationASN.CheckConstructed();
appleAttestationASN.CheckTag(AsnElt.SEQUENCE);
appleAttestationASN.CheckNumSub(1);

var sequence = appleAttestationASN.GetSub(0);
sequence.CheckConstructed();
sequence.CheckNumSub(1);

var context = sequence.GetSub(0);
context.CheckPrimitive();
context.CheckTag(AsnElt.OCTET_STRING);

return context.GetOctetString();
}
}
return null;
}

public override (AttestationType, X509Certificate2[]) Verify()
{
// 1. Verify that attStmt is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields.
// (handled in base class)

// 2. Verify x5c is a valid certificate chain starting from the credCert to the Apple WebAuthn root certificate.
// https://www.apple.com/certificateauthority/Apple_WebAuthn_Root_CA.pem
var appleWebAuthnRoots = new string[] {
"MIICEjCCAZmgAwIBAgIQaB0BbHo84wIlpQGUKEdXcTAKBggqhkjOPQQDAzBLMR8w" +
"HQYDVQQDDBZBcHBsZSBXZWJBdXRobiBSb290IENBMRMwEQYDVQQKDApBcHBsZSBJ" +
"bmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMB4XDTIwMDMxODE4MjEzMloXDTQ1MDMx" +
"NTAwMDAwMFowSzEfMB0GA1UEAwwWQXBwbGUgV2ViQXV0aG4gUm9vdCBDQTETMBEG" +
"A1UECgwKQXBwbGUgSW5jLjETMBEGA1UECAwKQ2FsaWZvcm5pYTB2MBAGByqGSM49" +
"AgEGBSuBBAAiA2IABCJCQ2pTVhzjl4Wo6IhHtMSAzO2cv+H9DQKev3//fG59G11k" +
"xu9eI0/7o6V5uShBpe1u6l6mS19S1FEh6yGljnZAJ+2GNP1mi/YK2kSXIuTHjxA/" +
"pcoRf7XkOtO4o1qlcaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUJtdk" +
"2cV4wlpn0afeaxLQG2PxxtcwDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA" +
"MGQCMFrZ+9DsJ1PW9hfNdBywZDsWDbWFp28it1d/5w2RPkRX3Bbn/UbDTNLx7Jr3" +
"jAGGiQIwHFj+dJZYUJR786osByBelJYsVZd2GbHQu209b5RCmGQ21gpSAk9QZW4B" +
"1bWeT0vT"};

var trustPath = X5c.Values
.Select(x => new X509Certificate2(x.GetByteString()))
.ToArray();

var appleWebAuthnRootCerts = appleWebAuthnRoots
.Select(x => new X509Certificate2(Convert.FromBase64String(x)))
.ToArray();

if (!CryptoUtils.ValidateTrustChain(trustPath, appleWebAuthnRootCerts))
throw new Fido2VerificationException("Invalid certificate chain in Apple attestation");

// 3. Concatenate authenticatorData and clientDataHash to form nonceToHash.
var nonceToHash = Data;

// 4. Perform SHA-256 hash of nonceToHash to produce nonce.
var nonce = CryptoUtils.GetHasher(HashAlgorithmName.SHA256).ComputeHash(nonceToHash);

// 5. Verify nonce matches the value of the extension with OID ( 1.2.840.113635.100.8.2 ) in credCert.
var credCert = trustPath[0];
if (!nonce.SequenceEqual(AppleAttestationExtensionBytes(credCert.Extensions)))
throw new Fido2VerificationException("Mismatch between nonce and credCert attestation extension in Apple attestation");

// 6. Verify credential public key matches the Subject Public Key of credCert.
var coseAlg = CredentialPublicKey[CBORObject.FromObject(COSE.KeyCommonParameter.Alg)].AsInt32();
var cpk = new CredentialPublicKey(credCert, coseAlg);

if (!cpk.GetBytes().SequenceEqual(AuthData.AttestedCredentialData.CredentialPublicKey.GetBytes()))
throw new Fido2VerificationException("Credential public key in Apple attestation does not match subject public key of credCert");

// 7. If successful, return implementation-specific values representing attestation type Anonymous CA and attestation trust path x5c.
return (AttestationType.Basic, trustPath);
}
}
}
3 changes: 0 additions & 3 deletions Src/Fido2/AttestationFormat/AttestationFormat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,6 @@ internal static int U2FTransportsFromAttnCert(X509ExtensionCollection exts)
}
return u2ftransports;
}



public virtual (AttestationType, X509Certificate2[]) Verify(CBORObject attStmt, byte[] authenticatorData, byte[] clientDataHash)
{
this.attStmt = attStmt;
Expand Down
1 change: 1 addition & 0 deletions Src/Fido2/AuthenticatorAttestationResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ public async Task<AttestationVerificationSuccess> VerifyAsync(CredentialCreateOp
"android-safetynet" => new AndroidSafetyNet(), // https://www.w3.org/TR/webauthn/#android-safetynet-attestation
"fido-u2f" => new FidoU2f(), // https://www.w3.org/TR/webauthn/#fido-u2f-attestation
"packed" => new Packed(), // https://www.w3.org/TR/webauthn/#packed-attestation
"apple" => new Apple(), // https://www.w3.org/TR/webauthn/#apple-anonymous-attestation
_ => throw new Fido2VerificationException("Missing or unknown attestation type"),
};

Expand Down
13 changes: 13 additions & 0 deletions Test/Attestation/Apple.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using fido2_net_lib.Test;
using PeterO.Cbor;

namespace Test.Attestation
{
public class Apple : Fido2Tests.Attestation
{
public Apple()
{
_attestationObject = CBORObject.NewMap().Add("fmt", "apple");
}
}
}
13 changes: 13 additions & 0 deletions Test/Fido2Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,20 @@ public async Task TestAndroidKeyAttestationAsync()
await o.VerifyAsync(options, _config, (x) => Task.FromResult(true), _metadataService, null);
byte[] ad = o.AttestationObject.AuthData;
// TODO : Why read ad ? Is the test finished ?
}

[Fact]
public async Task TestAppleAttestationAsync()
{
var jsonPost = JsonConvert.DeserializeObject<AuthenticatorAttestationRawResponse>(File.ReadAllText("./attestationAppleResponse.json"));
var options = JsonConvert.DeserializeObject<CredentialCreateOptions>(File.ReadAllText("./attestationAppleOptions.json"));
var o = AuthenticatorAttestationResponse.Parse(jsonPost);
var config = new Fido2Configuration { Origin = "https://6cc3c9e7967a.ngrok.io" };
await o.VerifyAsync(options, config, (x) => Task.FromResult(true), _metadataService, null);
byte[] ad = o.AttestationObject.AuthData;
// TODO : Why read ad ? Is the test finished ?
}

[Fact]
public async Task TaskPackedAttestation512()
{
Expand Down
28 changes: 28 additions & 0 deletions Test/TestFiles/attestationAppleOptions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"status": "ok",
"errorMessage": "",
"rp": {
"name": "Fido2 test",
"id": "6cc3c9e7967a.ngrok.io"
},
"user": {
"name": "KXDmnFe45QYAngSfMY9R",
"id": "KXDmnFe45QYAngSfMY9R",
"displayName": "Eleanor Duchene"
},
"challenge": "kOwMvE2mQO6ou0B0jjD0VA",
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -7
},
{
"type": "public-key",
"alg": -257
}
],
"timeout": 60000,
"attestation": "direct",
"authenticatorSelection": null,
"excludeCredentials": []
}
8 changes: 8 additions & 0 deletions Test/TestFiles/attestationAppleResponse.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"rawId": "U5cxFNxLbU9-SAi1K7k9atYwXhghkAMbxpL__VPtBlw",
"id": "U5cxFNxLbU9-SAi1K7k9atYwXhghkAMbxpL__VPtBlw",
"response": {
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoia093TXZFMm1RTzZvdTBCMGpqRDBWQSIsIm9yaWdpbiI6Imh0dHBzOi8vNmNjM2M5ZTc5NjdhLm5ncm9rLmlvIn0",
"attestationObject": "o2NmbXRlYXBwbGVnYXR0U3RtdKJjYWxnJmN4NWOCWQJIMIICRDCCAcmgAwIBAgIGAXUCfWGDMAoGCCqGSM49BAMCMEgxHDAaBgNVBAMME0FwcGxlIFdlYkF1dGhuIENBIDExEzARBgNVBAoMCkFwcGxlIEluYy4xEzARBgNVBAgMCkNhbGlmb3JuaWEwHhcNMjAxMDA3MDk0NjEyWhcNMjAxMDA4MDk1NjEyWjCBkTFJMEcGA1UEAwxANjEyNzZmYzAyZDNmZThkMTZiMzNiNTU0OWQ4MTkyMzZjODE3NDZhODNmMmU5NGE2ZTRiZWUxYzcwZjgxYjViYzEaMBgGA1UECwwRQUFBIENlcnRpZmljYXRpb24xEzARBgNVBAoMCkFwcGxlIEluYy4xEzARBgNVBAgMCkNhbGlmb3JuaWEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAR5_lkIu1EpyAk4t1TATSs0DvpmFbmHaYv1naTlPqPm_vsD2qEnDVgE6KthwVqsokNcfb82nXHKFcUjsABKG3W3o1UwUzAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB_wQEAwIE8DAzBgkqhkiG92NkCAIEJjAkoSIEIJxgAhVAs-GYNN_jfsYkRcieGylPeSzka5QTwyMO84aBMAoGCCqGSM49BAMCA2kAMGYCMQDaHBjrI75xAF7SXzyF5zSQB_Lg9PjTdyye-w7stiqy84K6lmo8d3fIptYjLQx81bsCMQCvC8MSN-aewiaU0bMsdxRbdDerCJJj3xJb3KZwloevJ3daCmCcrZrAPYfLp2kDOshZAjgwggI0MIIBuqADAgECAhBWJVOVx6f7QOviKNgmCFO2MAoGCCqGSM49BAMDMEsxHzAdBgNVBAMMFkFwcGxlIFdlYkF1dGhuIFJvb3QgQ0ExEzARBgNVBAoMCkFwcGxlIEluYy4xEzARBgNVBAgMCkNhbGlmb3JuaWEwHhcNMjAwMzE4MTgzODAxWhcNMzAwMzEzMDAwMDAwWjBIMRwwGgYDVQQDDBNBcHBsZSBXZWJBdXRobiBDQSAxMRMwEQYDVQQKDApBcHBsZSBJbmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEgy6HLyYUkYECJbn1_Na7Y3i19V8_ywRbxzWZNHX9VJBE35v-GSEXZcaaHdoFCzjUUINAGkNPsk0RLVbD4c-_y5iR_sBpYIG--Wy8d8iN3a9Gpa7h3VFbWvqrk76cCyaRo2YwZDASBgNVHRMBAf8ECDAGAQH_AgEAMB8GA1UdIwQYMBaAFCbXZNnFeMJaZ9Gn3msS0Btj8cbXMB0GA1UdDgQWBBTrroLE_6GsW1HUzyRhBQC-Y713iDAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIxAN2LGjSBpfrZ27TnZXuEHhRMJ7dbh2pBhsKxR1dQM3In7-VURX72SJUMYy5cSD5wwQIwLIpgRNwgH8_lm8NNKTDBSHhR2WDtanXx60rKvjjNJbiX0MgFvvDH94sHpXHG6A4HaGF1dGhEYXRhWJhWHo8_bWPQzAMKYRIrGXu__PkMUfuqHM4RH7Jea4WDgkUAAAAAAAAAAAAAAAAAAAAAAAAAAAAUomGfdaNI-cYgWrq2klNk97zkcg-lAQIDJiABIVggef5ZCLtRKcgJOLdUwE0rNA76ZhW5h2mL9Z2k5T6j5v4iWCD7A9qhJw1YBOirYcFarKJDXH2_Np1xyhXFI7AASht1tw"},
"type": "public-key"
}

0 comments on commit 0d6410c

Please sign in to comment.