@@ -37,7 +37,7 @@ import (
3737// See: https://www.w3.org/TR/webauthn/#sctn-android-key-attestation
3838//
3939//nolint:gocyclo
40- func attestationFormatValidationHandlerAndroidKey (att AttestationObject , clientDataHash []byte , _ metadata.Provider ) (attestationType string , x5cs []any , err error ) {
40+ func attestationFormatValidationHandlerAndroidKey (att AttestationObject , clientDataHash []byte , _ metadata.Provider , policy AttestationPolicy ) (attestationType string , x5cs []any , err error ) {
4141 var (
4242 alg int64
4343 sig []byte
@@ -145,22 +145,30 @@ func attestationFormatValidationHandlerAndroidKey(att AttestationObject, clientD
145145 return "" , nil , ErrAttestationFormat .WithDetails ("Attestation challenge not equal to clientDataHash" )
146146 }
147147
148- if protoErr := androidKeyValidateAuthorizationLists (& decoded ); protoErr != nil {
148+ if protoErr := androidKeyValidateAuthorizationLists (& decoded , policy . AndroidKey . AuthorizationScope ); protoErr != nil {
149149 return "" , nil , protoErr
150150 }
151151
152152 return string (metadata .BasicFull ), x5c , err
153153}
154154
155- // androidKeyValidateAuthorizationLists performs the §8.4 verification steps which apply to the authorization lists of
156- // the Android key attestation certificate extension.
157- func androidKeyValidateAuthorizationLists (decoded * androidkeyDescription ) * Error {
155+ // androidKeyValidateAuthorizationLists performs the §8.4 verification steps which apply to the authorization lists
156+ // of the Android key attestation certificate extension.
157+ //
158+ // The scope selects the lists the origin and purpose requirements are evaluated against, which §8.4 leaves to the
159+ // Relying Party. See [AndroidKeyAuthorizationScope].
160+ func androidKeyValidateAuthorizationLists (decoded * androidkeyDescription , scope AndroidKeyAuthorizationScope ) * Error {
158161 // The AuthorizationList.allApplications field is not present on either authorization list (softwareEnforced nor teeEnforced), since PublicKeyCredential MUST be scoped to the RP ID.
162+ //
163+ // This requirement precedes the sentence which introduces the Relying Party's choice of scope, so it applies to
164+ // both lists regardless of the scope in effect.
159165 if len (decoded .SoftwareEnforced .AllApplications .FullBytes ) != 0 || len (decoded .TeeEnforced .AllApplications .FullBytes ) != 0 {
160166 return ErrAttestationFormat .WithDetails ("Attestation certificate extensions contains all applications field" )
161167 }
162168
163169 // For the following, use only the teeEnforced authorization list if the RP wants to accept only keys from a trusted execution environment, otherwise use the union of teeEnforced and softwareEnforced.
170+ union := scope .union ()
171+
164172 // The value in the AuthorizationList.origin field is equal to KM_ORIGIN_GENERATED (which == 0).
165173 var (
166174 originTee , originSoftware int
@@ -172,26 +180,51 @@ func androidKeyValidateAuthorizationLists(decoded *androidkeyDescription) *Error
172180 return ErrAttestationFormat .WithDetails ("Unable to parse the origin of the teeEnforced authorization list" ).WithError (err )
173181 }
174182
175- if originSoftware , presentSoftware , err = authorizationListOrigin (& decoded .SoftwareEnforced ); err != nil {
176- return ErrAttestationFormat .WithDetails ("Unable to parse the origin of the softwareEnforced authorization list" ).WithError (err )
183+ // The softwareEnforced list is only parsed when the scope consults it, so a malformed origin there cannot fail
184+ // an attestation the teeEnforced scope would otherwise accept on the teeEnforced list alone.
185+ if union {
186+ if originSoftware , presentSoftware , err = authorizationListOrigin (& decoded .SoftwareEnforced ); err != nil {
187+ return ErrAttestationFormat .WithDetails ("Unable to parse the origin of the softwareEnforced authorization list" ).WithError (err )
188+ }
177189 }
178190
179- // The union is satisfied when either list carries an origin equal to KM_ORIGIN_GENERATED. An absent origin
180- // satisfies nothing as there is no value to compare against, which mirrors the purpose check below.
181- generated := (presentTee && originTee == KM_ORIGIN_GENERATED ) || (presentSoftware && originSoftware == KM_ORIGIN_GENERATED )
191+ // An absent origin satisfies nothing as there is no value to compare against, which mirrors the purpose check
192+ // below.
193+ generated := presentTee && originTee == KM_ORIGIN_GENERATED
194+
195+ if union && ! generated {
196+ generated = presentSoftware && originSoftware == KM_ORIGIN_GENERATED
197+ }
182198
183199 if ! generated {
184- return ErrAttestationFormat .WithDetails ("Attestation certificate extensions contains authorization list with origin not equal KM_ORIGIN_GENERATED" )
200+ return ErrAttestationFormat .WithDetails (fmt . Sprintf ( "Attestation certificate extensions contains %s with origin not equal KM_ORIGIN_GENERATED" , androidKeyScopeDescription ( union )) )
185201 }
186202
187203 // The value in the AuthorizationList.purpose field is equal to KM_PURPOSE_SIGN (which == 2).
188- if ! contains (decoded .SoftwareEnforced .Purpose , KM_PURPOSE_SIGN ) && ! contains (decoded .TeeEnforced .Purpose , KM_PURPOSE_SIGN ) {
189- return ErrAttestationFormat .WithDetails ("Attestation certificate extensions contains authorization list with purpose not equal KM_PURPOSE_SIGN" )
204+ sign := contains (decoded .TeeEnforced .Purpose , KM_PURPOSE_SIGN )
205+
206+ if union && ! sign {
207+ sign = contains (decoded .SoftwareEnforced .Purpose , KM_PURPOSE_SIGN )
208+ }
209+
210+ if ! sign {
211+ return ErrAttestationFormat .WithDetails (fmt .Sprintf ("Attestation certificate extensions contains %s with purpose not equal KM_PURPOSE_SIGN" , androidKeyScopeDescription (union )))
190212 }
191213
192214 return nil
193215}
194216
217+ // androidKeyScopeDescription names the authorization lists the §8.4 origin and purpose requirements were evaluated
218+ // against so that a failure identifies the scope in effect. The union wording is unqualified as it matches the
219+ // specification's own phrasing and preserves the error text of the union scope.
220+ func androidKeyScopeDescription (union bool ) string {
221+ if union {
222+ return "authorization list"
223+ }
224+
225+ return "teeEnforced authorization list"
226+ }
227+
195228// authorizationListOrigin returns the origin of an authorization list and reports whether the field was present. The
196229// value is decoded from the raw element because encoding/asn1 leaves an absent optional integer at its zero value,
197230// which is indistinguishable from a present origin of KM_ORIGIN_GENERATED.
@@ -264,8 +297,10 @@ var authorizationListValidatedTags = []int{
264297// [authorizationList] can't model and which precedes a field the verification procedure depends on.
265298//
266299// An unmodelled tag otherwise defeats the §8.4 requirement that allApplications is absent, as the element is dropped
267- // along with every field declared after it while the union permits the other list to supply the origin and purpose. A
268- // list which can't be modelled in full is rejected explicitly rather than silently truncated.
300+ // along with every field declared after it. That requirement is checked against both lists in every scope, not only
301+ // when the union scope draws on softwareEnforced for the origin and purpose checks, so an unmodelled tag in either
302+ // list needs the same protection regardless of which scope the Relying Party has selected. A list which can't be
303+ // modelled in full is rejected explicitly rather than silently truncated.
269304func androidKeyVerifyAuthorizationListTags (raw * androidkeyDescriptionRaw ) * Error {
270305 for _ , list := range []struct {
271306 name string
0 commit comments