@@ -4,7 +4,11 @@ import (
44 "bytes"
55 "crypto/x509"
66 "encoding/asn1"
7+ "errors"
78 "fmt"
9+ "reflect"
10+ "strconv"
11+ "strings"
812 "time"
913
1014 "github.com/go-webauthn/webauthn/metadata"
@@ -111,12 +115,24 @@ func attestationFormatValidationHandlerAndroidKey(att AttestationObject, clientD
111115 return "" , nil , ErrAttestationFormat .WithDetails ("Attestation certificate extensions missing 1.3.6.1.4.1.11129.2.1.17" )
112116 }
113117
114- decoded := keyDescription {}
118+ decoded := androidkeyDescription {}
115119
116120 if _ , err = asn1 .Unmarshal (attExtBytes , & decoded ); err != nil {
117121 return "" , nil , ErrAttestationFormat .WithDetails ("Unable to parse Android key attestation certificate extensions" ).WithError (err )
118122 }
119123
124+ // The decode above silently abandons the remaining fields of an authorization list on meeting an element it can't
125+ // model, so the elements actually present are checked against the raw extension before any of it is relied upon.
126+ raw := androidkeyDescriptionRaw {}
127+
128+ if _ , err = asn1 .Unmarshal (attExtBytes , & raw ); err != nil {
129+ return "" , nil , ErrAttestationFormat .WithDetails ("Unable to parse Android key attestation certificate extensions" ).WithError (err )
130+ }
131+
132+ if protoErr := androidKeyVerifyAuthorizationListTags (& raw ); protoErr != nil {
133+ return "" , nil , protoErr
134+ }
135+
120136 // Verify that the attestationChallenge field in the attestation certificate extension data is identical to clientDataHash.
121137 if ! bytes .Equal (decoded .AttestationChallenge , clientDataHash ) {
122138 return "" , nil , ErrAttestationFormat .WithDetails ("Attestation challenge not equal to clientDataHash" )
@@ -131,7 +147,7 @@ func attestationFormatValidationHandlerAndroidKey(att AttestationObject, clientD
131147
132148// androidKeyValidateAuthorizationLists performs the §8.4 verification steps which apply to the authorization lists of
133149// the Android key attestation certificate extension.
134- func androidKeyValidateAuthorizationLists (decoded * keyDescription ) * Error {
150+ func androidKeyValidateAuthorizationLists (decoded * androidkeyDescription ) * Error {
135151 // The AuthorizationList.allApplications field is not present on either authorization list (softwareEnforced nor teeEnforced), since PublicKeyCredential MUST be scoped to the RP ID.
136152 if len (decoded .SoftwareEnforced .AllApplications .FullBytes ) != 0 || len (decoded .TeeEnforced .AllApplications .FullBytes ) != 0 {
137153 return ErrAttestationFormat .WithDetails ("Attestation certificate extensions contains all applications field" )
@@ -172,7 +188,7 @@ func androidKeyValidateAuthorizationLists(decoded *keyDescription) *Error {
172188// authorizationListOrigin returns the origin of an authorization list and reports whether the field was present. The
173189// value is decoded from the raw element because encoding/asn1 leaves an absent optional integer at its zero value,
174190// which is indistinguishable from a present origin of KM_ORIGIN_GENERATED.
175- func authorizationListOrigin (list * authorizationList ) (origin int , present bool , err error ) {
191+ func authorizationListOrigin (list * androidkeyAuthorizationList ) (origin int , present bool , err error ) {
176192 // An explicit tag which is present always carries a child as encoding/asn1 rejects one which doesn't while
177193 // decoding the key description, so an empty raw value means the field was absent rather than empty.
178194 if len (list .Origin .FullBytes ) == 0 {
@@ -202,111 +218,108 @@ func contains(s []int, e int) bool {
202218 return false
203219}
204220
205- type keyDescription struct {
206- AttestationVersion int
207- AttestationSecurityLevel asn1.Enumerated
208- KeymasterVersion int
209- KeymasterSecurityLevel asn1.Enumerated
210- AttestationChallenge []byte
211- UniqueID []byte
212- SoftwareEnforced authorizationList
213- TeeEnforced authorizationList
214- }
221+ // authorizationListTags contains every context specific tag number modelled by [authorizationList]. It's derived from
222+ // the struct definition so that declaring a field is the only step needed to support a tag, and the two can't drift.
223+ var authorizationListTags = func () (tags map [int ]bool ) {
224+ t := reflect .TypeOf (androidkeyAuthorizationList {})
225+
226+ tags = make (map [int ]bool , t .NumField ())
215227
216- type authorizationList struct {
217- Purpose []int `asn1:"tag:1,explicit,set,optional"`
218- Algorithm int `asn1:"tag:2,explicit,optional"`
219- KeySize int `asn1:"tag:3,explicit,optional"`
220- Digest []int `asn1:"tag:5,explicit,set,optional"`
221- Padding []int `asn1:"tag:6,explicit,set,optional"`
222- EcCurve int `asn1:"tag:10,explicit,optional"`
223- RsaPublicExponent int `asn1:"tag:200,explicit,optional"`
224- RollbackResistance asn1.RawValue `asn1:"tag:303,explicit,optional"`
225- ActiveDateTime int `asn1:"tag:400,explicit,optional"`
226- OriginationExpireDateTime int `asn1:"tag:401,explicit,optional"`
227- UsageExpireDateTime int `asn1:"tag:402,explicit,optional"`
228- NoAuthRequired asn1.RawValue `asn1:"tag:503,explicit,optional"`
229- UserAuthType int `asn1:"tag:504,explicit,optional"`
230- AuthTimeout int `asn1:"tag:505,explicit,optional"`
231- AllowWhileOnBody asn1.RawValue `asn1:"tag:506,explicit,optional"`
232- TrustedUserPresenceRequired asn1.RawValue `asn1:"tag:507,explicit,optional"`
233- TrustedConfirmationRequired asn1.RawValue `asn1:"tag:508,explicit,optional"`
234- UnlockedDeviceRequired asn1.RawValue `asn1:"tag:509,explicit,optional"`
235- AllApplications asn1.RawValue `asn1:"tag:600,explicit,optional"`
236- ApplicationID asn1.RawValue `asn1:"tag:601,explicit,optional"`
237- CreationDateTime int `asn1:"tag:701,explicit,optional"`
238- // Origin is decoded as a raw element rather than an integer. encoding/asn1 leaves an absent optional integer at
239- // its zero value, which is indistinguishable from a present origin of KM_ORIGIN_GENERATED.
240- Origin asn1.RawValue `asn1:"tag:702,explicit,optional"`
241-
242- RootOfTrust rootOfTrust `asn1:"tag:704,explicit,optional"`
243-
244- OsVersion int `asn1:"tag:705,explicit,optional"`
245- OsPatchLevel int `asn1:"tag:706,explicit,optional"`
246- AttestationApplicationID []byte `asn1:"tag:709,explicit,optional"`
247- AttestationIDBrand []byte `asn1:"tag:710,explicit,optional"`
248- AttestationIDDevice []byte `asn1:"tag:711,explicit,optional"`
249- AttestationIDProduct []byte `asn1:"tag:712,explicit,optional"`
250- AttestationIDSerial []byte `asn1:"tag:713,explicit,optional"`
251- AttestationIDImei []byte `asn1:"tag:714,explicit,optional"`
252- AttestationIDMeid []byte `asn1:"tag:715,explicit,optional"`
253- AttestationIDManufacturer []byte `asn1:"tag:716,explicit,optional"`
254- AttestationIDModel []byte `asn1:"tag:717,explicit,optional"`
255- VendorPatchLevel int `asn1:"tag:718,explicit,optional"`
256- BootPatchLevel int `asn1:"tag:719,explicit,optional"`
228+ for i := range t .NumField () {
229+ field := t .Field (i )
230+
231+ for _ , option := range strings .Split (field .Tag .Get ("asn1" ), "," ) {
232+ if ! strings .HasPrefix (option , "tag:" ) {
233+ continue
234+ }
235+
236+ tag , err := strconv .Atoi (strings .TrimPrefix (option , "tag:" ))
237+ if err != nil {
238+ panic (fmt .Sprintf ("protocol: authorizationList field %s has a malformed asn1 tag: %v" , field .Name , err ))
239+ }
240+
241+ tags [tag ] = true
242+ }
243+ }
244+
245+ return tags
246+ }()
247+
248+ // authorizationListValidatedTags contains the tags of the authorization list fields which the §8.4 verification steps
249+ // consult. An element the struct can't model only matters when it displaces one of these.
250+ var authorizationListValidatedTags = []int {
251+ 1 , // purpose.
252+ 600 , // allApplications.
253+ 702 , // origin.
257254}
258255
259- type rootOfTrust struct {
260- VerifiedBootKey []byte
261- DeviceLocked bool
262- VerifiedBootState asn1.Enumerated
263- VerifiedBootHash []byte `asn1:"optional"`
256+ // androidKeyVerifyAuthorizationListTags rejects an attestation whose authorization lists carry an element that
257+ // [authorizationList] can't model and which precedes a field the verification procedure depends on.
258+ //
259+ // An unmodelled tag otherwise defeats the §8.4 requirement that allApplications is absent, as the element is dropped
260+ // along with every field declared after it while the union permits the other list to supply the origin and purpose. A
261+ // list which can't be modelled in full is rejected explicitly rather than silently truncated.
262+ func androidKeyVerifyAuthorizationListTags (raw * androidkeyDescriptionRaw ) * Error {
263+ for _ , list := range []struct {
264+ name string
265+ raw asn1.RawValue
266+ }{
267+ {"teeEnforced" , raw .TeeEnforced },
268+ {"softwareEnforced" , raw .SoftwareEnforced },
269+ } {
270+ if err := authorizationListVerifyTags (list .raw ); err != nil {
271+ return ErrAttestationFormat .WithDetails (fmt .Sprintf ("Unable to validate the %s authorization list" , list .name )).WithInfo (err .Error ()).WithError (err )
272+ }
273+ }
274+
275+ return nil
264276}
265277
266- type verifiedBootState int
278+ // authorizationListVerifyTags reports an error when an element of the raw authorization list isn't modelled by
279+ // [authorizationList] and is positioned before an element the verification procedure consults. Anything after the last
280+ // consulted element can't influence the outcome as decoding reached them all, which keeps a tag appended to a later
281+ // revision of the schema from rejecting an otherwise sound attestation.
282+ func authorizationListVerifyTags (raw asn1.RawValue ) (err error ) {
283+ if raw .Class != asn1 .ClassUniversal || raw .Tag != asn1 .TagSequence || ! raw .IsCompound {
284+ return errors .New ("authorization list is not a sequence" )
285+ }
267286
268- const (
269- Verified verifiedBootState = iota
270- SelfSigned
271- Unverified
272- Failed
273- )
287+ var tags []int
274288
275- const (
276- // KM_ORIGIN_GENERATED means generated in keymaster. Should not exist outside the TEE.
277- KM_ORIGIN_GENERATED = iota
289+ rest := raw .Bytes
278290
279- // KM_ORIGIN_DERIVED means derived inside keymaster. Likely exists off-device.
280- KM_ORIGIN_DERIVED
291+ for len ( rest ) > 0 {
292+ var element asn1. RawValue
281293
282- // KM_ORIGIN_IMPORTED means imported into keymaster. Existed as clear text in Android.
283- KM_ORIGIN_IMPORTED
294+ if rest , err = asn1 .Unmarshal (rest , & element ); err != nil {
295+ return err
296+ }
284297
285- // KM_ORIGIN_UNKNOWN means keymaster did not record origin. This value can only be seen on keys in a keymaster0
286- // implementation. The keymaster0 adapter uses this value to document the fact that it is unknown whether the key
287- // was generated inside or imported into keymaster.
288- KM_ORIGIN_UNKNOWN
289- )
298+ if element .Class != asn1 .ClassContextSpecific {
299+ return fmt .Errorf ("element %d has class %d where a context specific class was expected" , len (tags ), element .Class )
300+ }
290301
291- const (
292- // KM_PURPOSE_ENCRYPT is usable with RSA, EC and AES keys.
293- KM_PURPOSE_ENCRYPT = iota
302+ tags = append (tags , element .Tag )
303+ }
294304
295- // KM_PURPOSE_DECRYPT is usable with RSA, EC and AES keys.
296- KM_PURPOSE_DECRYPT
305+ last := - 1
297306
298- // KM_PURPOSE_SIGN is usable with RSA, EC and HMAC keys.
299- KM_PURPOSE_SIGN
307+ for i , tag := range tags {
308+ if contains (authorizationListValidatedTags , tag ) {
309+ last = i
310+ }
311+ }
300312
301- // KM_PURPOSE_VERIFY is usable with RSA, EC and HMAC keys.
302- KM_PURPOSE_VERIFY
313+ for i , tag := range tags [:last + 1 ] {
314+ if authorizationListTags [tag ] {
315+ continue
316+ }
303317
304- // KM_PURPOSE_DERIVE_KEY is usable with EC keys.
305- KM_PURPOSE_DERIVE_KEY
318+ return fmt . Errorf ( "element %d has tag [%d] which is not supported and precedes a field required by the verification procedure" , i , tag )
319+ }
306320
307- // KM_PURPOSE_WRAP is usable with wrapped keys.
308- KM_PURPOSE_WRAP
309- )
321+ return nil
322+ }
310323
311324var (
312325 attAndroidKeyHardwareRootsCertPool * x509.CertPool
0 commit comments