From 9add14dcd58e832070d28ba0c5e1ae44a4c7b679 Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Mon, 24 Jun 2024 11:54:15 +0200 Subject: [PATCH 1/4] added keyUsage flags to sql DB --- storage/sql_migrations/003_did.sql | 7 +++ vdr/didweb/manager.go | 1 + vdr/sql/did_document_test.go | 8 +++- vdr/sql/verification_method.go | 57 ++++++++++++++++++++++- vdr/sql/verification_method_test.go | 72 +++++++++++++++++++++++++++++ 5 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 vdr/sql/verification_method_test.go diff --git a/storage/sql_migrations/003_did.sql b/storage/sql_migrations/003_did.sql index 330b0c58c5..acc7c663a9 100644 --- a/storage/sql_migrations/003_did.sql +++ b/storage/sql_migrations/003_did.sql @@ -28,6 +28,13 @@ create table did_verificationmethod id varchar(254) not null primary key, -- did_document_id references the DID document version did_document_id varchar(36) not null, + -- key_types is a base64 encoded bitmask of the key types supported by the verification method. + -- 0x01 - AssertionMethod + -- 0x02 - Authentication + -- 0x04 - CapabilityDelegation + -- 0x08 - CapabilityInvocation + -- 0x10 - KeyAgreement + key_types varchar(2) not null, -- data is a JSON object containing the verification method data, e.g. the public key. -- When producing the verificationMethod, data is used as JSON base object and the id and type are added. data $TEXT_TYPE not null, diff --git a/vdr/didweb/manager.go b/vdr/didweb/manager.go index ced0440822..9b9fdf1c66 100644 --- a/vdr/didweb/manager.go +++ b/vdr/didweb/manager.go @@ -153,6 +153,7 @@ func (m Manager) Create(ctx context.Context, opts management.CreationOptions) (* if doc, err = documentStore.CreateOrUpdate(sqlDid, []sql.SqlVerificationMethod{{ ID: verificationMethod.ID.String(), DIDDocumentID: sqlDid.ID, + KeyTypes: sql.VerificationMethodKeyType(management.AssertionMethodUsage | management.AuthenticationUsage | management.CapabilityDelegationUsage | management.CapabilityInvocationUsage), // todo pass via options Data: vmAsJson, }}, nil); err != nil { return fmt.Errorf("store new DID document: %w", err) diff --git a/vdr/sql/did_document_test.go b/vdr/sql/did_document_test.go index c4359df209..4123656d0e 100644 --- a/vdr/sql/did_document_test.go +++ b/vdr/sql/did_document_test.go @@ -19,6 +19,7 @@ package sql import ( + "github.com/nuts-foundation/nuts-node/vdr/management" "testing" "github.com/nuts-foundation/go-did/did" @@ -30,9 +31,11 @@ import ( var sqlDidAlice = DID{ID: alice.String(), Subject: "alice"} func TestSqlDIDDocumentManager_CreateOrUpdate(t *testing.T) { + keyUsageFlag := VerificationMethodKeyType(management.AssertionMethodUsage | management.AuthenticationUsage | management.CapabilityDelegationUsage | management.CapabilityInvocationUsage) vm := SqlVerificationMethod{ - ID: "#1", - Data: []byte("{}"), + ID: "#1", + Data: []byte("{}"), + KeyTypes: keyUsageFlag, } service := SqlService{ ID: "#2", @@ -66,6 +69,7 @@ func TestSqlDIDDocumentManager_CreateOrUpdate(t *testing.T) { require.Len(t, doc.Services, 1) assert.Len(t, doc.ID, 36) // uuid v4 assert.Equal(t, []byte("{}"), doc.VerificationMethods[0].Data) + assert.Equal(t, keyUsageFlag, doc.VerificationMethods[0].KeyTypes) assert.Equal(t, []byte("{}"), doc.Services[0].Data) }) t.Run("update", func(t *testing.T) { diff --git a/vdr/sql/verification_method.go b/vdr/sql/verification_method.go index 6032116b88..605fcc55a7 100644 --- a/vdr/sql/verification_method.go +++ b/vdr/sql/verification_method.go @@ -18,7 +18,12 @@ package sql -import "gorm.io/gorm/schema" +import ( + "database/sql/driver" + "encoding/base64" + "errors" + "gorm.io/gorm/schema" +) var _ schema.Tabler = (*SqlVerificationMethod)(nil) @@ -26,9 +31,59 @@ var _ schema.Tabler = (*SqlVerificationMethod)(nil) type SqlVerificationMethod struct { ID string `gorm:"primaryKey"` DIDDocumentID string `gorm:"column:did_document_id"` + KeyTypes VerificationMethodKeyType Data []byte } func (v SqlVerificationMethod) TableName() string { return "did_verificationmethod" } + +// VerificationMethodKeyType is used to marshal and unmarshal the key type to the DB +// The string representation in the DB is the base64 encoded bit mask +type VerificationMethodKeyType uint8 + +// Scan decodes string value to byte slice +func (kt *VerificationMethodKeyType) Scan(value interface{}) error { + var err error + if value == nil { + return nil + } + switch v := value.(type) { + case string: + *kt, err = stringToUint(v) + default: + err = errors.New("not supported") + } + return err +} + +// Value returns base64 encoded value +func (kt VerificationMethodKeyType) Value() (driver.Value, error) { + return uintToString(kt) +} + +// stringToUint decodes a base64 encoded string to a uint +func stringToUint(s string) (VerificationMethodKeyType, error) { + if s == "" { + return 0, nil + } + bytes, err := base64.RawStdEncoding.DecodeString(s) + if err != nil { + return 0, err + } + if len(bytes) > 1 { + return 0, errors.New("keyTypes is too long") + } + return VerificationMethodKeyType(bytes[0]), nil +} + +// uintToString encodes a uint to a base64 encoded string +func uintToString(u VerificationMethodKeyType) (string, error) { + if u == 0 { + return "", nil + } + // convert uint to bytes array + bytes := [1]byte{byte(u)} + return base64.RawStdEncoding.EncodeToString(bytes[:]), nil +} diff --git a/vdr/sql/verification_method_test.go b/vdr/sql/verification_method_test.go new file mode 100644 index 0000000000..88095da4dc --- /dev/null +++ b/vdr/sql/verification_method_test.go @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2024 Nuts community + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package sql + +import ( + "testing" + + "github.com/nuts-foundation/nuts-node/vdr/management" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestVerificationMethodKeyType(t *testing.T) { + tests := []struct { + name string + keyType VerificationMethodKeyType + encoded string + }{ + { + "all", + VerificationMethodKeyType(management.AssertionMethodUsage | management.AuthenticationUsage | management.KeyAgreementUsage | management.CapabilityDelegationUsage | management.CapabilityInvocationUsage), + "Hw", + }, + { + "half", + VerificationMethodKeyType(management.AssertionMethodUsage | management.AuthenticationUsage), + "Aw", + }, + { + "none", + VerificationMethodKeyType(0), + "", + }, + } + + t.Run("encode", func(t *testing.T) { + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + encoded, err := uintToString(test.keyType) + + require.NoError(t, err) + assert.Equal(t, test.encoded, encoded) + }) + } + }) + t.Run("decode", func(t *testing.T) { + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + decoded, err := stringToUint(test.encoded) + + require.NoError(t, err) + assert.Equal(t, test.keyType, decoded) + }) + } + }) +} From 0e82d88d7c9a2649bc960cb750cab9cc4c956afa Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Mon, 24 Jun 2024 12:00:32 +0200 Subject: [PATCH 2/4] update buildDocument based on keyflags from DB --- vdr/didweb/manager.go | 20 +++++++++++++++----- vdr/didweb/manager_test.go | 2 +- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/vdr/didweb/manager.go b/vdr/didweb/manager.go index 9b9fdf1c66..9b8fc3c05a 100644 --- a/vdr/didweb/manager.go +++ b/vdr/didweb/manager.go @@ -332,11 +332,21 @@ func buildDocument(newDID did.DID, doc sql.DIDDocument) (did.Document, error) { return document, err } - document.AddAssertionMethod(&verificationMethod) - document.AddAuthenticationMethod(&verificationMethod) - document.AddKeyAgreement(&verificationMethod) - document.AddCapabilityDelegation(&verificationMethod) - document.AddCapabilityInvocation(&verificationMethod) + if sqlVM.KeyTypes&sql.VerificationMethodKeyType(management.AssertionMethodUsage) != 0 { + document.AddAssertionMethod(&verificationMethod) + } + if sqlVM.KeyTypes&sql.VerificationMethodKeyType(management.AuthenticationUsage) != 0 { + document.AddAuthenticationMethod(&verificationMethod) + } + if sqlVM.KeyTypes&sql.VerificationMethodKeyType(management.KeyAgreementUsage) != 0 { + document.AddKeyAgreement(&verificationMethod) + } + if sqlVM.KeyTypes&sql.VerificationMethodKeyType(management.CapabilityDelegationUsage) != 0 { + document.AddCapabilityDelegation(&verificationMethod) + } + if sqlVM.KeyTypes&sql.VerificationMethodKeyType(management.CapabilityInvocationUsage) != 0 { + document.AddCapabilityInvocation(&verificationMethod) + } } for _, sqlService := range doc.Services { service := did.Service{} diff --git a/vdr/didweb/manager_test.go b/vdr/didweb/manager_test.go index 6d1beea779..f4827a7d8f 100644 --- a/vdr/didweb/manager_test.go +++ b/vdr/didweb/manager_test.go @@ -73,7 +73,7 @@ func TestManager_Create(t *testing.T) { assert.Len(t, document.Authentication, 1) assert.Len(t, document.CapabilityInvocation, 1) assert.Len(t, document.AssertionMethod, 1) - assert.Len(t, document.KeyAgreement, 1) + assert.Len(t, document.KeyAgreement, 0) }) t.Run("with root DID option", func(t *testing.T) { db := testDB(t) From 7f80959f3270bb0b4ec0978db122db248a04ea91 Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Wed, 26 Jun 2024 11:51:55 +0200 Subject: [PATCH 3/4] additional error output for sql.VerificationMethod.Scan --- vdr/sql/did_document_test.go | 10 +++++++++- vdr/sql/verification_method.go | 8 ++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/vdr/sql/did_document_test.go b/vdr/sql/did_document_test.go index 4123656d0e..57073fa6ab 100644 --- a/vdr/sql/did_document_test.go +++ b/vdr/sql/did_document_test.go @@ -94,7 +94,13 @@ func TestSqlDIDDocumentManager_Latest(t *testing.T) { db := testDB(t) tx := transaction(t, db) docManager := NewDIDDocumentManager(tx) - doc, err := docManager.CreateOrUpdate(sqlDidAlice, nil, nil) + keyUsageFlag := VerificationMethodKeyType(management.AssertionMethodUsage | management.AuthenticationUsage | management.CapabilityDelegationUsage | management.CapabilityInvocationUsage) + vm := SqlVerificationMethod{ + ID: "#1", + Data: []byte("{}"), + KeyTypes: keyUsageFlag, + } + doc, err := docManager.CreateOrUpdate(sqlDidAlice, []SqlVerificationMethod{vm}, nil) require.NoError(t, err) t.Run("found", func(t *testing.T) { @@ -102,6 +108,8 @@ func TestSqlDIDDocumentManager_Latest(t *testing.T) { require.NoError(t, err) assert.Equal(t, doc.ID, latest.ID) + require.Len(t, latest.VerificationMethods, 1) + assert.Equal(t, keyUsageFlag, doc.VerificationMethods[0].KeyTypes) }) t.Run("not found", func(t *testing.T) { diff --git a/vdr/sql/verification_method.go b/vdr/sql/verification_method.go index 605fcc55a7..576e1d028b 100644 --- a/vdr/sql/verification_method.go +++ b/vdr/sql/verification_method.go @@ -19,9 +19,11 @@ package sql import ( + "errors" + "fmt" + "database/sql/driver" "encoding/base64" - "errors" "gorm.io/gorm/schema" ) @@ -52,8 +54,10 @@ func (kt *VerificationMethodKeyType) Scan(value interface{}) error { switch v := value.(type) { case string: *kt, err = stringToUint(v) + case []uint8: + *kt, err = stringToUint(string(v)) default: - err = errors.New("not supported") + err = fmt.Errorf("db type not supported: %T", v) } return err } From 3c30678aa67097adba05a2ddfb8357d8e0abb859 Mon Sep 17 00:00:00 2001 From: Wout Slakhorst Date: Wed, 26 Jun 2024 12:12:18 +0200 Subject: [PATCH 4/4] comment --- vdr/sql/verification_method.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vdr/sql/verification_method.go b/vdr/sql/verification_method.go index 576e1d028b..4071d02aa8 100644 --- a/vdr/sql/verification_method.go +++ b/vdr/sql/verification_method.go @@ -54,7 +54,7 @@ func (kt *VerificationMethodKeyType) Scan(value interface{}) error { switch v := value.(type) { case string: *kt, err = stringToUint(v) - case []uint8: + case []uint8: // mysql driver returns []uint8 for string *kt, err = stringToUint(string(v)) default: err = fmt.Errorf("db type not supported: %T", v)