From 3b5ec5b072f7f00c52bfa8fbb10baa9f0e5eabef Mon Sep 17 00:00:00 2001 From: Andrii Holovko Date: Mon, 27 Nov 2023 10:38:26 +0200 Subject: [PATCH] refactor: wallet binding check for attestation vp (#1540) Signed-off-by: Andrii Holovko --- .../client_attestation_service.go | 30 ++++++++++++++---- .../client_attestation_service_test.go | 31 +++---------------- 2 files changed, 28 insertions(+), 33 deletions(-) diff --git a/pkg/service/clientattestation/client_attestation_service.go b/pkg/service/clientattestation/client_attestation_service.go index 92c43becd..9419754f1 100644 --- a/pkg/service/clientattestation/client_attestation_service.go +++ b/pkg/service/clientattestation/client_attestation_service.go @@ -15,11 +15,15 @@ import ( "time" "github.com/piprate/json-gold/ld" + "github.com/samber/lo" + "github.com/trustbloc/vc-go/jwt" "github.com/trustbloc/vc-go/verifiable" profileapi "github.com/trustbloc/vcs/pkg/profile" ) +const WalletAttestationVCType = "WalletAttestationCredential" + type httpClient interface { Do(req *http.Request) (*http.Response, error) } @@ -60,18 +64,30 @@ func NewService(config *Config) *Service { func (s *Service) ValidateAttestationJWTVP(ctx context.Context, profile *profileapi.Issuer, jwtVP string) error { vp, err := verifiable.ParsePresentation( []byte(jwtVP), - verifiable.WithPresProofChecker(s.proofChecker), + // The verification of proof is conducted manually, along with an extra verification to ensure that signer of + // the VP matches the subject of the attestation VC. + verifiable.WithPresDisabledProofCheck(), verifiable.WithPresJSONLDDocumentLoader(s.documentLoader), ) if err != nil { return fmt.Errorf("parse attestation vp: %w", err) } - if len(vp.Credentials()) == 0 { - return fmt.Errorf("missing attestation vc") + var vc *verifiable.Credential + + for _, credential := range vp.Credentials() { + content := credential.Contents() + + if lo.Contains(content.Types, WalletAttestationVCType) { + vc = credential + + break + } } - vc := vp.Credentials()[0] + if vc == nil { + return fmt.Errorf("missing attestation vc") + } // validate attestation VC opts := []verifiable.CredentialOpt{ @@ -88,12 +104,14 @@ func (s *Service) ValidateAttestationJWTVP(ctx context.Context, profile *profile } vcc := vc.Contents() + if vcc.Expired != nil && time.Now().UTC().After(vcc.Expired.Time) { return fmt.Errorf("attestation vc is expired") } - if len(vcc.Subject) == 0 || vcc.Subject[0].ID != vp.Holder { - return fmt.Errorf("attestation vc subject does not match vp holder") + // validate vp proof with extra check for wallet binding + if err = jwt.CheckProof(jwtVP, s.proofChecker, &vcc.Subject[0].ID, nil); err != nil { + return fmt.Errorf("check attestation vp proof: %w", err) } // check attestation VC status diff --git a/pkg/service/clientattestation/client_attestation_service_test.go b/pkg/service/clientattestation/client_attestation_service_test.go index c2ba5f910..bba217d37 100644 --- a/pkg/service/clientattestation/client_attestation_service_test.go +++ b/pkg/service/clientattestation/client_attestation_service_test.go @@ -16,7 +16,6 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/require" utiltime "github.com/trustbloc/did-go/doc/util/time" - "github.com/trustbloc/kms-go/doc/jose" "github.com/trustbloc/kms-go/spi/kms" "github.com/trustbloc/vc-go/jwt" "github.com/trustbloc/vc-go/proof/checker" @@ -86,15 +85,7 @@ func TestService_ValidateClientAttestationJWTVP(t *testing.T) { { name: "fail to parse attestation vp", setup: func() { - attestationVC := createAttestationVC(t, attestationProofCreator, walletDID, false) - - jwtVP = createAttestationVP(t, attestationVC, - &mockProofCreator{ - SignJWTFunc: func(params jwt.SignParameters, data []byte) ([]byte, error) { - return []byte("invalid signature"), nil - }, - }, - ) + jwtVP = "invalid-jwt-vp" proofChecker = defaultProofChecker @@ -133,7 +124,7 @@ func TestService_ValidateClientAttestationJWTVP(t *testing.T) { }, }, { - name: "attestation vc subject does not match vp holder", + name: "attestation vc subject does not match vp signer", setup: func() { attestationVC := createAttestationVC(t, attestationProofCreator, "invalid-subject", false) @@ -144,7 +135,7 @@ func TestService_ValidateClientAttestationJWTVP(t *testing.T) { vcStatusVerifier.EXPECT().ValidateVCStatus(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) }, check: func(t *testing.T, err error) { - require.ErrorContains(t, err, "attestation vc subject does not match vp holder") + require.ErrorContains(t, err, "check attestation vp proof") }, }, { @@ -198,6 +189,7 @@ func createAttestationVC( ID: uuid.New().String(), Types: []string{ verifiable.VCType, + clientattestation.WalletAttestationVCType, }, Subject: []verifiable.Subject{ { @@ -247,7 +239,6 @@ func createAttestationVP( } vp.ID = uuid.New().String() - vp.Holder = walletDID claims, err := vp.JWTClaims([]string{}, false) require.NoError(t, err) @@ -260,17 +251,3 @@ func createAttestationVP( return jws } - -type mockProofCreator struct { - SignJWTFunc func(params jwt.SignParameters, data []byte) ([]byte, error) -} - -func (m *mockProofCreator) SignJWT(params jwt.SignParameters, data []byte) ([]byte, error) { - return m.SignJWTFunc(params, data) -} - -func (m *mockProofCreator) CreateJWTHeaders(_ jwt.SignParameters) (jose.Headers, error) { - return map[string]interface{}{ - jose.HeaderAlgorithm: "ES256", - }, nil -}