From 493a37772793fa50f772ed28f6aa80c56ea38fe6 Mon Sep 17 00:00:00 2001 From: James Elliott Date: Wed, 27 Nov 2024 20:09:02 +1100 Subject: [PATCH] feat: enhancements --- protocol/attestation.go | 46 ++------------------------------- protocol/metadata.go | 56 +++++++++++++++++++++++++++++++++-------- webauthn/login.go | 2 +- 3 files changed, 49 insertions(+), 55 deletions(-) diff --git a/protocol/attestation.go b/protocol/attestation.go index 8adfdec..378d87c 100644 --- a/protocol/attestation.go +++ b/protocol/attestation.go @@ -3,7 +3,6 @@ package protocol import ( "context" "crypto/sha256" - "crypto/x509" "encoding/json" "fmt" @@ -173,7 +172,6 @@ func (a *AttestationObject) VerifyAttestation(clientDataHash []byte, mds metadat var ( aaguid uuid.UUID - entry *metadata.Entry ) if len(a.AuthData.AttData.AAGUID) != 0 { @@ -188,48 +186,8 @@ func (a *AttestationObject) VerifyAttestation(clientDataHash []byte, mds metadat var protoErr *Error - ctx := context.Background() - - if entry, protoErr = ValidateMetadata(context.Background(), aaguid, attestationType, mds); protoErr != nil { - return ErrInvalidAttestation.WithInfo(fmt.Sprintf("Error occurred validating metadata during attestation validation: %+v", err)).WithDetails(protoErr.DevInfo) - } - - if entry == nil { - return nil - } - - if mds.GetValidateTrustAnchor(ctx) { - if x5cs == nil { - return nil - } - - var ( - x5c *x509.Certificate - raw []byte - ok bool - ) - - 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)) - } - - 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 { - return ErrInvalidAttestation.WithDetails(fmt.Sprintf("Unable to validate attestation signature statement during attestation validation: invalid certificate chain from MDS: %v", err)) - } - } + if protoErr = ValidateMetadata(context.Background(), mds, aaguid, attestationType, x5cs); protoErr != nil { + return ErrInvalidAttestation.WithInfo(fmt.Sprintf("Error occurred validating metadata during attestation validation: %+v", protoErr)).WithDetails(protoErr.DevInfo) } return nil diff --git a/protocol/metadata.go b/protocol/metadata.go index edb9744..7e66c47 100644 --- a/protocol/metadata.go +++ b/protocol/metadata.go @@ -2,6 +2,7 @@ package protocol import ( "context" + "crypto/x509" "fmt" "github.com/google/uuid" @@ -9,29 +10,30 @@ import ( "github.com/go-webauthn/webauthn/metadata" ) -func ValidateMetadata(ctx context.Context, aaguid uuid.UUID, attestationType string, mds metadata.Provider) (entry *metadata.Entry, protoErr *Error) { +func ValidateMetadata(ctx context.Context, mds metadata.Provider, aaguid uuid.UUID, attestationType string, x5cs []any) (protoErr *Error) { if mds == nil { - return nil, nil + return nil } var ( - err error + entry *metadata.Entry + err error ) if entry, err = mds.GetEntry(ctx, aaguid); err != nil { - return nil, ErrMetadata.WithInfo(fmt.Sprintf("Failed to validate authenticator metadata for Authenticator Attestation GUID '%s'. Error occurred retreiving the metadata entry: %+v", aaguid, err)) + return ErrMetadata.WithInfo(fmt.Sprintf("Failed to validate authenticator metadata for Authenticator Attestation GUID '%s'. Error occurred retreiving the metadata entry: %+v", aaguid, err)) } if entry == nil { if aaguid == uuid.Nil && mds.GetValidateEntryPermitZeroAAGUID(ctx) { - return nil, nil + return nil } if mds.GetValidateEntry(ctx) { - return nil, ErrMetadata.WithInfo(fmt.Sprintf("Failed to validate authenticator metadata for Authenticator Attestation GUID '%s'. The authenticator has no registered metadata.", aaguid)) + return ErrMetadata.WithInfo(fmt.Sprintf("Failed to validate authenticator metadata for Authenticator Attestation GUID '%s'. The authenticator has no registered metadata.", aaguid)) } - return nil, nil + return nil } if mds.GetValidateAttestationTypes(ctx) { @@ -46,15 +48,49 @@ func ValidateMetadata(ctx context.Context, aaguid uuid.UUID, attestationType str } if !found { - return entry, ErrMetadata.WithInfo(fmt.Sprintf("Failed to validate authenticator metadata for Authenticator Attestation GUID '%s'. The attestation type '%s' is not known to be used by this authenticator.", aaguid.String(), attestationType)) + return ErrMetadata.WithInfo(fmt.Sprintf("Failed to validate authenticator metadata for Authenticator Attestation GUID '%s'. The attestation type '%s' is not known to be used by this authenticator.", aaguid.String(), attestationType)) } } if mds.GetValidateStatus(ctx) { if err = mds.ValidateStatusReports(ctx, entry.StatusReports); err != nil { - return entry, ErrMetadata.WithInfo(fmt.Sprintf("Failed to validate authenticator metadata for Authenticator Attestation GUID '%s'. Error occurred validating the authenticator status: %+v", aaguid, err)) + return ErrMetadata.WithInfo(fmt.Sprintf("Failed to validate authenticator metadata for Authenticator Attestation GUID '%s'. Error occurred validating the authenticator status: %+v", aaguid, err)) } } - return entry, nil + if mds.GetValidateTrustAnchor(ctx) { + if x5cs == nil { + return nil + } + + var ( + x5c *x509.Certificate + data []byte + ok bool + ) + + if len(x5cs) == 0 { + return ErrMetadata.WithDetails(fmt.Sprintf("Failed to parse attestation certificate from x5c during attestation validation for Authenticator Attestation GUID '%s'.", aaguid)).WithInfo("The attestation had no certificates") + } + + if data, ok = x5cs[0].([]byte); !ok { + return ErrMetadata.WithDetails(fmt.Sprintf("Failed to parse attestation certificate from x5c during attestation validation for Authenticator Attestation GUID '%s'.", aaguid)).WithInfo(fmt.Sprintf("The first certificate in the attestation was type '%T' but '[]byte' was expected", x5cs[0])) + } + + if x5c, err = x509.ParseCertificate(data); err != nil { + return ErrMetadata.WithDetails(fmt.Sprintf("Failed to parse attestation certificate from x5c during attestation validation for Authenticator Attestation GUID '%s'.", aaguid)).WithInfo(fmt.Sprintf("Error returned from x509.ParseCertificate: %+v", err)) + } + + if x5c.Subject.CommonName != x5c.Issuer.CommonName { + if !entry.MetadataStatement.AttestationTypes.HasBasicFull() { + return ErrMetadata.WithDetails(fmt.Sprintf("Failed to validate attestation statement signature during attestation validation for Authenticator Attestation GUID '%s'. Attestation was provided in the full format but the authenticator doesn't support the full attestation format.", aaguid)) + } + + if _, err = x5c.Verify(entry.MetadataStatement.Verifier()); err != nil { + return ErrMetadata.WithDetails(fmt.Sprintf("Failed to validate attestation statement signature during attestation validation for Authenticator Attestation GUID '%s'. The attestation certificate could not be verified due to an error validating the trust chain agaisnt the Metadata Service.", aaguid)) + } + } + } + + return nil } diff --git a/webauthn/login.go b/webauthn/login.go index 46d4875..abddd58 100644 --- a/webauthn/login.go +++ b/webauthn/login.go @@ -319,7 +319,7 @@ func (webauthn *WebAuthn) validateLogin(user User, session SessionData, parsedRe var protoErr *protocol.Error - if _, protoErr = protocol.ValidateMetadata(context.Background(), aaguid, credential.AttestationType, webauthn.Config.MDS); protoErr != nil { + if protoErr = protocol.ValidateMetadata(context.Background(), webauthn.Config.MDS, aaguid, credential.AttestationType, nil); protoErr != nil { return nil, protocol.ErrBadRequest.WithDetails("Failed to validate credential record metadata").WithInfo(protoErr.DevInfo) } }