diff --git a/auth/auth.go b/auth/auth.go index 78167b31f..f135335c0 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 754f55bf1..e78160a86 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 1090251f9..182eda33e 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 e13d96f2c..f2fb94f18 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 9e566d619..718cc3738 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 cbc498dff..a3bf7b0ad 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 fb18300d5..73967320e 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 fc87eaa2e..a2306a481 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 ac4df93d2..6cf9876b8 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 0719fbc0f..8cf4ba732 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 70fad6b8f..19a224b34 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,