-
Notifications
You must be signed in to change notification settings - Fork 511
Add android-key attestation format support #2322
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
base: master
Are you sure you want to change the base?
Changes from 10 commits
62915f5
a3452b7
801e176
6489983
d45eb9d
035c62e
f3f0916
fa2ed56
e892f4f
04bbf23
8548092
6a766f4
b44ef15
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,6 +36,7 @@ import ( | |
| "go.step.sm/crypto/x509util" | ||
| "golang.org/x/exp/slices" | ||
|
|
||
| "github.com/mbreban/attestation" | ||
| "github.com/smallstep/certificates/acme/wire" | ||
| "github.com/smallstep/certificates/authority/provisioner" | ||
| wireprovisioner "github.com/smallstep/certificates/authority/provisioner/wire" | ||
|
|
@@ -834,7 +835,7 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose | |
| format := att.Format | ||
| prov := MustProvisionerFromContext(ctx) | ||
| if !prov.IsAttestationFormatEnabled(ctx, provisioner.ACMEAttestationFormat(format)) { | ||
| if format != "apple" && format != "step" && format != "tpm" { | ||
| if format != "apple" && format != "step" && format != "tpm" && format != "android-key" { | ||
| return storeError(ctx, db, ch, true, NewDetailedError(ErrorBadAttestationStatementType, "unsupported attestation object format %q", format)) | ||
| } | ||
|
|
||
|
|
@@ -843,6 +844,36 @@ func deviceAttest01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose | |
| } | ||
|
|
||
| switch format { | ||
| case "android-key": | ||
| data, err := doAndroidKeyAttestionFormat(ctx, prov, ch, jwk, &att) | ||
| if err != nil { | ||
| var acmeError *Error | ||
| if errors.As(err, &acmeError) { | ||
| if acmeError.Status == 500 { | ||
| return acmeError | ||
| } | ||
| return storeError(ctx, db, ch, true, acmeError) | ||
| } | ||
| return WrapErrorISE(err, "error validating attestation") | ||
| } | ||
|
|
||
| // 1. attestationSecurityLevel > 0 (TrustedEnvironment or Strongbox) | ||
| if data.Attestation.AttestationSecurityLevel < 1 { | ||
| return storeError(ctx, db, ch, true, NewDetailedError(ErrorBadAttestationStatementType, "security level does not match")) | ||
| } | ||
|
|
||
| // 2. validate teeEnforced device identifier against permanent-identifier | ||
| if ch.Value != string(data.Attestation.TeeEnforced.AttestationIdSerial) { | ||
| subproblem := NewSubproblemWithIdentifier( | ||
| ErrorRejectedIdentifierType, | ||
| Identifier{Type: "permanent-identifier", Value: ch.Value}, | ||
| "challenge identifier %q doesn't match any of the attested hardware identifiers %q", ch.Value, []string{string(data.Attestation.TeeEnforced.AttestationIdSerial)}, | ||
| ) | ||
| return storeError(ctx, db, ch, true, NewDetailedError(ErrorBadAttestationStatementType, "permanent identifier does not match").AddSubproblems(subproblem)) | ||
| } | ||
|
|
||
| // Update attestation key fingerprint to compare against the CSR | ||
| az.Fingerprint = data.Fingerprint | ||
| case "apple": | ||
| data, err := doAppleAttestationFormat(ctx, prov, ch, &att) | ||
| if err != nil { | ||
|
|
@@ -1367,6 +1398,304 @@ func doAppleAttestationFormat(_ context.Context, prov Provisioner, _ *Challenge, | |
| return data, nil | ||
| } | ||
|
|
||
| // Android Root CA for RSA | ||
| // https://developer.android.com/privacy-and-security/security-key-attestation#root_certificate | ||
| // Note: Update your attestation processes to trust both the new and existing root certificates. Older devices with factory-provisioned keys don't support key rotation and continue to use the old root. | ||
| const OldAndroidRootCARSA = `-----BEGIN CERTIFICATE----- | ||
| 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 | ||
| -----END CERTIFICATE-----` | ||
|
|
||
| const AndroidRootCARSA = `-----BEGIN CERTIFICATE----- | ||
| MIIFHDCCAwSgAwIBAgIJAPHBcqaZ6vUdMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV | ||
| BAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMjIwMzIwMTgwNzQ4WhcNNDIwMzE1MTgw | ||
| NzQ4WjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0B | ||
| AQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdS | ||
| Sxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7 | ||
| tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggj | ||
| nar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGq | ||
| C4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQ | ||
| oVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+O | ||
| JtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/Eg | ||
| sTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRi | ||
| igHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+M | ||
| RPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9E | ||
| aDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5Um | ||
| AGMCAwEAAaNjMGEwHQYDVR0OBBYEFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMB8GA1Ud | ||
| IwQYMBaAFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMA8GA1UdEwEB/wQFMAMBAf8wDgYD | ||
| VR0PAQH/BAQDAgIEMA0GCSqGSIb3DQEBCwUAA4ICAQB8cMqTllHc8U+qCrOlg3H7 | ||
| 174lmaCsbo/bJ0C17JEgMLb4kvrqsXZs01U3mB/qABg/1t5Pd5AORHARs1hhqGIC | ||
| W/nKMav574f9rZN4PC2ZlufGXb7sIdJpGiO9ctRhiLuYuly10JccUZGEHpHSYM2G | ||
| tkgYbZba6lsCPYAAP83cyDV+1aOkTf1RCp/lM0PKvmxYN10RYsK631jrleGdcdkx | ||
| oSK//mSQbgcWnmAEZrzHoF1/0gso1HZgIn0YLzVhLSA/iXCX4QT2h3J5z3znluKG | ||
| 1nv8NQdxei2DIIhASWfu804CA96cQKTTlaae2fweqXjdN1/v2nqOhngNyz1361mF | ||
| mr4XmaKH/ItTwOe72NI9ZcwS1lVaCvsIkTDCEXdm9rCNPAY10iTunIHFXRh+7KPz | ||
| lHGewCq/8TOohBRn0/NNfh7uRslOSZ/xKbN9tMBtw37Z8d2vvnXq/YWdsm1+JLVw | ||
| n6yYD/yacNJBlwpddla8eaVMjsF6nBnIgQOf9zKSe06nSTqvgwUHosgOECZJZ1Eu | ||
| zbH4yswbt02tKtKEFhx+v+OTge/06V+jGsqTWLsfrOCNLuA8H++z+pUENmpqnnHo | ||
| vaI47gC+TNpkgYGkkBT6B/m/U01BuOBBTzhIlMEZq9qkDWuM2cA5kW5V3FJUcfHn | ||
| w1IdYIg2Wxg7yHcQZemFQg== | ||
| -----END CERTIFICATE-----` | ||
|
|
||
| // Android Root CA for ECDSA (starting feb 26) | ||
| // https://developer.android.com/privacy-and-security/security-key-attestation#root_certificate | ||
| const AndroidRootCAECDSA = `-----BEGIN CERTIFICATE----- | ||
| MIICIjCCAaigAwIBAgIRAISp0Cl7DrWK5/8OgN52BgUwCgYIKoZIzj0EAwMwUjEc | ||
| MBoGA1UEAwwTS2V5IEF0dGVzdGF0aW9uIENBMTEQMA4GA1UECwwHQW5kcm9pZDET | ||
| MBEGA1UECgwKR29vZ2xlIExMQzELMAkGA1UEBhMCVVMwHhcNMjUwNzE3MjIzMjE4 | ||
| WhcNMzUwNzE1MjIzMjE4WjBSMRwwGgYDVQQDDBNLZXkgQXR0ZXN0YXRpb24gQ0Ex | ||
| MRAwDgYDVQQLDAdBbmRyb2lkMRMwEQYDVQQKDApHb29nbGUgTExDMQswCQYDVQQG | ||
| EwJVUzB2MBAGByqGSM49AgEGBSuBBAAiA2IABCPaI3FO3z5bBQo8cuiEas4HjqCt | ||
| G/mLFfRT0MsIssPBEEU5Cfbt6sH5yOAxqEi5QagpU1yX4HwnGb7OtBYpDTB57uH5 | ||
| Eczm34A5FNijV3s0/f0UPl7zbJcTx6xwqMIRq6NCMEAwDwYDVR0TAQH/BAUwAwEB | ||
| /zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFFIyuyz7RkOb3NaBqQ5lZuA0QepA | ||
| MAoGCCqGSM49BAMDA2gAMGUCMETfjPO/HwqReR2CS7p0ZWoD/LHs6hDi422opifH | ||
| EUaYLxwGlT9SLdjkVpz0UUOR5wIxAIoGyxGKRHVTpqpGRFiJtQEOOTp/+s1GcxeY | ||
| uR2zh/80lQyu9vAFCj6E4AXc+osmRg==` | ||
|
|
||
| // OID for the Android attestation extension | ||
| // https://source.android.com/docs/security/features/keystore/attestation#id-attestation | ||
| var oidAndroidAttestation = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 1, 17} | ||
| var oidAndroidProvisionningAttestation = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 1, 30} | ||
|
|
||
| type androidKeyAttestationData struct { | ||
| Certificate *x509.Certificate | ||
| Fingerprint string | ||
| Attestation *attestation.KeyDescription | ||
| } | ||
|
Comment on lines
+1491
to
+1495
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you make this a "plainer" struct? For example, for the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I imagine it is possible. The difference between Android Attestation and Apple Attestation, if I understand it, is that in Apple each data is a separate OID when Android is a structure in one OID. Parsing this value to the attestation KeyDescription object coming from the lib introduce in this implementation. We may still extract value necessary then discard the whole object. |
||
|
|
||
| func hasAndroidAttestation(cert *x509.Certificate) bool { | ||
| for _, ext := range cert.Extensions { | ||
| if ext.Id.Equal(oidAndroidAttestation) { | ||
| return true | ||
| } | ||
| } | ||
| return false | ||
| } | ||
|
|
||
| // https://developer.android.com/privacy-and-security/security-key-attestation | ||
| // 3. Verify that the root public certificate is trustworthy and that each certificate signs the next certificate in the chain. | ||
| // 4. Check each certificate's revocation status to ensure that none of the certificates have been revoked. | ||
| // 5. Optionally, inspect the provisioning information certificate extension that is only present in newer certificate chains. | ||
| // Obtain a reference to the CBOR parser library that is most appropriate for your toolset. Find the nearest certificate to the root that contains the provisioning information certificate extension. Use the parser to extract the provisioning information certificate extension data from that certificate. | ||
| // See the section about the provisioning information extension for more details. | ||
| // 6. Find the nearest certificate to the root that contains the key attestation certificate extension. If the provisioning information certificate extension was present, the key attestation certificate extension must be in the immediately subsequent certificate. Use the parser to extract the key attestation certificate extension data from that certificate. | ||
| // 7. Check the extension data that you've retrieved in the previous steps for consistency and compare with the set of values that you expect the hardware-backed key to contain. | ||
|
|
||
| func doAndroidKeyAttestionFormat(_ context.Context, prov Provisioner, ch *Challenge, jwk *jose.JSONWebKey, att *attestationObject) (*androidKeyAttestationData, error) { | ||
| // Extract x5c and verify certificate | ||
| acme := prov.(*provisioner.ACME) | ||
| certs := []*x509.Certificate{} | ||
| x5c, ok := att.AttStatement["x5c"].([]any) | ||
| if !ok { | ||
| return nil, NewDetailedError(ErrorBadAttestationStatementType, "x5c not present") | ||
| } | ||
| if len(x5c) == 0 { | ||
| return nil, NewDetailedError(ErrorRejectedIdentifierType, "x5c is empty") | ||
| } | ||
| if len(x5c) < 3 { | ||
| return nil, NewDetailedError(ErrorBadAttestationStatementType, "at least 3 certificates are required") | ||
| } | ||
| der, ok := x5c[0].([]byte) | ||
| if !ok { | ||
| return nil, NewDetailedError(ErrorBadAttestationStatementType, "x5c is malformed") | ||
| } | ||
| leaf, err := x509.ParseCertificate(der) | ||
| if err != nil { | ||
| return nil, WrapDetailedError(ErrorBadAttestationStatementType, err, "failed parsing leaf certificate") | ||
| } | ||
| if !hasAndroidAttestation(leaf) { | ||
| return nil, NewDetailedError(ErrorBadAttestationStatementType, "leaf certificate do not contains attestation") | ||
| } | ||
|
|
||
| certs = append(certs, leaf) | ||
|
|
||
| // Parse intermediates and root | ||
| intermediates := x509.NewCertPool() | ||
| var root *x509.Certificate | ||
| for i, v := range x5c[1:] { | ||
| der, ok := v.([]byte) | ||
| if !ok { | ||
| return nil, NewDetailedError(ErrorBadAttestationStatementType, "x5c is malformed") | ||
| } | ||
| cert, err := x509.ParseCertificate(der) | ||
| if err != nil { | ||
| return nil, WrapDetailedError(ErrorBadAttestationStatementType, err, "failed parsing certificate in chain") | ||
| } | ||
| // Verify CRL | ||
| if acme.IsCertificateRevoked(cert.SerialNumber.String()) { | ||
| return nil, NewDetailedError(ErrorBadAttestationStatementType, "x5c element contain a revoked certificate") | ||
| } | ||
| if i == len(x5c)-2 { | ||
| // Last cert = root | ||
| certs = append(certs, cert) | ||
| root = cert | ||
| } else { | ||
| certs = append(certs, cert) | ||
| intermediates.AddCert(cert) | ||
| } | ||
| } | ||
|
|
||
| if root == nil { | ||
| return nil, NewDetailedError(ErrorBadAttestationStatementType, "missing root certificate in x5c chain") | ||
| } | ||
|
Comment on lines
+1578
to
+1580
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the root cert part of the attestation chain? That's surprising to see. I'd expect to only contain intermediates. It seems to be in line with the Google docs.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes the root is part of the chain.
|
||
|
|
||
| // Verify attestation root signature | ||
| // Use configured or default attestation roots if none is configured. | ||
| attestationRoots, attOk := prov.GetAttestationRoots() | ||
| if !attOk { | ||
| attestationRoots = x509.NewCertPool() | ||
| switch root.PublicKey.(type) { | ||
| case *rsa.PublicKey: | ||
| rsaRoot, err := pemutil.ParseCertificate([]byte(AndroidRootCARSA)) | ||
| oldRsaRoot, err := pemutil.ParseCertificate([]byte(OldAndroidRootCARSA)) | ||
| if err != nil { | ||
| return nil, WrapErrorISE(err, "error parsing root ca") | ||
| } | ||
| // 1. verify public key | ||
| if !root.PublicKey.(*rsa.PublicKey).Equal(rsaRoot.PublicKey) { | ||
| return nil, NewDetailedError(ErrorBadAttestationStatementType, "root certificate not signed by Google") | ||
| } | ||
| attestationRoots.AddCert(rsaRoot) | ||
| attestationRoots.AddCert(oldRsaRoot) | ||
| case *ecdsa.PublicKey: | ||
| ecdsaRoot, err := pemutil.ParseCertificate([]byte(AndroidRootCAECDSA)) | ||
| if err != nil { | ||
| return nil, WrapErrorISE(err, "error parsing root ca") | ||
| } | ||
| if !root.PublicKey.(*rsa.PublicKey).Equal(ecdsaRoot.PublicKey) { | ||
| return nil, NewDetailedError(ErrorBadAttestationStatementType, "root certificate not signed by Google") | ||
| } | ||
| attestationRoots.AddCert(ecdsaRoot) | ||
| default: | ||
| return nil, NewDetailedError(ErrorBadAttestationStatementType, "invalid root certificate key type") | ||
| } | ||
| // 2. validate root certificate | ||
| if _, err := root.Verify(x509.VerifyOptions{ | ||
| Roots: attestationRoots, | ||
| CurrentTime: time.Now(), | ||
| KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, | ||
| }); err != nil { | ||
| return nil, WrapDetailedError(ErrorBadAttestationStatementType, err, "root certificate not signed by Google") | ||
| } | ||
| } else { | ||
| if _, err := root.Verify(x509.VerifyOptions{ | ||
| Roots: attestationRoots, | ||
| CurrentTime: time.Now(), | ||
| KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, | ||
| }); err != nil { | ||
| return nil, WrapDetailedError(ErrorBadAttestationStatementType, err, "root certificate not signed by provided roots") | ||
| } | ||
| } | ||
|
|
||
| // Validate the full chain including root as trust anchor | ||
| roots := x509.NewCertPool() | ||
| roots.AddCert(root) | ||
|
|
||
| if _, err := leaf.Verify(x509.VerifyOptions{ | ||
| Intermediates: intermediates, | ||
| Roots: roots, | ||
| CurrentTime: time.Now(), | ||
| KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, | ||
| }); err != nil { | ||
| return nil, WrapDetailedError(ErrorBadAttestationStatementType, err, "x5c chain verification failed") | ||
| } | ||
|
|
||
| // Get signature | ||
| sig, ok := att.AttStatement["sig"].([]byte) | ||
| if !ok { | ||
| return nil, NewDetailedError(ErrorBadAttestationStatementType, "sig not present") | ||
| } | ||
|
|
||
| keyAuth, err := KeyAuthorization(ch.Token, jwk) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| // Parse attestation data: | ||
| // find the attestation certificate | ||
| // attCert, err := findAndroidAttestationCert(certs) | ||
| // if err != nil { | ||
| // return nil, WrapDetailedError(ErrorBadAttestationStatementType, err, "") | ||
| // } | ||
| attCert := leaf | ||
|
||
|
|
||
| switch pub := attCert.PublicKey.(type) { | ||
| case *ecdsa.PublicKey: | ||
| if pub.Curve != elliptic.P256() { | ||
| return nil, WrapDetailedError(ErrorBadAttestationStatementType, err, "unsupported elliptic curve %s", pub.Curve) | ||
| } | ||
| sum := sha256.Sum256([]byte(keyAuth)) | ||
| if !ecdsa.VerifyASN1(pub, sum[:], sig) { | ||
| return nil, NewDetailedError(ErrorBadAttestationStatementType, "failed to validate signature") | ||
| } | ||
| case *rsa.PublicKey: | ||
| sum := sha256.Sum256([]byte(keyAuth)) | ||
| if err := rsa.VerifyPKCS1v15(pub, crypto.SHA256, sum[:], sig); err != nil { | ||
| return nil, NewDetailedError(ErrorBadAttestationStatementType, "failed to validate signature") | ||
| } | ||
| case ed25519.PublicKey: | ||
| if !ed25519.Verify(pub, []byte(keyAuth), sig) { | ||
| return nil, NewDetailedError(ErrorBadAttestationStatementType, "failed to validate signature") | ||
| } | ||
| default: | ||
| return nil, NewDetailedError(ErrorBadAttestationStatementType, "unsupported public key type %T", pub) | ||
| } | ||
|
|
||
| data := &androidKeyAttestationData{ | ||
| Certificate: attCert, | ||
| } | ||
| if data.Fingerprint, err = keyutil.Fingerprint(attCert.PublicKey); err != nil { | ||
| return nil, WrapErrorISE(err, "error calculating key fingerprint") | ||
| } | ||
|
|
||
| for _, ext := range attCert.Extensions { | ||
| if !ext.Id.Equal(oidAndroidAttestation) { | ||
| continue | ||
| } | ||
| keyDesc, err := attestation.ParseExtension(ext.Value) | ||
| if err != nil { | ||
| return nil, WrapError(ErrorBadAttestationStatementType, err, "error parsing attestation") | ||
| } | ||
| data.Attestation = keyDesc | ||
| break | ||
| } | ||
|
|
||
| // validate challenge | ||
| if subtle.ConstantTimeCompare([]byte(keyAuth), data.Attestation.AttestationChallenge) != 1 { | ||
| return nil, NewDetailedError(ErrorBadAttestationStatementType, fmt.Sprintf("challenge mismatch; expected %q, got %q", keyAuth, string(data.Attestation.AttestationChallenge))) | ||
| } | ||
|
|
||
| return data, nil | ||
| } | ||
|
|
||
| // Yubico PIV Root CA Serial 263751 | ||
| // https://developers.yubico.com/PIV/Introduction/piv-attestation-ca.pem | ||
| const yubicoPIVRootCA = `-----BEGIN CERTIFICATE----- | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that this is a new package, and not used in other projects (yet), it might be better to copy/fork its code over, and maintain it internally. Are you familiar with the author?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No I'm not familiar with the author. We can manage to rewrite this lib. The lib support version 1 to 300. I saw there is version 400 now.