From 9030c9ec083dc2519e2c4c2c9ef95e5f30ce172e Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Wed, 31 Jul 2024 08:34:50 +0200 Subject: [PATCH] remove calls to deprecates documentManager for new key creation (#3272) --- cmd/root.go | 2 +- main_test.go | 6 ++ test/node/server.go | 1 + vcr/test/openid4vci_integration_test.go | 12 +-- vdr/api/v1/api.go | 33 +++++- vdr/api/v1/api_test.go | 25 +++-- vdr/didnuts/ambassador_test.go | 64 +++++------ vdr/didnuts/manager.go | 126 +--------------------- vdr/didnuts/manager_test.go | 134 +----------------------- vdr/didnuts/validators_test.go | 58 +++++----- vdr/didsubject/management.go | 27 +---- vdr/didsubject/management_mock.go | 46 -------- vdr/didsubject/manager.go | 14 ++- vdr/interface.go | 1 + vdr/legacy_integration_test.go | 45 ++++++-- vdr/vdr.go | 2 + 16 files changed, 173 insertions(+), 423 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 2d83f81f55..59570b6209 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -209,7 +209,7 @@ func CreateSystem(shutdownCallback context.CancelFunc) *core.System { system.RegisterRoutes(&core.LandingPage{}) system.RegisterRoutes(&cryptoAPI.Wrapper{C: cryptoInstance, K: resolver.DIDKeyResolver{Resolver: vdrInstance.Resolver()}}) system.RegisterRoutes(&networkAPI.Wrapper{Service: networkInstance}) - system.RegisterRoutes(&vdrAPI.Wrapper{VDR: vdrInstance}) + system.RegisterRoutes(&vdrAPI.Wrapper{VDR: vdrInstance, SubjectManager: vdrInstance}) system.RegisterRoutes(&vdrAPIv2.Wrapper{VDR: vdrInstance, SubjectManager: vdrInstance}) system.RegisterRoutes(&vcrAPI.Wrapper{VCR: credentialInstance, ContextManager: jsonld}) system.RegisterRoutes(&openid4vciAPI.Wrapper{ diff --git a/main_test.go b/main_test.go index 00fb1b8d51..62d34e3a5a 100644 --- a/main_test.go +++ b/main_test.go @@ -34,6 +34,7 @@ import ( "github.com/nuts-foundation/nuts-node/network" "github.com/nuts-foundation/nuts-node/test" "github.com/nuts-foundation/nuts-node/test/pki" + "github.com/nuts-foundation/nuts-node/vdr" v1 "github.com/nuts-foundation/nuts-node/vdr/api/v1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -229,12 +230,16 @@ func getIntegrationTestConfig(t *testing.T, testDirectory string) (core.ServerCo httpConfig.Internal.Address = fmt.Sprintf("localhost:%d", test.FreeTCPPort()) httpConfig.Public.Address = fmt.Sprintf("localhost:%d", test.FreeTCPPort()) + vdrConfig := vdr.DefaultConfig() + vdrConfig.DIDMethods = []string{"nuts"} + return config, ModuleConfig{ Network: networkConfig, Auth: authConfig, Crypto: cryptoConfig, Events: eventsConfig, HTTP: httpConfig, + VDR: vdrConfig, } } @@ -244,4 +249,5 @@ type ModuleConfig struct { Crypto crypto.Config `koanf:"crypto"` Events events.Config `koanf:"events"` HTTP httpEngine.Config `koanf:"http"` + VDR vdr.Config `koanf:"vdr"` } diff --git a/test/node/server.go b/test/node/server.go index 4109fe519e..5043f89518 100644 --- a/test/node/server.go +++ b/test/node/server.go @@ -75,6 +75,7 @@ func StartServer(t *testing.T, configFunc ...func(internalHttpServerURL, publicH t.Setenv("NUTS_EVENTS_NATS_PORT", natsPort) t.Setenv("NUTS_EVENTS_NATS_HOSTNAME", "localhost") t.Setenv("NUTS_URL", publicHttpServerURL) + t.Setenv("NUTS_VDR_DIDMETHODS", "nuts") for _, fn := range configFunc { fn(internalHttpServerURL, publicHttpServerURL) diff --git a/vcr/test/openid4vci_integration_test.go b/vcr/test/openid4vci_integration_test.go index 2fb4443a43..146c5d6155 100644 --- a/vcr/test/openid4vci_integration_test.go +++ b/vcr/test/openid4vci_integration_test.go @@ -26,7 +26,6 @@ import ( "github.com/nuts-foundation/nuts-node/network/log" "github.com/nuts-foundation/nuts-node/vcr/issuer" "github.com/nuts-foundation/nuts-node/vcr/openid4vci" - "github.com/nuts-foundation/nuts-node/vdr" "github.com/nuts-foundation/nuts-node/vdr/didsubject" "github.com/nuts-foundation/nuts-node/vdr/resolver" "github.com/stretchr/testify/assert" @@ -42,7 +41,6 @@ import ( "github.com/nuts-foundation/go-did/did" "github.com/nuts-foundation/go-did/vc" "github.com/nuts-foundation/nuts-node/audit" - "github.com/nuts-foundation/nuts-node/didman" "github.com/nuts-foundation/nuts-node/test" "github.com/nuts-foundation/nuts-node/test/node" "github.com/nuts-foundation/nuts-node/vcr" @@ -247,17 +245,17 @@ func testCredential() vc.VerifiableCredential { } func registerDID(t *testing.T, system *core.System) did.DID { - vdrService := system.FindEngineByName("vdr").(vdr.VDR) + vdrService := system.FindEngineByName("vdr").(didsubject.SubjectManager) ctx := audit.TestContext() - didDocument, _, err := vdrService.NutsDocumentManager().Create(ctx, didsubject.DefaultCreationOptions()) + didDocument, _, err := vdrService.Create(ctx, didsubject.DefaultCreationOptions().With(didsubject.NutsLegacyNamingOption{})) require.NoError(t, err) - return didDocument.ID + return didDocument[0].ID } func registerBaseURL(t *testing.T, httpServerURL string, system *core.System, id did.DID) { - didmanService := system.FindEngineByName("didman").(didman.Didman) + subjectManager := system.FindEngineByName("vdr").(didsubject.SubjectManager) baseURL, _ := url.Parse(httpServerURL) - _, err := didmanService.AddEndpoint(audit.TestContext(), id, resolver.BaseURLServiceType, *baseURL) + _, err := subjectManager.CreateService(audit.TestContext(), id.String(), did.Service{Type: resolver.BaseURLServiceType, ServiceEndpoint: baseURL.String()}) require.NoError(t, err) } diff --git a/vdr/api/v1/api.go b/vdr/api/v1/api.go index 0c163b27b8..93e080aa6a 100644 --- a/vdr/api/v1/api.go +++ b/vdr/api/v1/api.go @@ -21,6 +21,7 @@ package v1 import ( "context" + "errors" "fmt" "github.com/nuts-foundation/nuts-node/audit" "github.com/nuts-foundation/nuts-node/vdr" @@ -41,7 +42,8 @@ var _ core.ErrorStatusCodeResolver = (*Wrapper)(nil) // Wrapper is needed to connect the implementation to the echo ServiceWrapper type Wrapper struct { - VDR vdr.VDR + VDR vdr.VDR + SubjectManager didsubject.SubjectManager } // ResolveStatusCode maps errors returned by this API to specific HTTP status codes. @@ -88,10 +90,21 @@ func (a *Wrapper) AddNewVerificationMethod(ctx context.Context, request AddNewVe opts = &VerificationMethodRelationship{} } - vm, err := a.VDR.NutsDocumentManager().AddVerificationMethod(ctx, *d, opts.ToFlags(didnuts.DefaultKeyFlags())) + vms, err := a.SubjectManager.AddVerificationMethod(ctx, d.String(), opts.ToFlags(didnuts.DefaultKeyFlags())) if err != nil { return nil, err } + var vm *did.VerificationMethod + for _, m := range vms { + if m.ID.DID.String() == request.Did { + vm = &m + break + } + } + if vm == nil { + return nil, fmt.Errorf("verification method added for subject: %s but not for DID: %s, do not use the V1 API for non-nuts DIDs", request.Did, request.Did) + } + return AddNewVerificationMethod200JSONResponse(*vm), nil } @@ -120,12 +133,24 @@ func (a *Wrapper) CreateDID(ctx context.Context, request CreateDIDRequestObject) if keyFlags != defaultKeyFlags { options = options.With(keyFlags) } + options = options.With(didsubject.NutsLegacyNamingOption{}) - doc, _, err := a.VDR.NutsDocumentManager().Create(ctx, options) + docs, _, err := a.SubjectManager.Create(ctx, options) // if this operation leads to an error, it may return a 500 if err != nil { return nil, err } + var doc *did.Document + for _, m := range docs { + if m.ID.Method == "nuts" { + doc = &m + break + } + } + if doc == nil { + // only happens when did:nuts is disabled but V1 API is used. + return nil, errors.New("no nuts DID created, did you disable did:nuts support?") + } // this API returns a DIDDocument according to spec, so it may return the business object return CreateDID200JSONResponse(*doc), nil @@ -212,7 +237,7 @@ func (a *Wrapper) DeactivateDID(ctx context.Context, request DeactivateDIDReques if err != nil { return nil, err } - err = a.VDR.NutsDocumentManager().Deactivate(ctx, *id) + err = a.SubjectManager.Deactivate(ctx, id.String()) if err != nil { return nil, err } diff --git a/vdr/api/v1/api_test.go b/vdr/api/v1/api_test.go index 4e18bf29b5..a31b277b13 100644 --- a/vdr/api/v1/api_test.go +++ b/vdr/api/v1/api_test.go @@ -49,7 +49,7 @@ func TestWrapper_CreateDID(t *testing.T) { t.Run("ok - defaults", func(t *testing.T) { ctx := newMockContext(t) request := DIDCreateRequest{} - ctx.nutsDocumentManager.EXPECT().Create(gomock.Any(), didsubject.DefaultCreationOptions()).Return(didDoc, nil, nil) + ctx.subjectManager.EXPECT().Create(gomock.Any(), didsubject.DefaultCreationOptions().With(didsubject.NutsLegacyNamingOption{})).Return([]did.Document{*didDoc}, "subject", nil) response, err := ctx.client.CreateDID(nil, CreateDIDRequestObject{Body: &request}) @@ -72,7 +72,7 @@ func TestWrapper_CreateDID(t *testing.T) { SelfControl: new(bool), Controllers: &controllers, } - ctx.nutsDocumentManager.EXPECT().Create(gomock.Any(), gomock.Any()).Return(didDoc, nil, nil) + ctx.subjectManager.EXPECT().Create(gomock.Any(), gomock.Any()).Return([]did.Document{*didDoc}, "subject", nil) response, err := ctx.client.CreateDID(nil, CreateDIDRequestObject{Body: &request}) @@ -83,7 +83,7 @@ func TestWrapper_CreateDID(t *testing.T) { t.Run("error - create fails", func(t *testing.T) { ctx := newMockContext(t) request := DIDCreateRequest{} - ctx.nutsDocumentManager.EXPECT().Create(gomock.Any(), gomock.Any()).Return(nil, nil, errors.New("b00m!")) + ctx.subjectManager.EXPECT().Create(gomock.Any(), gomock.Any()).Return(nil, "", errors.New("b00m!")) response, err := ctx.client.CreateDID(nil, CreateDIDRequestObject{Body: &request}) @@ -307,7 +307,7 @@ func TestWrapper_DeactivateDID(t *testing.T) { did123, _ := did.ParseDID("did:nuts:123") t.Run("ok", func(t *testing.T) { ctx := newMockContext(t) - ctx.nutsDocumentManager.EXPECT().Deactivate(ctx.requestCtx, *did123).Return(nil) + ctx.subjectManager.EXPECT().Deactivate(ctx.requestCtx, did123.String()).Return(nil) _, err := ctx.client.DeactivateDID(ctx.requestCtx, DeactivateDIDRequestObject{Did: did123.String()}) @@ -326,7 +326,7 @@ func TestWrapper_DeactivateDID(t *testing.T) { t.Run("error - not found", func(t *testing.T) { ctx := newMockContext(t) - ctx.nutsDocumentManager.EXPECT().Deactivate(ctx.requestCtx, *did123).Return(resolver.ErrNotFound) + ctx.subjectManager.EXPECT().Deactivate(ctx.requestCtx, did123.String()).Return(resolver.ErrNotFound) _, err := ctx.client.DeactivateDID(ctx.requestCtx, DeactivateDIDRequestObject{Did: did123.String()}) @@ -336,7 +336,7 @@ func TestWrapper_DeactivateDID(t *testing.T) { t.Run("error - document already deactivated", func(t *testing.T) { ctx := newMockContext(t) - ctx.nutsDocumentManager.EXPECT().Deactivate(ctx.requestCtx, *did123).Return(resolver.ErrDeactivated) + ctx.subjectManager.EXPECT().Deactivate(ctx.requestCtx, did123.String()).Return(resolver.ErrDeactivated) _, err := ctx.client.DeactivateDID(ctx.requestCtx, DeactivateDIDRequestObject{Did: did123.String()}) @@ -346,7 +346,7 @@ func TestWrapper_DeactivateDID(t *testing.T) { t.Run("error - did not managed by this node", func(t *testing.T) { ctx := newMockContext(t) - ctx.nutsDocumentManager.EXPECT().Deactivate(ctx.requestCtx, *did123).Return(resolver.ErrDIDNotManagedByThisNode) + ctx.subjectManager.EXPECT().Deactivate(ctx.requestCtx, did123.String()).Return(resolver.ErrDIDNotManagedByThisNode) _, err := ctx.client.DeactivateDID(ctx.requestCtx, DeactivateDIDRequestObject{Did: did123.String()}) @@ -363,7 +363,7 @@ func TestWrapper_AddNewVerificationMethod(t *testing.T) { t.Run("ok - without key usage", func(t *testing.T) { ctx := newMockContext(t) - ctx.nutsDocumentManager.EXPECT().AddVerificationMethod(ctx.requestCtx, *did123, didnuts.DefaultKeyFlags()).Return(newMethod, nil) + ctx.subjectManager.EXPECT().AddVerificationMethod(ctx.requestCtx, did123.String(), didnuts.DefaultKeyFlags()).Return([]did.VerificationMethod{*newMethod}, nil) response, err := ctx.client.AddNewVerificationMethod(ctx.requestCtx, AddNewVerificationMethodRequestObject{Did: did123.String()}) @@ -374,7 +374,7 @@ func TestWrapper_AddNewVerificationMethod(t *testing.T) { t.Run("ok - with key usage", func(t *testing.T) { ctx := newMockContext(t) expectedKeyUsage := didnuts.DefaultKeyFlags() | orm.AuthenticationUsage | orm.CapabilityDelegationUsage - ctx.nutsDocumentManager.EXPECT().AddVerificationMethod(ctx.requestCtx, *did123, expectedKeyUsage).Return(newMethod, nil) + ctx.subjectManager.EXPECT().AddVerificationMethod(ctx.requestCtx, did123.String(), expectedKeyUsage).Return([]did.VerificationMethod{*newMethod}, nil) trueBool := true request := AddNewVerificationMethodJSONRequestBody{ Authentication: &trueBool, @@ -399,7 +399,7 @@ func TestWrapper_AddNewVerificationMethod(t *testing.T) { t.Run("error - internal error", func(t *testing.T) { ctx := newMockContext(t) - ctx.nutsDocumentManager.EXPECT().AddVerificationMethod(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("something went wrong")) + ctx.subjectManager.EXPECT().AddVerificationMethod(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("something went wrong")) response, err := ctx.client.AddNewVerificationMethod(ctx.requestCtx, AddNewVerificationMethodRequestObject{Did: did123.String()}) @@ -460,6 +460,7 @@ type mockContext struct { vdr *vdr.MockVDR didResolver *resolver.MockDIDResolver nutsDocumentManager *didsubject.MockDocumentManager + subjectManager *didsubject.MockSubjectManager client *Wrapper requestCtx context.Context } @@ -472,7 +473,8 @@ func newMockContext(t *testing.T) mockContext { vdr.EXPECT().Resolver().Return(didResolver).AnyTimes() nutsDocumentManager := didsubject.NewMockDocumentManager(ctrl) vdr.EXPECT().NutsDocumentManager().Return(nutsDocumentManager).AnyTimes() - client := &Wrapper{VDR: vdr} + subjectManager := didsubject.NewMockSubjectManager(ctrl) + client := &Wrapper{VDR: vdr, SubjectManager: subjectManager} requestCtx := audit.TestContext() return mockContext{ @@ -481,6 +483,7 @@ func newMockContext(t *testing.T) mockContext { didResolver: didResolver, client: client, nutsDocumentManager: nutsDocumentManager, + subjectManager: subjectManager, requestCtx: requestCtx, } } diff --git a/vdr/didnuts/ambassador_test.go b/vdr/didnuts/ambassador_test.go index a59dc81816..3e6d62996c 100644 --- a/vdr/didnuts/ambassador_test.go +++ b/vdr/didnuts/ambassador_test.go @@ -26,7 +26,6 @@ import ( "crypto/rand" "encoding/json" "errors" - "fmt" "github.com/nuts-foundation/nuts-node/audit" "github.com/nuts-foundation/nuts-node/crypto/dpop" "github.com/nuts-foundation/nuts-node/network" @@ -320,8 +319,7 @@ func TestAmbassador_handleCreateDIDDocument(t *testing.T) { } t.Run("create ok", func(t *testing.T) { - didDocument, signingKey, err := newDidDoc() - require.NoError(t, err) + didDocument, signingKey := newDidDoc(t) tx := newCreateTX(signingKey) @@ -329,7 +327,7 @@ func TestAmbassador_handleCreateDIDDocument(t *testing.T) { ctx := newMockContext(t) ctx.didStore.EXPECT().Add(didDocument, toStoreTX(tx)).Return(nil) - err = ctx.ambassador.handleCreateDIDDocument(tx, didDocument) + err := ctx.ambassador.handleCreateDIDDocument(tx, didDocument) assert.NoError(t, err) }) @@ -337,14 +335,13 @@ func TestAmbassador_handleCreateDIDDocument(t *testing.T) { t.Run("create failed", func(t *testing.T) { ctx := newMockContext(t) - didDocument, signingKey, err := newDidDoc() - require.NoError(t, err) + didDocument, signingKey := newDidDoc(t) tx := newCreateTX(signingKey) ctx.didStore.EXPECT().Add(didDocument, toStoreTX(tx)).Return(errors.New("b00m!")) - err = ctx.ambassador.handleCreateDIDDocument(tx, didDocument) + err := ctx.ambassador.handleCreateDIDDocument(tx, didDocument) assert.EqualError(t, err, "unable to register DID document: b00m!") }) @@ -358,7 +355,7 @@ func TestAmbassador_handleCreateDIDDocument(t *testing.T) { tx.signingKeyID = "" tx.signingKey = signingKey - doc, _, _ := newDidDoc() + doc, _ := newDidDoc(t) err := ctx.ambassador.handleCreateDIDDocument(tx, doc) @@ -386,8 +383,7 @@ func TestAmbassador_handleUpdateDIDDocument(t *testing.T) { t.Run("update ok - with a deactivated document", func(t *testing.T) { ctx := newMockContext(t) - didDocument, signingKey, err := newDidDoc() - require.NoError(t, err) + didDocument, signingKey := newDidDoc(t) didDocPayload, _ := json.Marshal(didDocument) payloadHash := hash.SHA256Sum(didDocPayload) @@ -419,7 +415,7 @@ func TestAmbassador_handleUpdateDIDDocument(t *testing.T) { ctx.keyResolver.EXPECT().ResolvePublicKey(storedDocument.CapabilityInvocation[0].ID.String(), gomock.Any()).Return(pKey, nil) ctx.didStore.EXPECT().Add(deactivatedDocument, toStoreTX(tx)) - err = ctx.ambassador.handleUpdateDIDDocument(tx, deactivatedDocument) + err := ctx.ambassador.handleUpdateDIDDocument(tx, deactivatedDocument) assert.NoError(t, err) }) @@ -428,7 +424,7 @@ func TestAmbassador_handleUpdateDIDDocument(t *testing.T) { t.Run("update ok - update with a new capabilityInvocation key", func(t *testing.T) { ctx := newMockContext(t) - currentDoc, signingKey, _ := newDidDoc() + currentDoc, signingKey := newDidDoc(t) newDoc := did.Document{Context: []interface{}{did.DIDContextV1URI()}, ID: currentDoc.ID} newCapInv, _ := CreateNewVerificationMethodForDID(audit.TestContext(), currentDoc.ID, &mockKeyStore{}) newDoc.AddCapabilityInvocation(newCapInv) @@ -463,7 +459,7 @@ func TestAmbassador_handleUpdateDIDDocument(t *testing.T) { t.Run("update ok - using 2nd prev for document resolution", func(t *testing.T) { ctx := newMockContext(t) - currentDoc, signingKey, _ := newDidDoc() + currentDoc, signingKey := newDidDoc(t) newDoc := did.Document{Context: []interface{}{did.DIDContextV1URI()}, ID: currentDoc.ID} newCapInv, _ := CreateNewVerificationMethodForDID(audit.TestContext(), currentDoc.ID, &mockKeyStore{}) newDoc.AddCapabilityInvocation(newCapInv) @@ -503,12 +499,10 @@ func TestAmbassador_handleUpdateDIDDocument(t *testing.T) { ctx := newMockContext(t) // Create a fresh DID Document - didDocument, _, err := newDidDoc() - require.NoError(t, err) + didDocument, _ := newDidDoc(t) // Create the DID docs controller - didDocumentController, controllerSigningKey, err := newDidDoc() - require.NoError(t, err) + didDocumentController, controllerSigningKey := newDidDoc(t) var pKey crypto2.PublicKey _ = controllerSigningKey.Raw(&pKey) @@ -549,7 +543,7 @@ func TestAmbassador_handleUpdateDIDDocument(t *testing.T) { ctx.keyResolver.EXPECT().ResolvePublicKey(didDocumentController.CapabilityInvocation[0].ID.String(), gomock.Any()).Return(pKey, nil) ctx.didStore.EXPECT().Add(expectedDocument, toStoreTX(tx)) - err = ctx.ambassador.handleUpdateDIDDocument(tx, expectedDocument) + err := ctx.ambassador.handleUpdateDIDDocument(tx, expectedDocument) assert.NoError(t, err) }) @@ -561,12 +555,10 @@ func TestAmbassador_handleUpdateDIDDocument(t *testing.T) { ctx := newMockContext(t) // Create a fresh DID Document - didDocument, documentSigningKey, err := newDidDoc() - require.NoError(t, err) + didDocument, documentSigningKey := newDidDoc(t) // Create the DID docs controller - didDocumentController, _, err := newDidDoc() - require.NoError(t, err) + didDocumentController, _ := newDidDoc(t) // We still use the document signing key, not the one from the controller var pKey crypto2.PublicKey @@ -609,7 +601,7 @@ func TestAmbassador_handleUpdateDIDDocument(t *testing.T) { ctx.didResolver.EXPECT().Resolve(didDocumentController.ID, gomock.Any()).Return(&didDocumentController, currentMetadata, nil) ctx.keyResolver.EXPECT().ResolvePublicKey(keyID, gomock.Any()).Return(pKey, nil) - err = ctx.ambassador.handleUpdateDIDDocument(tx, didDocument) + err := ctx.ambassador.handleUpdateDIDDocument(tx, didDocument) assert.EqualError(t, err, "network document not signed by one of its controllers") }) } @@ -628,8 +620,8 @@ func Test_handleUpdateDIDDocument(t *testing.T) { didResolver: didResolver, } - didDocument, _, _ := newDidDoc() - didDocumentController, _, _ := newDidDoc() + didDocument, _ := newDidDoc(t) + didDocumentController, _ := newDidDoc(t) didDocument.Controller = []did.DID{didDocumentController.ID} tx := testTransaction{signingTime: time.Now()} @@ -728,17 +720,19 @@ func Test_checkTransactionIntegrity(t *testing.T) { } } -func newDidDoc() (did.Document, jwk.Key, error) { +func newDidDoc(t *testing.T) (did.Document, jwk.Key) { kc := &mockKeyStore{} docCreator := Manager{keyStore: kc} - didDocument, key, err := docCreator.create(audit.TestContext(), DefaultKeyFlags()) - signingKey, _ := jwk.FromRaw(key.Public()) - thumbStr, _ := crypto.Thumbprint(signingKey) - didDocument.ID = did.MustParseDID(fmt.Sprintf("did:nuts:%s", thumbStr)) - if err != nil { - return did.Document{}, nil, err - } - serviceID := did.DIDURL{DID: didDocument.ID} + ormDocument, err := docCreator.NewDocument(audit.TestContext(), DefaultKeyFlags()) + require.NoError(t, err) + didDocument, err := ormDocument.ToDIDDocument() + require.NoError(t, err) + publicKey, err := didDocument.VerificationMethod[0].PublicKey() + require.NoError(t, err) + signingKey, _ := jwk.FromRaw(publicKey) + //thumbStr, _ := crypto.Thumbprint(signingKey) + //didDocument.ID = did.MustParseDID(fmt.Sprintf("did:nuts:%s", thumbStr)) + serviceID := did.MustParseDIDURL(didDocument.ID.String()) serviceID.Fragment = "1234" didDocument.Service = []did.Service{ { @@ -747,7 +741,7 @@ func newDidDoc() (did.Document, jwk.Key, error) { ServiceEndpoint: "https://nuts.nl", }, } - return *didDocument, signingKey, nil + return didDocument, signingKey } type mockContext struct { diff --git a/vdr/didnuts/manager.go b/vdr/didnuts/manager.go index 8ef874db11..0388b17f06 100644 --- a/vdr/didnuts/manager.go +++ b/vdr/didnuts/manager.go @@ -130,22 +130,6 @@ func getKIDName(pKey crypto.PublicKey, idFunc func(key jwk.Key) (string, error)) return kid.String(), nil } -// Create creates a Nuts DID Document with a valid DID id based on a freshly generated keypair. -// The key is added to the verificationMethod list and referred to from the Authentication list -// It also publishes the DID Document to the network. -func (m Manager) Create(ctx context.Context, _ didsubject.CreationOptions) (*did.Document, nutsCrypto.Key, error) { - doc, key, err := m.create(ctx, DefaultKeyFlags()) - if err != nil { - return nil, nil, err - } - if err := m.publish(ctx, *doc, key); err != nil { - return nil, nil, err - } - - // return the doc and the keyCreator that created the private key - return doc, key, nil -} - // Deactivate updates the DID Document so it can no longer be updated // It removes key material, services and controllers. func (m Manager) Deactivate(ctx context.Context, id did.DID) error { @@ -155,112 +139,6 @@ func (m Manager) Deactivate(ctx context.Context, id did.DID) error { return m.Update(ctx, id, emptyDoc) } -func (m Manager) create(ctx context.Context, flags orm.DIDKeyFlags) (*did.Document, nutsCrypto.Key, error) { - // First, generate a new keyPair with the correct kid - // Currently, always keep the key in the keystore. This allows us to change the transaction format and regenerate transactions at a later moment. - // Relevant issue: - // https://github.com/nuts-foundation/nuts-node/issues/1947 - key, err := m.keyStore.New(ctx, DIDKIDNamingFunc) - // } else { - // key, err = nutsCrypto.NewEphemeralKey(didKIDNamingFunc) - // } - if err != nil { - return nil, nil, err - } - - keyID, err := did.ParseDIDURL(key.KID()) - if err != nil { - return nil, nil, err - } - - // Create the bare document. The Document DID will be the keyIDStr without the fragment. - didID, _ := resolver.GetDIDFromURL(key.KID()) - doc := CreateDocument() - doc.ID = didID - - var verificationMethod *did.VerificationMethod - - // Add VerificationMethod using generated key - verificationMethod, err = did.NewVerificationMethod(*keyID, ssi.JsonWebKey2020, did.DID{}, key.Public()) - if err != nil { - return nil, nil, err - } - - applyKeyUsage(&doc, verificationMethod, flags) - return &doc, key, nil -} - -func (m Manager) publish(ctx context.Context, doc did.Document, key nutsCrypto.Key) error { - payload, err := json.Marshal(doc) - if err != nil { - return err - } - - // extract the transaction refs from the controller metadata - refs := make([]hash.SHA256Hash, 0) - - tx := network.TransactionTemplate(DIDDocumentType, payload, key).WithAttachKey().WithAdditionalPrevs(refs) - dagTx, err := m.networkClient.CreateTransaction(ctx, tx) - if err != nil { - return fmt.Errorf("could not store DID document in network: %w", err) - } - - // add it to the store after the transaction is successful - if err = m.store.Add(doc, didnutsStore.Transaction{ - Clock: dagTx.Clock(), - PayloadHash: dagTx.PayloadHash(), - Previous: dagTx.Previous(), - Ref: dagTx.Ref(), - SigningTime: dagTx.SigningTime(), - }); err != nil { - return fmt.Errorf("DID document created but could not add result to store: %w", err) - } - - return nil -} - -// applyKeyUsage checks intendedKeyUsage and adds the given verificationMethod to every relationship specified as key usage. -func applyKeyUsage(document *did.Document, keyToAdd *did.VerificationMethod, intendedKeyUsage orm.DIDKeyFlags) { - if intendedKeyUsage.Is(orm.CapabilityDelegationUsage) { - document.AddCapabilityDelegation(keyToAdd) - } - if intendedKeyUsage.Is(orm.CapabilityInvocationUsage) { - document.AddCapabilityInvocation(keyToAdd) - } - if intendedKeyUsage.Is(orm.AuthenticationUsage) { - document.AddAuthenticationMethod(keyToAdd) - } - if intendedKeyUsage.Is(orm.AssertionMethodUsage) { - document.AddAssertionMethod(keyToAdd) - } - if intendedKeyUsage.Is(orm.KeyAgreementUsage) { - document.AddKeyAgreement(keyToAdd) - } -} - -// AddVerificationMethod adds a new key as a VerificationMethod to the document. -// The key is added to the VerficationMethod relationships specified by keyUsage. -func (m Manager) AddVerificationMethod(ctx context.Context, id did.DID, keyUsage orm.DIDKeyFlags) (*did.VerificationMethod, error) { - doc, meta, err := m.resolver.Resolve(id, &resolver.ResolveMetadata{AllowDeactivated: true}) - if err != nil { - return nil, err - } - if meta.Deactivated { - return nil, resolver.ErrDeactivated - } - method, err := CreateNewVerificationMethodForDID(ctx, doc.ID, m.keyStore) - if err != nil { - return nil, err - } - method.Controller = doc.ID - doc.VerificationMethod.Add(method) - applyKeyUsage(doc, method, keyUsage) - if err = m.Update(ctx, id, *doc); err != nil { - return nil, err - } - return method, nil -} - // RemoveVerificationMethod is a helper function to remove a verificationMethod from a DID Document func (m Manager) RemoveVerificationMethod(ctx context.Context, id did.DID, keyID did.DIDURL) error { doc, _, err := m.resolver.Resolve(id, &resolver.ResolveMetadata{AllowDeactivated: true}) @@ -373,7 +251,7 @@ func (m Manager) Update(ctx context.Context, id did.DID, next did.Document) erro * New style DID Method Manager ******************************/ -func (m Manager) NewDocument(ctx context.Context, keyFlags orm.DIDKeyFlags) (*orm.DIDDocument, error) { +func (m Manager) NewDocument(ctx context.Context, _ orm.DIDKeyFlags) (*orm.DIDDocument, error) { // First, generate a new keyPair with the correct kid // Currently, always keep the key in the keystore. This allows us to change the transaction format and regenerate transactions at a later moment. // Relevant issue: @@ -383,6 +261,8 @@ func (m Manager) NewDocument(ctx context.Context, keyFlags orm.DIDKeyFlags) (*or return nil, err } + keyFlags := DefaultKeyFlags() + keyID, err := did.ParseDIDURL(key.KID()) if err != nil { return nil, err diff --git a/vdr/didnuts/manager_test.go b/vdr/didnuts/manager_test.go index e392b2e2bf..6f7f829143 100644 --- a/vdr/didnuts/manager_test.go +++ b/vdr/didnuts/manager_test.go @@ -27,7 +27,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/nuts-foundation/nuts-node/crypto/storage/spi" "strings" "testing" @@ -38,12 +37,12 @@ import ( "github.com/nuts-foundation/nuts-node/audit" nutsCrypto "github.com/nuts-foundation/nuts-node/crypto" "github.com/nuts-foundation/nuts-node/crypto/hash" + "github.com/nuts-foundation/nuts-node/crypto/storage/spi" "github.com/nuts-foundation/nuts-node/network" "github.com/nuts-foundation/nuts-node/network/dag" "github.com/nuts-foundation/nuts-node/storage" "github.com/nuts-foundation/nuts-node/storage/orm" "github.com/nuts-foundation/nuts-node/vdr/didnuts/didstore" - "github.com/nuts-foundation/nuts-node/vdr/didsubject" "github.com/nuts-foundation/nuts-node/vdr/resolver" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -92,31 +91,6 @@ func testDB(t *testing.T) *gorm.DB { return db } -func TestManager_Create(t *testing.T) { - ctx := newTestContext(t) - ctx.didStore.EXPECT().Add(gomock.Any(), gomock.Any()).Return(nil) - ctx.networkClient.EXPECT().CreateTransaction(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, template network.Template) (dag.Transaction, error) { - assert.Equal(t, DIDDocumentType, template.Type) - assert.True(t, template.AttachKey) - assert.Empty(t, template.AdditionalPrevs) - assert.Empty(t, template.Participants) - var didDocument did.Document - _ = json.Unmarshal(template.Payload, &didDocument) - assert.Len(t, didDocument.VerificationMethod, 1) - assert.Len(t, didDocument.CapabilityInvocation, 1) - assert.Len(t, didDocument.CapabilityDelegation, 1) - assert.Len(t, didDocument.AssertionMethod, 1) - assert.Len(t, didDocument.Authentication, 1) - assert.Len(t, didDocument.KeyAgreement, 1) - assert.Nil(t, didDocument.Service) - return testTransaction{}, nil - }) - - _, _, err := ctx.manager.Create(nil, didsubject.DefaultCreationOptions()) - - assert.NoError(t, err) -} - func TestManager_RemoveVerificationMethod(t *testing.T) { id123, _ := did.ParseDID("did:nuts:123") id123Method, _ := did.ParseDIDURL("did:nuts:123#method-1") @@ -191,59 +165,6 @@ func TestManager_CreateNewAuthenticationMethodForDID(t *testing.T) { }) } -func TestManipulator_AddKey(t *testing.T) { - id, _ := did.ParseDID("did:nuts:123") - keyID, _ := did.ParseDIDURL("did:nuts:123#key-1") - - t.Run("ok - add a new key", func(t *testing.T) { - ctx := newTestContext(t) - - currentDIDDocument := did.Document{ID: *id, Controller: []did.DID{*id}} - ctx.didResolver.EXPECT().Resolve(*id, &resolver.ResolveMetadata{AllowDeactivated: true}).Return(¤tDIDDocument, &resolver.DocumentMetadata{}, nil) - ctx.didStore.EXPECT().Resolve(*id, &resolver.ResolveMetadata{AllowDeactivated: true}).Return(¤tDIDDocument, &resolver.DocumentMetadata{}, nil) - ctx.didStore.EXPECT().Add(gomock.Any(), gomock.Any()).Return(nil) - ctx.didResolver.EXPECT().Resolve(*id, nil).Return(¤tDIDDocument, &resolver.DocumentMetadata{}, nil) - ctx.networkClient.EXPECT().CreateTransaction(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, template network.Template) (dag.Transaction, error) { - var didDocument did.Document - _ = json.Unmarshal(template.Payload, &didDocument) - assert.Len(t, didDocument.VerificationMethod, 1) - assert.Len(t, didDocument.CapabilityInvocation, 1) - return testTransaction{}, nil - }) - - key, err := ctx.manager.AddVerificationMethod(ctx.ctx, *id, orm.CapabilityInvocationUsage) - require.NoError(t, err) - assert.NotNil(t, key) - assert.Equal(t, key.Controller, *id, - "expected method to have DID as controller") - }) - - t.Run("error - didStore throws an error", func(t *testing.T) { - ctx := newTestContext(t) - - currentDIDDocument := did.Document{ID: *id, Controller: []did.DID{*id}} - currentDIDDocument.AddCapabilityInvocation(&did.VerificationMethod{ID: *keyID}) - ctx.didResolver.EXPECT().Resolve(*id, &resolver.ResolveMetadata{AllowDeactivated: true}).Return(¤tDIDDocument, &resolver.DocumentMetadata{}, nil) - ctx.didStore.EXPECT().Resolve(*id, &resolver.ResolveMetadata{AllowDeactivated: true}).Return(nil, nil, resolver.ErrNotFound) - - key, err := ctx.manager.AddVerificationMethod(ctx.ctx, *id, 0) - assert.ErrorIs(t, err, resolver.ErrNotFound) - assert.Nil(t, key) - }) - - t.Run("error - did is deactivated", func(t *testing.T) { - ctx := newTestContext(t) - - currentDIDDocument := did.Document{ID: *id, Controller: []did.DID{*id}} - ctx.didResolver.EXPECT().Resolve(*id, &resolver.ResolveMetadata{AllowDeactivated: true}).Return(¤tDIDDocument, &resolver.DocumentMetadata{Deactivated: true}, nil) - - key, err := ctx.manager.AddVerificationMethod(nil, *id, 0) - - assert.ErrorIs(t, err, resolver.ErrDeactivated) - assert.Nil(t, key) - }) -} - func TestManager_GenerateDocument(t *testing.T) { keyStore := nutsCrypto.NewMemoryCryptoInstance() ctx := audit.TestContext() @@ -288,55 +209,6 @@ func TestManager_GenerateDocument(t *testing.T) { var jwkString = `{"crv":"P-256","kid":"did:nuts:3gU9z3j7j4VCboc3qq3Vc5mVVGDNGjfg32xokeX8c8Zn#J9O6wvqtYOVwjc8JtZ4aodRdbPv_IKAjLkEq9uHlDdE","kty":"EC","x":"Qn6xbZtOYFoLO2qMEAczcau9uGGWwa1bT+7JmAVLtg4=","y":"d20dD0qlT+d1djVpAfrfsAfKOUxKwKkn1zqFSIuJ398="},"type":"JsonWebKey2020"}` -func TestManager_Create2(t *testing.T) { - defaultOptions := didsubject.DefaultCreationOptions() - - t.Run("ok", func(t *testing.T) { - t.Run("defaults", func(t *testing.T) { - ctx := newTestContext(t) - var txTemplate network.Template - ctx.didStore.EXPECT().Add(gomock.Any(), gomock.Any()).Return(nil) - ctx.networkClient.EXPECT().CreateTransaction(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, tx network.Template) (dag.Transaction, error) { - txTemplate = tx - return testTransaction{}, nil - }) - - doc, key, err := ctx.manager.Create(nil, defaultOptions) - assert.NoError(t, err, "create should not return an error") - assert.NotNil(t, doc, "create should return a document") - assert.NotNil(t, key, "create should return a Key") - assert.Equal(t, did.MustParseDIDURL(ctx.keyStore.key.KID()).DID, doc.ID, "the DID Doc should have the expected id") - assert.Len(t, doc.VerificationMethod, 1, "it should have one verificationMethod") - assert.Equal(t, ctx.keyStore.key.KID(), doc.VerificationMethod[0].ID.String(), - "verificationMethod should have the correct id") - assert.Len(t, doc.CapabilityInvocation, 1, "it should have 1 CapabilityInvocation") - assert.Equal(t, doc.CapabilityInvocation[0].VerificationMethod, doc.VerificationMethod[0], "the assertionMethod should be a pointer to the verificationMethod") - assert.Len(t, doc.AssertionMethod, 1, "it should have 1 AssertionMethod") - assert.Equal(t, DIDDocumentType, txTemplate.Type) - payload, _ := json.Marshal(doc) - assert.Equal(t, payload, txTemplate.Payload) - assert.Equal(t, key, txTemplate.Key) - assert.Empty(t, txTemplate.AdditionalPrevs) - assert.Len(t, doc.AssertionMethod, 1) - assert.Len(t, doc.Authentication, 1) - assert.Len(t, doc.CapabilityDelegation, 1) - assert.Len(t, doc.CapabilityInvocation, 1) - assert.Len(t, doc.KeyAgreement, 1) - }) - }) - - t.Run("error - failed to create key", func(t *testing.T) { - ctx := newTestContext(t) - mock := nutsCrypto.NewMockKeyStore(ctx.ctrl) - ctx.manager.keyStore = mock - mock.EXPECT().New(gomock.Any(), gomock.Any()).Return(nil, errors.New("b00m!")) - - _, _, err := ctx.manager.Create(nil, didsubject.DefaultCreationOptions()) - - assert.EqualError(t, err, "b00m!") - }) -} - func TestManager_Deactivate(t *testing.T) { id, _ := did.ParseDID("did:nuts:123") keyID, _ := did.ParseDIDURL("did:nuts:123#key-1") @@ -460,7 +332,7 @@ func TestManager_NewDocument(t *testing.T) { } func TestManager_Commit(t *testing.T) { - document, _, _ := newDidDoc() + document, _ := newDidDoc(t) data, _ := json.Marshal(document.VerificationMethod[0]) eventLog := orm.DIDChangeLog{ Type: orm.DIDChangeCreated, @@ -544,7 +416,7 @@ func TestManager_Commit(t *testing.T) { } func TestManager_IsCommitted(t *testing.T) { - document, _, _ := newDidDoc() + document, _ := newDidDoc(t) documentData, _ := json.Marshal(document) vmData, _ := json.Marshal(document.VerificationMethod[0]) eventLog := orm.DIDChangeLog{ diff --git a/vdr/didnuts/validators_test.go b/vdr/didnuts/validators_test.go index 867527d34a..600986613a 100644 --- a/vdr/didnuts/validators_test.go +++ b/vdr/didnuts/validators_test.go @@ -35,27 +35,27 @@ import ( func Test_verificationMethodValidator(t *testing.T) { table := []validatorTest{ {"ok - valid document", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) return didDoc }, nil}, {"nok - verificationMethod ID has no fragment", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) didDoc.VerificationMethod[0].ID.Fragment = "" return didDoc }, errors.New("invalid verificationMethod: ID must have a fragment")}, {"nok - verificationMethod ID has wrong prefix", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) didDoc.VerificationMethod[0].ID.ID = "foo:123" return didDoc }, errors.New("invalid verificationMethod: ID must have document prefix")}, {"nok - verificationMethod with duplicate id", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) method := didDoc.VerificationMethod[0] didDoc.VerificationMethod = append(didDoc.VerificationMethod, method) return didDoc }, errors.New("invalid verificationMethod: ID must be unique")}, {"nok - verificationMethod with invalid thumbprint", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) keyID := didDoc.VerificationMethod[0].ID keyID.Fragment = "foobar" pk, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) @@ -70,34 +70,34 @@ func Test_verificationMethodValidator(t *testing.T) { func Test_basicServiceValidator(t *testing.T) { table := []validatorTest{ {"ok - valid document", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) return didDoc }, nil}, {"ok - service endpoint is not validated", func() did.Document { // service endpoint is validated in managedServiceValidator - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) didDoc.Service[0].ServiceEndpoint = "did:foo:123/serviceEndpoint?type=NutsComm" return didDoc }, nil}, {"nok - service with duplicate id", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) service := didDoc.Service[0] didDoc.Service = append(didDoc.Service, service) return didDoc }, errors.New("invalid service: ID must be unique")}, {"nok - service ID has no fragment", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) didDoc.Service[0].ID.Fragment = "" return didDoc }, errors.New("invalid service: ID must have a fragment")}, {"nok - service ID has wrong prefix", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) uri := ssi.MustParseURI("did:foo:123#foobar") didDoc.Service[0].ID = uri return didDoc }, errors.New("invalid service: ID must have document prefix")}, {"nok - service with duplicate type", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) service := didDoc.Service[0] service.ID.Fragment = "foobar" didDoc.Service = append(didDoc.Service, service) @@ -110,7 +110,7 @@ func Test_basicServiceValidator(t *testing.T) { func Test_managedServiceValidator(t *testing.T) { // table driven testing errors for unexpected (number of) mock calls have poor localisation. // comment out validatorTests in the table to find the culprit. - referencedDocument, _, _ := newDidDoc() + referencedDocument, _ := newDidDoc(t) service := did.Service{Type: "referenced_service", ServiceEndpoint: "https://nuts.nl"} serviceRef := resolver.MakeServiceReference(referencedDocument.ID, service.Type) referencedDocument.Service = append(referencedDocument.Service, service) @@ -122,16 +122,16 @@ func Test_managedServiceValidator(t *testing.T) { t.Run("basic", func(t *testing.T) { table := []validatorTest{ {"ok - valid document", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) return didDoc }, nil}, {"ok - doesn't panic", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) didDoc.Service = nil return didDoc }, nil}, {"ok - resolves string", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) didDoc.Service[0].ServiceEndpoint = serviceRef.String() didResolver.EXPECT().Resolve(referencedDocument.ID, nil).Return(&referencedDocument, nil, nil) @@ -139,7 +139,7 @@ func Test_managedServiceValidator(t *testing.T) { return didDoc }, nil}, {"ok - normalizes and resolves map", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) didDoc.Service[0].ServiceEndpoint = map[string]interface{}{ "reference": serviceRef, // URI must be normalized "url": "super invalid but isn't validated", @@ -151,7 +151,7 @@ func Test_managedServiceValidator(t *testing.T) { return didDoc }, nil}, {"ok - self reference", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) didDoc.Service[0].ServiceEndpoint = serviceRef.String() didDoc.Service = append(didDoc.Service, did.Service{ ID: ssi.URI{}, @@ -164,7 +164,7 @@ func Test_managedServiceValidator(t *testing.T) { return didDoc }, nil}, {"nok - resolve fails", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) didDoc.Service[0].ServiceEndpoint = resolver.MakeServiceReference(referencedDocument.ID, "does_not_exist") didResolver.EXPECT().Resolve(referencedDocument.ID, nil).Return(&referencedDocument, nil, nil) @@ -172,12 +172,12 @@ func Test_managedServiceValidator(t *testing.T) { return didDoc }, errors.New("invalid service: service not found in DID Document")}, {"nok - invalid format", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) didDoc.Service[0].ServiceEndpoint = []string{serviceRef.String(), serviceRef.String()} return didDoc }, errors.New("invalid service: invalid service format")}, {"nok - invalid reference", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) didDoc.Service[0].ServiceEndpoint = "did:invalid:reference" return didDoc }, errors.New("invalid service: DID service query invalid: endpoint URI path must be /serviceEndpoint")}, @@ -188,18 +188,18 @@ func Test_managedServiceValidator(t *testing.T) { t.Run("NutsComm", func(t *testing.T) { table := []validatorTest{ {"ok", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) didDoc.Service[0].Type = "NutsComm" didDoc.Service[0].ServiceEndpoint = "grpc://nuts.nl:5555" return didDoc }, nil}, {"nok - invalid scheme", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) didDoc.Service[0].Type = "NutsComm" return didDoc }, errors.New("invalid service: NutsComm: scheme must be grpc")}, {"nok - validates after resolving", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) didDoc.Service = append(didDoc.Service, did.Service{ Type: "NutsComm", ServiceEndpoint: resolver.MakeServiceReference(didDoc.ID, didDoc.Service[0].Type), @@ -207,7 +207,7 @@ func Test_managedServiceValidator(t *testing.T) { return didDoc }, errors.New("invalid service: NutsComm: scheme must be grpc")}, {"nok - invalid format", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) didDoc.Service[0].Type = "NutsComm" didDoc.Service[0].ServiceEndpoint = map[string]string{"NutsComm": "grpc://nuts.nl:5555"} return didDoc @@ -219,7 +219,7 @@ func Test_managedServiceValidator(t *testing.T) { t.Run("node-contact-info", func(t *testing.T) { table := []validatorTest{ {"ok - node-contact-info all valid", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) didDoc.Service[0].Type = "node-contact-info" didDoc.Service[0].ServiceEndpoint = map[string]string{ "name": "name", @@ -230,7 +230,7 @@ func Test_managedServiceValidator(t *testing.T) { return didDoc }, nil}, {"ok - minimal info", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) didDoc.Service[0].Type = "node-contact-info" didDoc.Service[0].ServiceEndpoint = map[string]string{ "email": "valid@email.address", @@ -238,7 +238,7 @@ func Test_managedServiceValidator(t *testing.T) { return didDoc }, nil}, {"ok - validates after resolving", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) didDoc.Service[0] = did.Service{ Type: "node-contact-info", ServiceEndpoint: resolver.MakeServiceReference(didDoc.ID, "otherService"), @@ -249,13 +249,13 @@ func Test_managedServiceValidator(t *testing.T) { return didDoc }, nil}, {"nok - missing email", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) didDoc.Service[0].Type = "node-contact-info" didDoc.Service[0].ServiceEndpoint = map[string]string{} return didDoc }, errors.New("invalid service: node-contact-info: missing email")}, {"nok - not a map", func() did.Document { - didDoc, _, _ := newDidDoc() + didDoc, _ := newDidDoc(t) didDoc.Service[0].Type = "node-contact-info" didDoc.Service[0].ServiceEndpoint = "valid@email.address" return didDoc diff --git a/vdr/didsubject/management.go b/vdr/didsubject/management.go index 1e712f4eb6..2602a1411f 100644 --- a/vdr/didsubject/management.go +++ b/vdr/didsubject/management.go @@ -23,7 +23,6 @@ import ( "errors" ssi "github.com/nuts-foundation/go-did" "github.com/nuts-foundation/go-did/did" - "github.com/nuts-foundation/nuts-node/crypto" "github.com/nuts-foundation/nuts-node/storage/orm" ) @@ -55,29 +54,17 @@ type MethodManager interface { } // DocumentManager is the interface that groups several higher level methods to create and update DID documents. +// Deprecated // Only used for V1 API calls. type DocumentManager interface { - // Create creates a new DID document and returns it. - // The ID in the provided DID document will be ignored and a new one will be generated. - // If something goes wrong an error is returned. - // Implementors should generate private key and store it in a secure backend - Create(ctx context.Context, options CreationOptions) (*did.Document, crypto.Key, error) - // Update replaces the DID document identified by DID with the nextVersion // Deprecated: only did:nuts implements it, new methods should higher level functions, then this method can become private // If the DID Document is not found, ErrNotFound is returned // If the DID Document is not managed by this node, ErrDIDNotManagedByThisNode is returned Update(ctx context.Context, id did.DID, next did.Document) error - // Deactivate deactivates a DID document - // Deactivation will be done in such a way that a DID doc cannot be used / activated anymore. - // Since the deactivation is definitive, no version is required - // If the DID Document is not found ErrNotFound is returned - // If the DID Document is not managed by this node, ErrDIDNotManagedByThisNode is returned - // If the DID Document is already deactivated ErrDeactivated is returned - Deactivate(ctx context.Context, id did.DID) error - // RemoveVerificationMethod removes a VerificationMethod from a DID document. + // Deprecated: only relevant for v1 API calls. // It accepts the id DID as identifier for the DID document. // It accepts the kid DID as identifier for the VerificationMethod. // It returns an ErrNotFound when the DID document could not be found. @@ -85,13 +72,6 @@ type DocumentManager interface { // It returns an ErrDeactivated when the DID document has the deactivated state. // It returns an ErrDIDNotManagedByThisNode if the DID document is not managed by this node. RemoveVerificationMethod(ctx context.Context, id did.DID, keyID did.DIDURL) error - - // AddVerificationMethod generates a new key and adds it, wrapped as a VerificationMethod, to a DID document. - // It accepts a DID as identifier for the DID document. - // It returns an ErrNotFound when the DID document could not be found. - // It returns an ErrDeactivated when the DID document has the deactivated state. - // It returns an ErrDIDNotManagedByThisNode if the DID document is not managed by this node. - AddVerificationMethod(ctx context.Context, id did.DID, keyUsage orm.DIDKeyFlags) (*did.VerificationMethod, error) } // SubjectManager abstracts DID Document management away from the API caller. @@ -143,6 +123,9 @@ type EncryptionKeyCreationOption struct{} // SkipAssertionKeyCreationOption signals that no assertion key should be created. type SkipAssertionKeyCreationOption struct{} +// NutsLegacyNamingOption will make the subject equal to the Nuts DID. +type NutsLegacyNamingOption struct{} + // CreationOptions defines options for creating DID Documents. type CreationOptions interface { // With adds an option to the CreationOptions. diff --git a/vdr/didsubject/management_mock.go b/vdr/didsubject/management_mock.go index cf3590d365..7d15335240 100644 --- a/vdr/didsubject/management_mock.go +++ b/vdr/didsubject/management_mock.go @@ -15,7 +15,6 @@ import ( ssi "github.com/nuts-foundation/go-did" did "github.com/nuts-foundation/go-did/did" - crypto "github.com/nuts-foundation/nuts-node/crypto" orm "github.com/nuts-foundation/nuts-node/storage/orm" gomock "go.uber.org/mock/gomock" ) @@ -125,51 +124,6 @@ func (m *MockDocumentManager) EXPECT() *MockDocumentManagerMockRecorder { return m.recorder } -// AddVerificationMethod mocks base method. -func (m *MockDocumentManager) AddVerificationMethod(ctx context.Context, id did.DID, keyUsage orm.DIDKeyFlags) (*did.VerificationMethod, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AddVerificationMethod", ctx, id, keyUsage) - ret0, _ := ret[0].(*did.VerificationMethod) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// AddVerificationMethod indicates an expected call of AddVerificationMethod. -func (mr *MockDocumentManagerMockRecorder) AddVerificationMethod(ctx, id, keyUsage any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddVerificationMethod", reflect.TypeOf((*MockDocumentManager)(nil).AddVerificationMethod), ctx, id, keyUsage) -} - -// Create mocks base method. -func (m *MockDocumentManager) Create(ctx context.Context, options CreationOptions) (*did.Document, crypto.Key, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Create", ctx, options) - ret0, _ := ret[0].(*did.Document) - ret1, _ := ret[1].(crypto.Key) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// Create indicates an expected call of Create. -func (mr *MockDocumentManagerMockRecorder) Create(ctx, options any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockDocumentManager)(nil).Create), ctx, options) -} - -// Deactivate mocks base method. -func (m *MockDocumentManager) Deactivate(ctx context.Context, id did.DID) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Deactivate", ctx, id) - ret0, _ := ret[0].(error) - return ret0 -} - -// Deactivate indicates an expected call of Deactivate. -func (mr *MockDocumentManagerMockRecorder) Deactivate(ctx, id any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deactivate", reflect.TypeOf((*MockDocumentManager)(nil).Deactivate), ctx, id) -} - // RemoveVerificationMethod mocks base method. func (m *MockDocumentManager) RemoveVerificationMethod(ctx context.Context, id did.DID, keyID did.DIDURL) error { m.ctrl.T.Helper() diff --git a/vdr/didsubject/manager.go b/vdr/didsubject/manager.go index b67ebc7271..f776ffc23d 100644 --- a/vdr/didsubject/manager.go +++ b/vdr/didsubject/manager.go @@ -64,6 +64,7 @@ func (r *Manager) Create(ctx context.Context, options CreationOptions) ([]did.Do // defaults keyFlags := orm.AssertionKeyUsage() subject := uuid.New().String() + nutsLegacy := false // apply options for _, option := range options.All() { @@ -72,6 +73,8 @@ func (r *Manager) Create(ctx context.Context, options CreationOptions) ([]did.Do subject = opt.Subject case EncryptionKeyCreationOption: keyFlags = keyFlags | orm.EncryptionKeyUsage() + case NutsLegacyNamingOption: + nutsLegacy = true default: return nil, "", fmt.Errorf("unknown option: %T", option) } @@ -95,6 +98,9 @@ func (r *Manager) Create(ctx context.Context, options CreationOptions) ([]did.Do if err != nil { return nil, fmt.Errorf("could not generate DID document (method %s): %w", method, err) } + if nutsLegacy && method == "nuts" { + subject = sqlDoc.DID.ID + } sqlDocs[method] = *sqlDoc } @@ -369,9 +375,11 @@ func (r *Manager) transactionHelper(ctx context.Context, operation func(tx *gorm // Call commit for all managers on the created docs var errManager error for method, manager := range r.MethodManagers { - errManager = manager.Commit(ctx, changes[method]) - if errManager != nil { - break + if change, ok := changes[method]; ok { + errManager = manager.Commit(ctx, change) + if errManager != nil { + break + } } } diff --git a/vdr/interface.go b/vdr/interface.go index 3d8c288f1d..78d275e266 100644 --- a/vdr/interface.go +++ b/vdr/interface.go @@ -28,6 +28,7 @@ import ( // VDR defines the public end facing methods for the Verifiable Data Registry. type VDR interface { // NutsDocumentManager returns the nuts document manager. + // Deprecated NutsDocumentManager() didsubject.DocumentManager // DocumentOwner returns the document owner. DocumentOwner() didsubject.DocumentOwner diff --git a/vdr/legacy_integration_test.go b/vdr/legacy_integration_test.go index 03276f0bc0..1613feec54 100644 --- a/vdr/legacy_integration_test.go +++ b/vdr/legacy_integration_test.go @@ -20,6 +20,7 @@ package vdr import ( "context" + crypto2 "crypto" "encoding/json" "fmt" "github.com/nuts-foundation/nuts-node/audit" @@ -54,8 +55,9 @@ func TestVDRIntegration_Test(t *testing.T) { ctx := setup(t) // Start with a first and fresh document named DocumentA. - docA, _, err := ctx.vdr.nutsDocumentManager.Create(ctx.audit, didsubject.DefaultCreationOptions()) + docs, _, err := ctx.vdr.Create(ctx.audit, didsubject.DefaultCreationOptions().With(didsubject.NutsLegacyNamingOption{})) require.NoError(t, err) + docA := &docs[0] docAID := docA.ID @@ -88,8 +90,9 @@ func TestVDRIntegration_Test(t *testing.T) { "expected updated docA to have a service") // Create a new DID Document we name DocumentB - docB, _, err := ctx.vdr.nutsDocumentManager.Create(ctx.audit, didsubject.DefaultCreationOptions()) + docs, _, err = ctx.vdr.Create(ctx.audit, didsubject.DefaultCreationOptions().With(didsubject.NutsLegacyNamingOption{})) require.NoError(t, err, "unexpected error while creating DocumentB") + docB := &docs[0] _, _, err = ctx.didStore.Resolve(docB.ID, nil) assert.NoError(t, err, "unexpected error while resolving documentB") @@ -113,7 +116,7 @@ func TestVDRIntegration_Test(t *testing.T) { "news service of document a does not contain expected values") // deactivate document B - err = ctx.vdr.nutsDocumentManager.Deactivate(ctx.audit, docB.ID) + err = ctx.vdr.Deactivate(ctx.audit, docB.ID.String()) require.NoError(t, err, "expected deactivation to succeed") @@ -123,7 +126,7 @@ func TestVDRIntegration_Test(t *testing.T) { "expected document B to not have any CapabilityInvocation methods after deactivation") // try to deactivate the document again - err = ctx.vdr.nutsDocumentManager.Deactivate(ctx.audit, docB.ID) + err = ctx.vdr.Deactivate(ctx.audit, docB.ID.String()) assert.ErrorIs(t, err, resolver.ErrDeactivated, "expected an error when trying to deactivate an already deactivated document") } @@ -133,12 +136,14 @@ func TestVDRMigration_Test(t *testing.T) { ctx := setup(t) // Start with a first and fresh document named DocumentA. - docA, _, err := ctx.vdr.nutsDocumentManager.Create(ctx.audit, didsubject.DefaultCreationOptions()) + docs, _, err := ctx.vdr.Create(ctx.audit, didsubject.DefaultCreationOptions()) require.NoError(t, err) + docA := &docs[0] // Create a new DID Document we name DocumentB - docB, _, err := ctx.vdr.nutsDocumentManager.Create(ctx.audit, didsubject.DefaultCreationOptions()) + docs, _, err = ctx.vdr.Create(ctx.audit, didsubject.DefaultCreationOptions()) require.NoError(t, err) + docB := &docs[0] // Update the controller of DocumentA with DocumentB docA.Controller = []did.DID{docB.ID} @@ -166,8 +171,9 @@ func TestVDRIntegration_ConcurrencyTest(t *testing.T) { ctx := setup(t) // Start with a first and fresh document named DocumentA. - initialDoc, _, err := ctx.vdr.nutsDocumentManager.Create(ctx.audit, didsubject.DefaultCreationOptions()) + docs, _, err := ctx.vdr.Create(ctx.audit, didsubject.DefaultCreationOptions().With(didsubject.NutsLegacyNamingOption{})) require.NoError(t, err) + initialDoc := &docs[0] // Check if the document can be found in the store _, _, err = ctx.didStore.Resolve(initialDoc.ID, nil) @@ -208,10 +214,13 @@ func TestVDRIntegration_ReprocessEvents(t *testing.T) { ctx := setup(t) // Publish a DID Document - didDoc, key, _ := ctx.vdr.NutsDocumentManager().Create(audit.TestContext(), didsubject.DefaultCreationOptions()) + docs, _, _ := ctx.vdr.Create(audit.TestContext(), didsubject.DefaultCreationOptions().With(didsubject.NutsLegacyNamingOption{})) + didDoc := &docs[0] + kid := didDoc.VerificationMethod[0].ID.String() + publicKey, _ := didDoc.VerificationMethod[0].PublicKey() payload, _ := json.Marshal(didDoc) unsignedTransaction, _ := dag.NewTransaction(hash.SHA256Sum(payload), didnuts.DIDDocumentType, nil, nil, uint32(0)) - signedTransaction, err := dag.NewTransactionSigner(ctx.cryptoInstance, key, true).Sign(audit.TestContext(), unsignedTransaction, time.Now()) + signedTransaction, err := dag.NewTransactionSigner(ctx.cryptoInstance, testKey{kid: kid, publicKey: publicKey}, true).Sign(audit.TestContext(), unsignedTransaction, time.Now()) require.NoError(t, err) twp := events.TransactionWithPayload{ Transaction: signedTransaction, @@ -244,7 +253,7 @@ func setup(t *testing.T) testContext { testDir := io.TestDirectory(t) nutsConfig := core.TestServerConfig(func(config *core.ServerConfig) { config.Strictmode = false - config.Verbosity = "debug" + config.Verbosity = "trace" config.Datadir = testDir }) @@ -288,7 +297,7 @@ func setup(t *testing.T) testContext { pkiValidator, ) vdr := NewVDR(cryptoInstance, nutsNetwork, didStore, eventPublisher, storageEngine) - vdr.Config().(*Config).DIDMethods = []string{"web", "nuts"} + vdr.Config().(*Config).DIDMethods = []string{"nuts"} // Configure require.NoError(t, vdr.Configure(nutsConfig)) @@ -313,3 +322,17 @@ func setup(t *testing.T) testContext { storageInstance: storageEngine, } } + +// testKey is temporary and will be removed by a future PR +type testKey struct { + kid string + publicKey crypto2.PublicKey +} + +func (t testKey) KID() string { + return t.kid +} + +func (t testKey) Public() crypto2.PublicKey { + return t.publicKey +} diff --git a/vdr/vdr.go b/vdr/vdr.go index ade588f673..b0de1b439c 100644 --- a/vdr/vdr.go +++ b/vdr/vdr.go @@ -272,6 +272,8 @@ func (r *Module) ConflictedDocuments() ([]did.Document, []resolver.DocumentMetad return conflictedDocs, conflictedMeta, err } +// NutsDocumentManager returns the nuts document manager +// Deprecated func (r *Module) NutsDocumentManager() didsubject.DocumentManager { return r.nutsDocumentManager }