diff --git a/protocol/attestation.go b/protocol/attestation.go index a0701ed..72a64f0 100644 --- a/protocol/attestation.go +++ b/protocol/attestation.go @@ -232,35 +232,56 @@ func (a *AttestationObject) VerifyAttestation(clientDataHash []byte, mds metadat } var ( - x5c *x509.Certificate - raw []byte - ok bool + x5c *x509.Certificate + parents []*x509.Certificate ) if len(x5cs) == 0 { return ErrInvalidAttestation.WithDetails("Unable to parse attestation certificate from x5c during attestation validation").WithInfo("The attestation had no certificates") } - if raw, ok = x5cs[0].([]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])) - } - - if x5c, 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)) + for _, x5cAny := range x5cs { + x5cRaw, ok := x5cAny.([]byte) + if !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 { + 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 + } else { + parents = append(parents, x5cParsed) + } } if attestationType == string(metadata.AttCA) { if err = tpmParseSANExtension(x5c); err != nil { return err } + if err = tpmRemoveEKU(x5c); err != nil { + return err + } + for _, parent := range parents { + if err = tpmRemoveEKU(parent); err != nil { + return err + } + } } if 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") } - - if _, err = x5c.Verify(entry.MetadataStatement.Verifier()); err != nil { + verifier := entry.MetadataStatement.Verifier() + if len(parents) != 0 { + verifier.Intermediates = x509.NewCertPool() + for _, parent := range parents { + verifier.Intermediates.AddCert(parent) + } + } + 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 347d70a..9251ae3 100644 --- a/protocol/attestation_tpm.go +++ b/protocol/attestation_tpm.go @@ -395,9 +395,35 @@ 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} ) type tpmBasicConstraints struct { IsCA bool `asn1:"optional"` 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 +func tpmRemoveEKU(x5c *x509.Certificate) error { + var unknown []asn1.ObjectIdentifier + hasAiK := false + 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 +}