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

Apple AppAttest Attestations can fail due to incorrect comparison of credential public key with subject public key of credCert #552

Closed
rorydpayne opened this issue Oct 16, 2024 · 7 comments · Fixed by #571
Assignees
Milestone

Comments

@rorydpayne
Copy link

In some instances AppAttest attestation objects fail to validate when they should succeed. The final validation in Apple.VerifyAsync compares the values of the attested credential data subject public key with the public key value of the credCert in the x5c and can produce a false negative in the form of a Fido2VerificationException.

As far as I can tell, the problem emerges because on the one hand a CredentialPublicKey is constructed from a CborMap when parsing attested credential data. On the other a CredentialPublicKey is constructed from an X509Certificate2. For an X509Certficate2 with an ECDsa public key, the exported parameters will always contain X and Y coordinates as byte[] values with a fixed length (e.g. 32 bytes for P-256 etc). When parsing the attested credential data, the same EC coordinates can end up being represented with byte[] values of varying length instead of being padded out to a fixed key length. Thus comparing the encoded values of each CredentialPublicKey instance we can produce a false negative.

The issue can be reproduced in a test like this one using two AppAttest attestations as inputs.

[Theory]
[InlineData("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")]
[InlineData("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")]
public void AttestationCredCertPublicKeyShouldMatchSubjectPublicKey(string encodedAttestation)
{
    var attestationObject = AttestationObject.FromBase64String(encodedAttestation);

    var credCert = new X509Certificate2((byte[])(attestationObject.AttestationStatement["x5c"] as CborArray)![0]);
    var certPublicKey = new CredentialPublicKey(credCert, ES256);

    var parsedAuthData = AuthenticatorData.Parse(attestationObject.AuthenticatorData);
    var attestedPublicKey = parsedAuthData.AttestedCredentialData!.CredentialPublicKey;

    Assert.True(certPublicKey.GetBytes().AsSpan().SequenceEqual(attestedPublicKey.GetBytes().AsSpan()));
}
@abergs
Copy link
Collaborator

abergs commented Oct 18, 2024

Ahhh, good find @rorydpayne. We're in the midst of landing a large PR, so a PR to fix this would be accepted after we've managed to merge #531

@abergs
Copy link
Collaborator

abergs commented Oct 18, 2024

Just FYI: #531 has now been merged.

@abergs
Copy link
Collaborator

abergs commented Oct 29, 2024

@rorydpayne I'm open to reviewing a PR that adreses this!

@abergs abergs added this to the Version 4 milestone Oct 29, 2024
@aseigler aseigler self-assigned this Oct 31, 2024
@aseigler
Copy link
Collaborator

Oh goodie, the IEEE P-1363 vs ASN.1 problem again, just backwards!

@aseigler
Copy link
Collaborator

aseigler commented Nov 1, 2024

Ok, so my knee jerk reaction was to think this was IEEE P-1363 vs ASN.1 encoding issue. This is because of heartburn from previous issues with ECDSA-Sig-Value from RFC5480 ASN.1 Module:

   -- ECDSA

   ECDSA-Sig-Value ::= SEQUENCE {
     r  INTEGER,
     s  INTEGER
   }

When dealing with a structure like that, the r and s values are encoded as a sequence of integers with ASN.1 encoding rules, sometimes there is a leading 00 byte that has to be dealt with. See this SO question for gory details on that.

However, in the case at hand, ECDSA-Sig-Value is not in play, on one hand it's a COSE key within the attestation object (basically attestationOjbect["authData"].AttestedCredentialData.CredentialPublicKey, I will call this Key1) and on the other an ECPoint within an X.509 certificate (basically attestationObject["attStmt"]["x5c"][0].tbsCertificate.subjectPublicKeyInfo.subjectPublicKey, which I will call Key2). These two are compared as step 5 in the Apple Anonymous Attestation Statement Format verification procedure.

So what's going on here? Of the samples provided, the second one is the one with the issue, so let's tear that apart and see what we've got. First, let's take that attestation object over to CyberChef to base64 decode and turn into hex. If we take the output of that over to cbor.me and plug it into the right pane, we can get the stuff we're looking for, which is ["authData"] where we'll get Key1 and ["attStmt"]["x5c"][0] were we'll find Key2.

Those are:

"authData": h'767A668FACF09FA300CF86E400E87B40A8B84F096CB6EBAC1BD2CA0C3824FB7A400000000061707061747465737400000000000000002099CD68E0684153E91E63329EEA1C1A7CC2A97BC0400862D3CB087C6887C3AE8DA501020326200121581F6F56E6590BD91D39744F83A820E8B3FBB6608DA583794091538296D1DA73E2225820B0A65E0B18D3189DA3B4A7036202ADF65A6B68EFF8C24825532D7A04386AE628'

