@@ -145,13 +145,13 @@ func verifyTPMFormat(att AttestationObject, clientDataHash []byte, _ metadata.Pr
145145 return "" , nil , ErrAttestation .WithDetails ("Error getting certificate from x5c cert chain" )
146146 }
147147
148- aikCert , err := x509 .ParseCertificate (aikCertBytes )
149- if err != nil {
148+ var aikCert * x509.Certificate
149+
150+ if aikCert , err = x509 .ParseCertificate (aikCertBytes ); err != nil {
150151 return "" , nil , ErrAttestationFormat .WithDetails ("Error parsing certificate from ASN.1" )
151152 }
152153
153- err = aikCert .CheckSignature (webauthncose .SigAlgFromCOSEAlg (coseAlg ), certInfoBytes , sigBytes )
154- if err != nil {
154+ if err = aikCert .CheckSignature (webauthncose .SigAlgFromCOSEAlg (coseAlg ), certInfoBytes , sigBytes ); err != nil {
155155 return "" , nil , ErrAttestationFormat .WithDetails (fmt .Sprintf ("Signature validation error: %+v\n " , err ))
156156 }
157157 // Verify that aikCert meets the requirements in §8.3.1 TPM Attestation Statement Certificate Requirements
@@ -160,23 +160,41 @@ func verifyTPMFormat(att AttestationObject, clientDataHash []byte, _ metadata.Pr
160160 if aikCert .Version != 3 {
161161 return "" , nil , ErrAttestationFormat .WithDetails ("AIK certificate version must be 3" )
162162 }
163+
163164 // 2/6 Subject field MUST be set to empty.
164165 if aikCert .Subject .String () != "" {
165166 return "" , nil , ErrAttestationFormat .WithDetails ("AIK certificate subject must be empty" )
166167 }
167168
168- // 3/6 The Subject Alternative Name extension MUST be set as defined in [TPMv2-EK-Profile] section 3.2.9{}
169- var manufacturer , model , version string
169+ var (
170+ manufacturer , model , version string
171+ ekuValid = false
172+ eku []asn1.ObjectIdentifier
173+ constraints tpmBasicConstraints
174+ rest []byte
175+ )
170176
171177 for _ , ext := range aikCert .Extensions {
172- if ext .Id .Equal ([]int {2 , 5 , 29 , 17 }) {
173- manufacturer , model , version , err = parseSANExtension (ext .Value )
174- if err != nil {
178+ if ext .Id .Equal (oidExtensionSubjectAltName ) {
179+ if manufacturer , model , version , err = parseSANExtension (ext .Value ); err != nil {
175180 return "" , nil , err
176181 }
182+ } else if ext .Id .Equal (oidExtensionExtendedKeyUsage ) {
183+ if rest , err = asn1 .Unmarshal (ext .Value , & eku ); len (rest ) != 0 || err != nil || ! eku [0 ].Equal (tcgKpAIKCertificate ) {
184+ return "" , nil , ErrAttestationFormat .WithDetails ("AIK certificate EKU missing 2.23.133.8.3" )
185+ }
186+
187+ ekuValid = true
188+ } else if ext .Id .Equal (oidExtensionBasicConstraints ) {
189+ if rest , err = asn1 .Unmarshal (ext .Value , & constraints ); err != nil {
190+ return "" , nil , ErrAttestationFormat .WithDetails ("AIK certificate basic constraints malformed" )
191+ } else if len (rest ) != 0 {
192+ return "" , nil , ErrAttestationFormat .WithDetails ("AIK certificate basic constraints contains extra data" )
193+ }
177194 }
178195 }
179196
197+ // 3/6 The Subject Alternative Name extension MUST be set as defined in [TPMv2-EK-Profile] section 3.2.9{}
180198 if manufacturer == "" || model == "" || version == "" {
181199 return "" , nil , ErrAttestationFormat .WithDetails ("Invalid SAN data in AIK certificate" )
182200 }
@@ -186,44 +204,10 @@ func verifyTPMFormat(att AttestationObject, clientDataHash []byte, _ metadata.Pr
186204 }
187205
188206 // 4/6 The Extended Key Usage extension MUST contain the "joint-iso-itu-t(2) internationalorganizations(23) 133 tcg-kp(8) tcg-kp-AIKCertificate(3)" OID.
189- var (
190- ekuValid = false
191- eku []asn1.ObjectIdentifier
192- )
193-
194- for _ , ext := range aikCert .Extensions {
195- if ext .Id .Equal ([]int {2 , 5 , 29 , 37 }) {
196- rest , err := asn1 .Unmarshal (ext .Value , & eku )
197- if len (rest ) != 0 || err != nil || ! eku [0 ].Equal (tcgKpAIKCertificate ) {
198- return "" , nil , ErrAttestationFormat .WithDetails ("AIK certificate EKU missing 2.23.133.8.3" )
199- }
200-
201- ekuValid = true
202- }
203- }
204-
205207 if ! ekuValid {
206208 return "" , nil , ErrAttestationFormat .WithDetails ("AIK certificate missing EKU" )
207209 }
208210
209- // 5/6 The Basic Constraints extension MUST have the CA component set to false.
210- type basicConstraints struct {
211- IsCA bool `asn1:"optional"`
212- MaxPathLen int `asn1:"optional,default:-1"`
213- }
214-
215- var constraints basicConstraints
216-
217- for _ , ext := range aikCert .Extensions {
218- if ext .Id .Equal ([]int {2 , 5 , 29 , 19 }) {
219- if rest , err := asn1 .Unmarshal (ext .Value , & constraints ); err != nil {
220- return "" , nil , ErrAttestationFormat .WithDetails ("AIK certificate basic constraints malformed" )
221- } else if len (rest ) != 0 {
222- return "" , nil , ErrAttestationFormat .WithDetails ("AIK certificate basic constraints contains extra data" )
223- }
224- }
225- }
226-
227211 // 6/6 An Authority Information Access (AIA) extension with entry id-ad-ocsp and a CRL Distribution Point
228212 // extension [RFC5280] are both OPTIONAL as the status of many attestation certificates is available
229213 // through metadata services. See, for example, the FIDO Metadata Service.
@@ -374,3 +358,46 @@ func isValidTPMManufacturer(id string) bool {
374358
375359 return false
376360}
361+
362+ func tpmParseSANExtension (attestation * x509.Certificate ) (err error ) {
363+ var (
364+ manufacturer , model , version string
365+ )
366+
367+ for _ , ext := range attestation .Extensions {
368+ if ext .Id .Equal (oidExtensionSubjectAltName ) {
369+ if manufacturer , model , version , err = parseSANExtension (ext .Value ); err != nil {
370+ return ErrInvalidAttestation .WithDetails ("Authenticator with invalid AIK SAN data encountered during attestation validation." ).WithInfo (fmt .Sprintf ("Error occurred parsing SAN extension: %s" , err .Error ()))
371+ }
372+ }
373+ }
374+
375+ if manufacturer == "" || model == "" || version == "" {
376+ return ErrAttestationFormat .WithDetails ("Invalid SAN data in AIK certificate" )
377+ }
378+
379+ var unhandled []asn1.ObjectIdentifier
380+
381+ for _ , uce := range attestation .UnhandledCriticalExtensions {
382+ if uce .Equal (oidExtensionSubjectAltName ) {
383+ continue
384+ }
385+
386+ unhandled = append (unhandled , uce )
387+ }
388+
389+ attestation .UnhandledCriticalExtensions = unhandled
390+
391+ return nil
392+ }
393+
394+ var (
395+ oidExtensionSubjectAltName = []int {2 , 5 , 29 , 17 }
396+ oidExtensionExtendedKeyUsage = []int {2 , 5 , 29 , 37 }
397+ oidExtensionBasicConstraints = []int {2 , 5 , 29 , 19 }
398+ )
399+
400+ type tpmBasicConstraints struct {
401+ IsCA bool `asn1:"optional"`
402+ MaxPathLen int `asn1:"optional,default:-1"`
403+ }
0 commit comments