From bcde56cfe84ac3bea6f6604b1dc369aa64eeb576 Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Fri, 20 Dec 2024 11:35:50 +0100 Subject: [PATCH] pass pki.Validator to resolve validator --- auth/auth.go | 2 +- auth/services/oauth/relying_party.go | 7 +++-- vcr/credential/resolver.go | 5 ++-- vcr/credential/resolver_test.go | 8 +++--- vcr/credential/validator.go | 33 ++++++++++++++++++++---- vcr/credential/validator_test.go | 38 +++++++++++++++++++++++++--- vcr/issuer/issuer.go | 3 ++- vcr/issuer/issuer_test.go | 2 +- vcr/vcr.go | 2 +- vcr/verifier/verifier.go | 7 +++-- vcr/verifier/verifier_test.go | 2 +- 11 files changed, 85 insertions(+), 24 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index 78167b31f3..f135335c01 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -178,7 +178,7 @@ func (auth *Auth) Configure(config core.ServerConfig) error { auth.authzServer = oauth.NewAuthorizationServer(auth.vdrInstance.Resolver(), auth.vcr, auth.vcr.Verifier(), auth.serviceResolver, auth.keyStore, auth.contractNotary, auth.jsonldManager, accessTokenLifeSpan) auth.relyingParty = oauth.NewRelyingParty(auth.vdrInstance.Resolver(), auth.serviceResolver, - auth.keyStore, auth.vcr.Wallet(), auth.httpClientTimeout, auth.tlsConfig, config.Strictmode) + auth.keyStore, auth.vcr.Wallet(), auth.httpClientTimeout, auth.tlsConfig, config.Strictmode, auth.pkiProvider) if err := auth.authzServer.Configure(auth.config.ClockSkew, config.Strictmode); err != nil { return err diff --git a/auth/services/oauth/relying_party.go b/auth/services/oauth/relying_party.go index 754f55bf11..e78160a86c 100644 --- a/auth/services/oauth/relying_party.go +++ b/auth/services/oauth/relying_party.go @@ -22,6 +22,7 @@ import ( "context" "crypto/tls" "fmt" + "github.com/nuts-foundation/nuts-node/pki" "net/url" "strings" "time" @@ -50,12 +51,13 @@ type relyingParty struct { httpClientTimeout time.Duration httpClientTLS *tls.Config wallet holder.Wallet + pkiValidator pki.Validator } // NewRelyingParty returns an implementation of RelyingParty func NewRelyingParty( didResolver resolver.DIDResolver, serviceResolver didman.CompoundServiceResolver, privateKeyStore nutsCrypto.KeyStore, - wallet holder.Wallet, httpClientTimeout time.Duration, httpClientTLS *tls.Config, strictMode bool) RelyingParty { + wallet holder.Wallet, httpClientTimeout time.Duration, httpClientTLS *tls.Config, strictMode bool, pkiValidator pki.Validator) RelyingParty { return &relyingParty{ keyResolver: resolver.DIDKeyResolver{Resolver: didResolver}, serviceResolver: serviceResolver, @@ -64,6 +66,7 @@ func NewRelyingParty( httpClientTLS: httpClientTLS, strictMode: strictMode, wallet: wallet, + pkiValidator: pkiValidator, } } @@ -81,7 +84,7 @@ func (s *relyingParty) CreateJwtGrant(ctx context.Context, request services.Crea } for _, verifiableCredential := range request.Credentials { - validator := credential.FindValidator(verifiableCredential) + validator := credential.FindValidator(verifiableCredential, s.pkiValidator) if err := validator.Validate(verifiableCredential); err != nil { return nil, fmt.Errorf("invalid VerifiableCredential: %w", err) } diff --git a/vcr/credential/resolver.go b/vcr/credential/resolver.go index 1090251f94..182eda33e7 100644 --- a/vcr/credential/resolver.go +++ b/vcr/credential/resolver.go @@ -25,12 +25,13 @@ import ( "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/go-did/vc" "github.com/nuts-foundation/nuts-node/crypto" + "github.com/nuts-foundation/nuts-node/pki" "github.com/nuts-foundation/nuts-node/vcr/signature/proof" ) // FindValidator finds the Validator the provided credential based on its Type // When no additional type is provided, it returns the default validator -func FindValidator(credential vc.VerifiableCredential) Validator { +func FindValidator(credential vc.VerifiableCredential, pkiValidator pki.Validator) Validator { if vcTypes := ExtractTypes(credential); len(vcTypes) > 0 { for _, t := range vcTypes { switch t { @@ -39,7 +40,7 @@ func FindValidator(credential vc.VerifiableCredential) Validator { case NutsAuthorizationCredentialType: return nutsAuthorizationCredentialValidator{} case X509CredentialType: - return x509CredentialValidator{} + return x509CredentialValidator{pkiValidator: pkiValidator} } } } diff --git a/vcr/credential/resolver_test.go b/vcr/credential/resolver_test.go index e13d96f2c5..f2fb94f18a 100644 --- a/vcr/credential/resolver_test.go +++ b/vcr/credential/resolver_test.go @@ -39,19 +39,19 @@ import ( func TestFindValidator(t *testing.T) { t.Run("an unknown type returns the default validator", func(t *testing.T) { - assert.IsType(t, defaultCredentialValidator{}, FindValidator(vc.VerifiableCredential{})) + assert.IsType(t, defaultCredentialValidator{}, FindValidator(vc.VerifiableCredential{}, nil)) }) t.Run("validator found for NutsOrganizationCredential", func(t *testing.T) { - assert.IsType(t, nutsOrganizationCredentialValidator{}, FindValidator(test.ValidNutsOrganizationCredential(t))) + assert.IsType(t, nutsOrganizationCredentialValidator{}, FindValidator(test.ValidNutsOrganizationCredential(t), nil)) }) t.Run("validator found for NutsAuthorizationCredential", func(t *testing.T) { - assert.IsType(t, nutsAuthorizationCredentialValidator{}, FindValidator(test.ValidNutsAuthorizationCredential(t))) + assert.IsType(t, nutsAuthorizationCredentialValidator{}, FindValidator(test.ValidNutsAuthorizationCredential(t), nil)) }) t.Run("validator found for X509Credential", func(t *testing.T) { - assert.IsType(t, x509CredentialValidator{}, FindValidator(test.ValidX509Credential(t))) + assert.IsType(t, x509CredentialValidator{}, FindValidator(test.ValidX509Credential(t), nil)) }) } diff --git a/vcr/credential/validator.go b/vcr/credential/validator.go index 9e566d6197..718cc3738d 100644 --- a/vcr/credential/validator.go +++ b/vcr/credential/validator.go @@ -20,18 +20,21 @@ package credential import ( + "crypto/x509" + "encoding/base64" "encoding/json" "errors" "fmt" - "github.com/nuts-foundation/nuts-node/crypto" - "github.com/nuts-foundation/nuts-node/vdr/didx509" - "net/url" - "strings" - + "github.com/lestrrat-go/jwx/v2/jwk" "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/go-did/vc" + "github.com/nuts-foundation/nuts-node/crypto" + "github.com/nuts-foundation/nuts-node/pki" "github.com/nuts-foundation/nuts-node/vcr/revocation" + "github.com/nuts-foundation/nuts-node/vdr/didx509" "github.com/nuts-foundation/nuts-node/vdr/resolver" + "net/url" + "strings" ) // Validator is the interface specific VC verification. @@ -259,6 +262,7 @@ func validateNutsCredentialID(credential vc.VerifiableCredential) error { // x509CredentialValidator checks the did:x509 issuer and if the credentialSubject claims match the x509 certificate type x509CredentialValidator struct { + pkiValidator pki.Validator } func (d x509CredentialValidator) Validate(credential vc.VerifiableCredential) error { @@ -287,6 +291,25 @@ func (d x509CredentialValidator) Validate(credential vc.VerifiableCredential) er return fmt.Errorf("%w: %w", errValidation, err) } + chainHeader, _ := resolveMetadata.GetProtectedHeaderChain(jwk.X509CertChainKey) // already succeeded for resolve + // convert cert.Chain to []*x509.Certificate + chain := make([]*x509.Certificate, chainHeader.Len()) + for i := 0; i < chainHeader.Len(); i++ { + base64Cert, _ := chainHeader.Get(i) + der, err := base64.StdEncoding.DecodeString(string(base64Cert)) + if err != nil { + return fmt.Errorf("%w: invalid certificate chain: %w", errValidation, err) + } + cert, err := x509.ParseCertificate(der) + if err != nil { + return fmt.Errorf("%w: invalid certificate chain: %w", errValidation, err) + } + chain[i] = cert + } + if err = d.pkiValidator.CheckCRL(chain); err != nil { + return fmt.Errorf("%w: %w", errValidation, err) + } + return (defaultCredentialValidator{}).Validate(credential) } diff --git a/vcr/credential/validator_test.go b/vcr/credential/validator_test.go index cbc498dff7..a3bf7b0ad0 100644 --- a/vcr/credential/validator_test.go +++ b/vcr/credential/validator_test.go @@ -20,6 +20,8 @@ package credential import ( + "github.com/nuts-foundation/nuts-node/pki" + "go.uber.org/mock/gomock" "testing" "time" @@ -498,19 +500,31 @@ func Test_validateCredentialStatus(t *testing.T) { } func TestX509CredentialValidator_Validate(t *testing.T) { - validator := x509CredentialValidator{} + ctx := createTestContext(t) t.Run("ok", func(t *testing.T) { x509credential := test.ValidX509Credential(t) + ctx := createTestContext(t) + ctx.pkiValidator.EXPECT().CheckCRL(gomock.Any()).Return(nil) - err := validator.Validate(x509credential) + err := ctx.validator.Validate(x509credential) assert.NoError(t, err) }) + t.Run("CRL check failed", func(t *testing.T) { + x509credential := test.ValidX509Credential(t) + ctx := createTestContext(t) + ctx.pkiValidator.EXPECT().CheckCRL(gomock.Any()).Return(assert.AnError) + + err := ctx.validator.Validate(x509credential) + + assert.ErrorIs(t, err, errValidation) + assert.ErrorIs(t, err, assert.AnError) + }) t.Run("invalid did", func(t *testing.T) { x509credential := vc.VerifiableCredential{Issuer: ssi.MustParseURI("not_a_did")} - err := validator.Validate(x509credential) + err := ctx.validator.Validate(x509credential) assert.ErrorIs(t, err, errValidation) assert.ErrorIs(t, err, did.ErrInvalidDID) @@ -555,7 +569,7 @@ func TestX509CredentialValidator_Validate(t *testing.T) { return builder }) - err := validator.Validate(x509credential) + err := ctx.validator.Validate(x509credential) assert.ErrorIs(t, err, errValidation) assert.ErrorContains(t, err, tc.expectedError) @@ -563,3 +577,19 @@ func TestX509CredentialValidator_Validate(t *testing.T) { } }) } + +type testContext struct { + ctrl *gomock.Controller + validator x509CredentialValidator + pkiValidator *pki.MockValidator +} + +func createTestContext(t *testing.T) testContext { + ctrl := gomock.NewController(t) + pkiValidator := pki.NewMockValidator(ctrl) + return testContext{ + ctrl: ctrl, + validator: x509CredentialValidator{pkiValidator: pkiValidator}, + pkiValidator: pkiValidator, + } +} diff --git a/vcr/issuer/issuer.go b/vcr/issuer/issuer.go index fb18300d5d..73967320e2 100644 --- a/vcr/issuer/issuer.go +++ b/vcr/issuer/issuer.go @@ -115,7 +115,8 @@ func (i issuer) Issue(ctx context.Context, template vc.VerifiableCredential, opt } // Validate the VC using the type-specific validator - validator := credential.FindValidator(*createdVC) + // we don't pass a pki.Validator since we don't issue x509 certs + validator := credential.FindValidator(*createdVC, nil) if err := validator.Validate(*createdVC); err != nil { return nil, err } diff --git a/vcr/issuer/issuer_test.go b/vcr/issuer/issuer_test.go index fc87eaa2eb..a2306a4813 100644 --- a/vcr/issuer/issuer_test.go +++ b/vcr/issuer/issuer_test.go @@ -944,7 +944,7 @@ func TestIssuer_StatusList(t *testing.T) { vDIDResolverMock.EXPECT().Resolve(gomock.Any(), gomock.Any()) vKeyResolverMock := resolver.NewMockKeyResolver(ctrl) vKeyResolverMock.EXPECT().ResolveKeyByID(gomock.Any(), gomock.Any(), gomock.Any()).Return(signingKey, nil) - verif := verifier.NewVerifier(vStoreMock, vDIDResolverMock, vKeyResolverMock, jsonldManager, nil, &revocation.StatusList2021{}) + verif := verifier.NewVerifier(vStoreMock, vDIDResolverMock, vKeyResolverMock, jsonldManager, nil, &revocation.StatusList2021{}, nil) assert.NoError(t, verif.Verify(*result, true, true, nil)) }) t.Run("error - unknown status list credential", func(t *testing.T) { diff --git a/vcr/vcr.go b/vcr/vcr.go index ac4df93d20..6cf9876b89 100644 --- a/vcr/vcr.go +++ b/vcr/vcr.go @@ -231,7 +231,7 @@ func (c *vcr) Configure(config core.ServerConfig) error { status := revocation.NewStatusList2021(c.storageClient.GetSQLDatabase(), client.NewWithCache(config.HTTPClient.Timeout), config.URL) c.issuer = issuer.NewIssuer(c.issuerStore, c, networkPublisher, openidHandlerFn, didResolver, c.keyStore, c.jsonldManager, c.trustConfig, status) - c.verifier = verifier.NewVerifier(c.verifierStore, didResolver, c.keyResolver, c.jsonldManager, c.trustConfig, status) + c.verifier = verifier.NewVerifier(c.verifierStore, didResolver, c.keyResolver, c.jsonldManager, c.trustConfig, status, c.pkiProvider) if !c.network.Disabled() { c.ambassador = NewAmbassador(c.network, c, c.verifier, c.eventManager) diff --git a/vcr/verifier/verifier.go b/vcr/verifier/verifier.go index 0719fbc0fc..8cf4ba7328 100644 --- a/vcr/verifier/verifier.go +++ b/vcr/verifier/verifier.go @@ -22,6 +22,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/nuts-foundation/nuts-node/pki" "github.com/nuts-foundation/nuts-node/vcr/revocation" "strings" "time" @@ -56,6 +57,7 @@ type verifier struct { trustConfig *trust.Config signatureVerifier credentialStatus revocation.StatusList2021Verifier + pkiValidator pki.Validator } // VerificationError is used to describe a VC/VP verification failure. @@ -83,13 +85,14 @@ func (e VerificationError) Error() string { } // NewVerifier creates a new instance of the verifier. It needs a key resolver for validating signatures. -func NewVerifier(store Store, didResolver resolver.DIDResolver, keyResolver resolver.KeyResolver, jsonldManager jsonld.JSONLD, trustConfig *trust.Config, credentialStatus *revocation.StatusList2021) Verifier { +func NewVerifier(store Store, didResolver resolver.DIDResolver, keyResolver resolver.KeyResolver, jsonldManager jsonld.JSONLD, trustConfig *trust.Config, credentialStatus *revocation.StatusList2021, pkiValidator pki.Validator) Verifier { v := &verifier{store: store, didResolver: didResolver, keyResolver: keyResolver, jsonldManager: jsonldManager, trustConfig: trustConfig, signatureVerifier: signatureVerifier{ keyResolver: keyResolver, jsonldManager: jsonldManager, }, credentialStatus: credentialStatus, + pkiValidator: pkiValidator, } credentialStatus.VerifySignature = v.VerifySignature return v @@ -99,7 +102,7 @@ func NewVerifier(store Store, didResolver resolver.DIDResolver, keyResolver reso // It currently checks if the credential has the required fields and values, if it is valid at the given time and optional the signature. func (v verifier) Verify(credentialToVerify vc.VerifiableCredential, allowUntrusted bool, checkSignature bool, validAt *time.Time) error { // it must have valid content - validator := credential.FindValidator(credentialToVerify) + validator := credential.FindValidator(credentialToVerify, v.pkiValidator) if err := validator.Validate(credentialToVerify); err != nil { return err } diff --git a/vcr/verifier/verifier_test.go b/vcr/verifier/verifier_test.go index 70fad6b8fe..19a224b342 100644 --- a/vcr/verifier/verifier_test.go +++ b/vcr/verifier/verifier_test.go @@ -848,7 +848,7 @@ func newMockContext(t *testing.T) mockContext { verifierStore := NewMockStore(ctrl) trustConfig := trust.NewConfig(path.Join(io.TestDirectory(t), "trust.yaml")) db := orm.NewTestDatabase(t) - verifier := NewVerifier(verifierStore, didResolver, keyResolver, jsonldManager, trustConfig, revocation.NewStatusList2021(db, nil, "")).(*verifier) + verifier := NewVerifier(verifierStore, didResolver, keyResolver, jsonldManager, trustConfig, revocation.NewStatusList2021(db, nil, ""), nil).(*verifier) return mockContext{ ctrl: ctrl, verifier: verifier,