and

"x5c": h'308202F030820275A003020102020601928A85F6A2300A06082A8648CE3D040302304F3123302106035504030C1A4170706C6520417070204174746573746174696F6E204341203131133011060355040A0C0A4170706C6520496E632E3113301106035504080C0A43616C69666F726E6961301E170D3234313031333130313434385A170D3235313031303133313434385A3081913149304706035504030C4039396364363865303638343135336539316536333332396565613163316137636332613937626330343030383632643363623038376336383837633361653864311A3018060355040B0C114141412043657274696669636174696F6E31133011060355040A0C0A4170706C6520496E632E3113301106035504080C0A43616C69666F726E69613059301306072A8648CE3D020106082A8648CE3D03010703420004006F56E6590BD91D39744F83A820E8B3FBB6608DA583794091538296D1DA73E2B0A65E0B18D3189DA3B4A7036202ADF65A6B68EFF8C24825532D7A04386AE628A381F93081F6300C0603551D130101FF04023000300E0603551D0F0101FF0404030204F0307906092A864886F763640805046C306AA40302010ABF893003020101BF893103020100BF893203020101BF893303020101BF89341A041842583646414A364B33322E636F6D2E7A6F70612E7A656F73A5060404736B7320BF893603020105BF893703020100BF893903020100BF893A03020100BF893B03020100302606092A864886F76364080704193017BF8A7808040631362E302E33BF885007020500FFFFFFFF303306092A864886F76364080204263024A1220420925F5F46F9F362516CFEC0D03B525F1A2E764B55079B4C452534DB5B0F608431300A06082A8648CE3D0403020369003066023100B371AA31432C66AFE964DCF66A706E5593EF539B8B69C01CA1B9A9623CB8D13E34106644619D79FAF2F6260B102C156A023100A556A693459B2258EB1A5167AD0711A4440F928C35922EBB2FFBEC0F42935BD8D0176CB40ACEE943876F909B31A51007'

Breaking down authData manually:

767A668FACF09FA300CF86E400E87B40A8B84F096CB6EBAC1BD2CA0C3824FB7A // rdIdHash
40 // flags
00000000 // signCount
// attestedCredentialData
61707061747465737400000000000000002099CD68E0684153E91E63329EEA1C1A7CC2A97BC0400862D3CB087C6887C3AE8DA501020326200121581F6F56E6590BD91D39744F83A820E8B3FBB6608DA583794091538296D1DA73E2225820B0A65E0B18D3189DA3B4A7036202ADF65A6B68EFF8C24825532D7A04386AE628 

Since flags has AT but not ED the rest we know is the attestedCredentialData with no extensions data. Breaking down the attestedCredentialData manually:

61707061747465737400000000000000 // aaguid
0020 // credentialIdLength
99CD68E0684153E91E63329EEA1C1A7CC2A97BC0400862D3CB087C6887C3AE8D // credentialId
 // credentialPublicKey
A501020326200121581F6F56E6590BD91D39744F83A820E8B3FBB6608DA583794091538296D1DA73E2225820B0A65E0B18D3189DA3B4A7036202ADF65A6B68EFF8C24825532D7A04386AE628

Now if we take that credentialPublicKey value and break it down, we get:

A5          // CBOR map, 5 items
   01 02    // kty (1), EC2
   03 26    // alg (3), ES256
   20 01    // crv (-1), P-256 curve
            // x-coordinate (-2), byte string (0x58), 31 (0x1F) bytes in length
   21 58 1F 6F56E6590BD91D39744F83A820E8B3FBB6608DA583794091538296D1DA73E2
            // y-coordinate (-3), byte string (0x58), 32 (0x20) bytes in length
   22 58 20 B0A65E0B18D3189DA3B4A7036202ADF65A6B68EFF8C24825532D7A04386AE628

So that's Key1. Next let's feed ["x5c"][0] to CyberChef we can get the cert in PEM format, and if we feed that to openssl we can get the public key details:

