@@ -2,12 +2,10 @@ package protocol
22
33import (
44 "bytes"
5- "crypto"
65 "crypto/subtle"
76 "crypto/x509"
87 "crypto/x509/pkix"
98 "encoding/asn1"
10- "encoding/binary"
119 "errors"
1210 "fmt"
1311 "math"
@@ -208,6 +206,7 @@ func attestationFormatValidationHandlerTPM(att AttestationObject, clientDataHash
208206 var (
209207 manufacturer , model , version string
210208 ekuValid = false
209+ constraintsValid = false
211210 eku []asn1.ObjectIdentifier
212211 constraints tpmBasicConstraints
213212 rest []byte
@@ -246,6 +245,8 @@ func attestationFormatValidationHandlerTPM(att AttestationObject, clientDataHash
246245 } else if len (rest ) != 0 {
247246 return "" , nil , ErrAttestationFormat .WithDetails ("AIK certificate basic constraints contains extra data" )
248247 }
248+
249+ constraintsValid = true
249250 }
250251 }
251252
@@ -263,13 +264,29 @@ func attestationFormatValidationHandlerTPM(att AttestationObject, clientDataHash
263264 return "" , nil , ErrAttestationFormat .WithDetails ("AIK certificate missing EKU" )
264265 }
265266
267+ // 5/6 The Basic Constraints extension MUST have the CA component set to false. An absent extension can't have the
268+ // CA component set to false so it's rejected in the same way as one which asserts CA is true.
269+ //
266270 // 6/6 An Authority Information Access (AIA) extension with entry id-ad-ocsp and a CRL Distribution Point
267271 // extension [RFC5280] are both OPTIONAL as the status of many attestation certificates is available
268272 // through metadata services. See, for example, the FIDO Metadata Service.
269- if constraints .IsCA {
273+ if ! constraintsValid || constraints .IsCA {
270274 return "" , nil , ErrAttestationFormat .WithDetails ("AIK certificate basic constraints missing or CA is true" )
271275 }
272276
277+ // If aikCert contains an extension with OID 1.3.6.1.4.1.45724.1.1.4 (id-fido-gen-ce-aaguid) verify that the value
278+ // of this extension matches the aaguid in authenticatorData.
279+ var (
280+ aaguid []byte
281+ found bool
282+ )
283+
284+ if aaguid , _ , found , err = attestationCertAAGUID (aikCert ); err != nil {
285+ return "" , nil , ErrInvalidAttestation .WithDetails ("Error unmarshalling AAGUID from certificate" ).WithError (err )
286+ } else if found && ! bytes .Equal (aaguid , att .AuthData .AttData .AAGUID ) {
287+ return "" , nil , ErrInvalidAttestation .WithDetails ("Certificate AAGUID does not match Auth Data certificate" )
288+ }
289+
273290 // 4/4 Verify that attested contains a TPMS_CERTIFY_INFO structure as specified in
274291 // [TPMv2-Part2] section 10.12.3, whose name field contains a valid Name for pubArea,
275292 // as computed using the algorithm in the nameAlg field of pubArea
@@ -322,34 +339,6 @@ func tpm2NameMatch(certInfo *tpm2.TPMSAttest, pubArea *tpm2.TPMTPublic) (match b
322339 return subtle .ConstantTimeCompare (certifyInfo .Name .Buffer , name .Buffer ) == 1 , nil
323340}
324341
325- func tpm2NameDigest (name tpm2.TPM2BName ) (alg tpm2.TPMIAlgHash , digest []byte , err error ) {
326- buf := name .Buffer
327-
328- if len (buf ) < 3 {
329- return 0 , nil , fmt .Errorf ("name too short" )
330- }
331-
332- alg = tpm2 .TPMIAlgHash (binary .BigEndian .Uint16 (buf [:2 ]))
333-
334- var hash crypto.Hash
335-
336- if hash , err = alg .Hash (); err != nil {
337- return 0 , nil , fmt .Errorf ("invalid hash algorithm: %w" , err )
338- }
339-
340- digest = buf [2 :]
341-
342- if len (digest ) == 0 {
343- return 0 , nil , fmt .Errorf ("name digest is empty" )
344- }
345-
346- if len (digest ) != hash .Size () {
347- return 0 , nil , fmt .Errorf ("invalid name digest length: %d" , len (digest ))
348- }
349-
350- return alg , digest , nil
351- }
352-
353342type tpm2AttStatement struct {
354343 Version string
355344 Algorithm int64
@@ -507,72 +496,29 @@ type tpmManufacturer struct {
507496 code string
508497}
509498
510- // See https://trustedcomputinggroup.org/resource/vendor-id-registry/ for registry contents.
511- var (
512- tpmManufacturers = []tpmManufacturer {
513- {"414D4400" , "AMD" , "AMD" },
514- {"414E5400" , "Ant Group" , "ANT" },
515- {"41544D4C" , "Atmel" , "ATML" },
516- {"4252434D" , "Broadcom" , "BRCM" },
517- {"4353434F" , "Cisco" , "CSCO" },
518- {"464C5953" , "Flyslice Technologies" , "FLYS" },
519- {"524F4343" , "Fuzhou Rockchip" , "ROCC" },
520- {"474F4F47" , "Google" , "GOOG" },
521- {"48504900" , "HPI" , "HPI" },
522- {"48504500" , "HPE" , "HPE" },
523- {"48495349" , "Huawei" , "HISI" },
524- {"49424d00" , "IBM" , "IBM" },
525- {"49424D00" , "IBM" , "IBM" },
526- {"49465800" , "Infineon" , "IFX" },
527- {"494E5443" , "Intel" , "INTC" },
528- {"4C454E00" , "Lenovo" , "LEN" },
529- {"4D534654" , "Microsoft" , "MSFT" },
530- {"4E534D20" , "National Semiconductor" , "NSM" },
531- {"4E545A00" , "Nationz" , "NTZ" },
532- {"4E534700" , "NSING" , "NSG" },
533- {"4E544300" , "Nuvoton Technology" , "NTC" },
534- {"51434F4D" , "Qualcomm" , "QCOM" },
535- {"534D534E" , "Samsung" , "SECE" },
536- {"53454345" , "SecEdge" , "SecEdge" },
537- {"534E5300" , "Sinosun" , "SNS" },
538- {"534D5343" , "SMSC" , "SMSC" },
539- {"53544D20" , "ST Microelectronics" , "STM" },
540- {"54584E00" , "Texas Instruments" , "TXN" },
541- {"57454300" , "Winbond" , "WEC" },
542- {"5345414C" , "Wisekey" , "SEAL" },
543- {"FFFFF1D0" , "FIDO Alliance Conformance Testing" , "FIDO" },
544- }
545- )
546-
547- func isValidTPMManufacturer (id string ) bool {
548- for _ , m := range tpmManufacturers {
549- if m .id == id {
550- return true
551- }
499+ func tpmParseAIKAttCA (x5c * x509.Certificate , x5cis []* x509.Certificate ) (leaf * x509.Certificate , parents []* x509.Certificate , protoErr * Error ) {
500+ if leaf , protoErr = tpmParseSANExtension (x5c ); protoErr != nil {
501+ return nil , nil , protoErr
552502 }
553503
554- return false
555- }
504+ var hasAIK bool
556505
557- func tpmParseAIKAttCA (x5c * x509.Certificate , x5cis []* x509.Certificate ) (err * Error ) {
558- if err = tpmParseSANExtension (x5c ); err != nil {
559- return err
506+ // The AIK Extended Key Usage is only required on the attestation certificate itself per §8.3.1. TPM vendor
507+ // intermediates generally don't carry it so it must not be required of them.
508+ if leaf , hasAIK = tpmRemoveEKU (leaf ); ! hasAIK {
509+ return nil , nil , ErrAttestationFormat .WithDetails ("Attestation Identity Key certificate missing required Extended Key Usage." )
560510 }
561511
562- if err = tpmRemoveEKU (x5c ); err != nil {
563- return err
564- }
512+ for _ , x5ci := range x5cis {
513+ parent , _ := tpmRemoveEKU (x5ci )
565514
566- for _ , parent := range x5cis {
567- if err = tpmRemoveEKU (parent ); err != nil {
568- return err
569- }
515+ parents = append (parents , parent )
570516 }
571517
572- return nil
518+ return leaf , parents , nil
573519}
574520
575- func tpmParseSANExtension (attestation * x509.Certificate ) (protoErr * Error ) {
521+ func tpmParseSANExtension (attestation * x509.Certificate ) (out * x509. Certificate , protoErr * Error ) {
576522 var (
577523 manufacturer , model , version string
578524 err error
@@ -581,13 +527,13 @@ func tpmParseSANExtension(attestation *x509.Certificate) (protoErr *Error) {
581527 for _ , ext := range attestation .Extensions {
582528 if ext .Id .Equal (oidExtensionSubjectAltName ) {
583529 if manufacturer , model , version , err = parseSANExtension (ext .Value ); err != nil {
584- return ErrInvalidAttestation .WithDetails ("Authenticator with invalid Authenticator Identity Key SAN data encountered during attestation validation." ).WithInfo (fmt .Sprintf ("Error occurred parsing SAN extension: %s" , err .Error ())).WithError (err )
530+ return nil , ErrInvalidAttestation .WithDetails ("Authenticator with invalid Authenticator Identity Key SAN data encountered during attestation validation." ).WithInfo (fmt .Sprintf ("Error occurred parsing SAN extension: %s" , err .Error ())).WithError (err )
585531 }
586532 }
587533 }
588534
589535 if manufacturer == "" || model == "" || version == "" {
590- return ErrAttestationFormat .WithDetails ("Invalid SAN data in AIK certificate." )
536+ return nil , ErrAttestationFormat .WithDetails ("Invalid SAN data in AIK certificate." )
591537 }
592538
593539 var unhandled []asn1.ObjectIdentifier
@@ -600,22 +546,23 @@ func tpmParseSANExtension(attestation *x509.Certificate) (protoErr *Error) {
600546 unhandled = append (unhandled , uce )
601547 }
602548
603- attestation .UnhandledCriticalExtensions = unhandled
549+ out = new (x509.Certificate )
550+ * out = * attestation
551+ out .UnhandledCriticalExtensions = unhandled
604552
605- return nil
553+ return out , nil
606554}
607555
608556type tpmBasicConstraints struct {
609557 IsCA bool `asn1:"optional"`
610558 MaxPathLen int `asn1:"optional,default:-1"`
611559}
612560
613- // Remove extension key usage to avoid ExtKeyUsage check failure.
614- func tpmRemoveEKU (x5c * x509.Certificate ) * Error {
615- var (
616- unknown []asn1.ObjectIdentifier
617- hasAiK bool
618- )
561+ // tpmRemoveEKU returns a copy of the certificate with the TCG and Microsoft extension key usages removed to avoid the
562+ // ExtKeyUsage check failure, and reports whether the certificate carried the AIK extension key usage. The certificate
563+ // given is never modified as it's owned by the caller.
564+ func tpmRemoveEKU (x5c * x509.Certificate ) (out * x509.Certificate , hasAiK bool ) {
565+ var unknown []asn1.ObjectIdentifier
619566
620567 for _ , eku := range x5c .UnknownExtKeyUsage {
621568 if eku .Equal (oidTCGKpAIKCertificate ) {
@@ -631,13 +578,11 @@ func tpmRemoveEKU(x5c *x509.Certificate) *Error {
631578 unknown = append (unknown , eku )
632579 }
633580
634- if ! hasAiK {
635- return ErrAttestationFormat .WithDetails ("Attestation Identity Key certificate missing required Extended Key Usage." )
636- }
637-
638- x5c .UnknownExtKeyUsage = unknown
581+ out = new (x509.Certificate )
582+ * out = * x5c
583+ out .UnknownExtKeyUsage = unknown
639584
640- return nil
585+ return out , hasAiK
641586}
642587
643588func init () {
0 commit comments