diff --git a/metadata/metadata.go b/metadata/metadata.go index 0a43e682..d6e91832 100644 --- a/metadata/metadata.go +++ b/metadata/metadata.go @@ -319,15 +319,26 @@ type Statement struct { AuthenticatorGetInfo AuthenticatorGetInfo } -func (s *Statement) Verifier() (opts x509.VerifyOptions) { +func (s *Statement) Verifier(x5cis []*x509.Certificate) (opts x509.VerifyOptions) { roots := x509.NewCertPool() for _, root := range s.AttestationRootCertificates { roots.AddCert(root) } + var intermediates *x509.CertPool + + if len(x5cis) > 0 { + intermediates = x509.NewCertPool() + + for _, x5c := range x5cis { + intermediates.AddCert(x5c) + } + } + return x509.VerifyOptions{ - Roots: roots, + Roots: roots, + Intermediates: intermediates, } } diff --git a/protocol/attestation.go b/protocol/attestation.go index 72a64f06..be30e92a 100644 --- a/protocol/attestation.go +++ b/protocol/attestation.go @@ -232,8 +232,10 @@ func (a *AttestationObject) VerifyAttestation(clientDataHash []byte, mds metadat } var ( - x5c *x509.Certificate - parents []*x509.Certificate + x5c, parsed *x509.Certificate + x5cis []*x509.Certificate + raw []byte + ok bool ) if len(x5cs) == 0 { @@ -241,18 +243,18 @@ func (a *AttestationObject) VerifyAttestation(clientDataHash []byte, mds metadat } for _, x5cAny := range x5cs { - x5cRaw, ok := x5cAny.([]byte) - if !ok { + if raw, ok = x5cAny.([]byte); !ok { return ErrInvalidAttestation.WithDetails("Unable to parse attestation certificate from x5c during attestation validation").WithInfo(fmt.Sprintf("The first certificate in the attestation was type '%T' but '[]byte' was expected", x5cs[0])) } - x5cParsed, err := x509.ParseCertificate(x5cRaw) - if err != nil { + + if parsed, err = x509.ParseCertificate(raw); err != nil { return ErrInvalidAttestation.WithDetails("Unable to parse attestation certificate from x5c during attestation validation").WithInfo(fmt.Sprintf("Error returned from x509.ParseCertificate: %+v", err)) } + if x5c == nil { - x5c = x5cParsed + x5c = parsed } else { - parents = append(parents, x5cParsed) + x5cis = append(x5cis, parsed) } } @@ -260,27 +262,25 @@ func (a *AttestationObject) VerifyAttestation(clientDataHash []byte, mds metadat if err = tpmParseSANExtension(x5c); err != nil { return err } + if err = tpmRemoveEKU(x5c); err != nil { return err } - for _, parent := range parents { + + for _, parent := range x5cis { if err = tpmRemoveEKU(parent); err != nil { return err } } } - if x5c.Subject.CommonName != x5c.Issuer.CommonName { + if x5c != nil && x5c.Subject.CommonName != x5c.Issuer.CommonName { if !entry.MetadataStatement.AttestationTypes.HasBasicFull() { return ErrInvalidAttestation.WithDetails("Unable to validate attestation statement signature during attestation validation: attestation with full attestation from authenticator that does not support full attestation") } - verifier := entry.MetadataStatement.Verifier() - if len(parents) != 0 { - verifier.Intermediates = x509.NewCertPool() - for _, parent := range parents { - verifier.Intermediates.AddCert(parent) - } - } + + verifier := entry.MetadataStatement.Verifier(x5cis) + if _, err = x5c.Verify(verifier); err != nil { return ErrInvalidAttestation.WithDetails(fmt.Sprintf("Unable to validate attestation signature statement during attestation validation: invalid certificate chain from MDS: %v", err)) } diff --git a/protocol/attestation_tpm.go b/protocol/attestation_tpm.go index 9251ae39..873e8640 100644 --- a/protocol/attestation_tpm.go +++ b/protocol/attestation_tpm.go @@ -395,10 +395,7 @@ var ( oidExtensionSubjectAltName = []int{2, 5, 29, 17} oidExtensionExtendedKeyUsage = []int{2, 5, 29, 37} oidExtensionBasicConstraints = []int{2, 5, 29, 19} - - // From wincrypt.h of Windows SDK. - // Enhanced Key Usage for Privacy CA encryption certificate - oidKpPrivacyCA = []int{1, 3, 6, 1, 4, 1, 311, 21, 36} + oidKpPrivacyCA = []int{1, 3, 6, 1, 4, 1, 311, 21, 36} ) type tpmBasicConstraints struct { @@ -406,24 +403,32 @@ type tpmBasicConstraints struct { MaxPathLen int `asn1:"optional,default:-1"` } -// remove extension key usage to avoid ExtKeyUsage check failure -// see also https://github.com/go-webauthn/webauthn/issues/342 +// Remove extension key usage to avoid ExtKeyUsage check failure. func tpmRemoveEKU(x5c *x509.Certificate) error { - var unknown []asn1.ObjectIdentifier - hasAiK := false + var ( + unknown []asn1.ObjectIdentifier + hasAiK bool + ) + for _, eku := range x5c.UnknownExtKeyUsage { if eku.Equal(tcgKpAIKCertificate) { hasAiK = true + continue } + if eku.Equal(oidKpPrivacyCA) { continue } + unknown = append(unknown, eku) } + if !hasAiK { return ErrAttestationFormat.WithDetails("AIK certificate missing EKU") } + x5c.UnknownExtKeyUsage = unknown + return nil }