$ openssl x509 -inform PEM -pubkey -noout | openssl ec -inform PEM -pubin -text -noout
read EC key
-----BEGIN CERTIFICATE-----
MIIC8DCCAnWgAwIBAgIGAZKKhfaiMAoGCCqGSM49BAMCME8xIzAhBgNVBAMMGkFw
cGxlIEFwcCBBdHRlc3RhdGlvbiBDQSAxMRMwEQYDVQQKDApBcHBsZSBJbmMuMRMw
EQYDVQQIDApDYWxpZm9ybmlhMB4XDTI0MTAxMzEwMTQ0OFoXDTI1MTAxMDEzMTQ0
OFowgZExSTBHBgNVBAMMQDk5Y2Q2OGUwNjg0MTUzZTkxZTYzMzI5ZWVhMWMxYTdj
YzJhOTdiYzA0MDA4NjJkM2NiMDg3YzY4ODdjM2FlOGQxGjAYBgNVBAsMEUFBQSBD
ZXJ0aWZpY2F0aW9uMRMwEQYDVQQKDApBcHBsZSBJbmMuMRMwEQYDVQQIDApDYWxp
Zm9ybmlhMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAG9W5lkL2R05dE+DqCDo
s/u2YI2lg3lAkVOCltHac+Kwpl4LGNMYnaO0pwNiAq32Wmto7/jCSCVTLXoEOGrm
KKOB+TCB9jAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIE8DB5BgkqhkiG92Nk
CAUEbDBqpAMCAQq/iTADAgEBv4kxAwIBAL+JMgMCAQG/iTMDAgEBv4k0GgQYQlg2
RkFKNkszMi5jb20uem9wYS56ZW9zpQYEBHNrcyC/iTYDAgEFv4k3AwIBAL+JOQMC
AQC/iToDAgEAv4k7AwIBADAmBgkqhkiG92NkCAcEGTAXv4p4CAQGMTYuMC4zv4hQ
BwIFAP////8wMwYJKoZIhvdjZAgCBCYwJKEiBCCSX19G+fNiUWz+wNA7Ul8aLnZL
VQebTEUlNNtbD2CEMTAKBggqhkjOPQQDAgNpADBmAjEAs3GqMUMsZq/pZNz2anBu
VZPvU5uLacAcobmpYjy40T40EGZEYZ15+vL2JgsQLBVqAjEApVamk0WbIljrGlFn
rQcRpEQPkow1ki67L/vsD0KTW9jQF2y0Cs7pQ4dvkJsxpRAH
-----END CERTIFICATE-----
Public-Key: (256 bit)
pub:
   04:00:6f:56:e6:59:0b:d9:1d:39:74:4f:83:a8:20:
   e8:b3:fb:b6:60:8d:a5:83:79:40:91:53:82:96:d1:
   da:73:e2:b0:a6:5e:0b:18:d3:18:9d:a3:b4:a7:03:
   62:02:ad:f6:5a:6b:68:ef:f8:c2:48:25:53:2d:7a:
   04:38:6a:e6:28
ASN1 OID: prime256v1
NIST CURVE: P-256

Removing the colons and turning that public key into a string, we get our ECPoint:

04006f56e6590bd91d39744f83a820e8b3fbb6608da583794091538296d1da73e2b0a65e0b18d3189da3b4a7036202adf65a6b68eff8c24825532d7a04386ae628

The leading 04 indicates we are looking at an uncompressed form of ECPoint, so stripping that away leaves us with Key2, which I have split into two lines for viewing clarity:

006f56e6590bd91d39744f83a820e8b3fbb6608da583794091538296d1da73e2
b0a65e0b18d3189da3b4a7036202adf65a6b68eff8c24825532d7a04386ae628

The top line is the X-coordinate, the bottom line the Y-coordinate, both 32 bytes in length. If we compare the coordinates from Key1 with the coordinates from Key2 we see that they almost match, the X coordinates only differ by a missing single 00 byte in Key1' or an extra single 00 byte in Key2`, and the Y-coordinates are identical. So which one is wrong?

Based on the language found in RFC9053, CBOR Object Signing and Encryption (COSE): Initial Algorithms, section 7.1.1 Double Coordinate Curves for the EC key type, definition for the x-coordinate:

This contains the x-coordinate for the EC point. The integer is converted to a byte string as defined in [[SEC1](https://www.secg.org/sec1-v2.pdf)]. Leading-zero octets MUST be preserved.

It is my opinion that the generator of Key1 has failed to preserve the leading-zero octets in violation of RFC9053 as referenced in the description of the credentialPublicKey.

@aseigler
Copy link
Collaborator

aseigler commented Nov 4, 2024

I wanted to know how we could have caught such an issue. We actually would have caught this if you attempted to use credentialPublicKey.Verify(), but the Apple attestations don't actually supply any signed data to verify the signature for, so Verify() is never called. So, I am going to suggest we consider moving the KeyTypeParameter validation from CredentialPublicKey.Verify() into constructors.

If you try to validate the KeyTypeParameter values for this attestationOjbect["authData"].AttestedCredentialData.CredentialPublicKey, you get a System.Security.Cryptography.CryptographicException thrown with this message:

The specified key parameters are not valid. Q.X and Q.Y, or D, must be specified. Q.X, Q.Y must be the same length. If D is specified it must be the same length as Q.X and Q.Y if also specified for named curves or the same length as Order for explicit curves.

@abergs
Copy link
Collaborator

abergs commented Nov 5, 2024

Great find @aseigler!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment