Skip to content

Commit

Permalink
Extend DID verification method model with key usage (#3209)
Browse files Browse the repository at this point in the history
  • Loading branch information
woutslakhorst authored Jun 26, 2024
1 parent 53912e6 commit 2dca044
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 10 deletions.
7 changes: 7 additions & 0 deletions storage/sql_migrations/003_did.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
21 changes: 16 additions & 5 deletions vdr/didweb/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -331,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{}
Expand Down
2 changes: 1 addition & 1 deletion vdr/didweb/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
18 changes: 15 additions & 3 deletions vdr/sql/did_document_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package sql

import (
"github.com/nuts-foundation/nuts-node/vdr/management"
"testing"

"github.com/nuts-foundation/go-did/did"
Expand All @@ -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",
Expand Down Expand Up @@ -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) {
Expand All @@ -90,14 +94,22 @@ 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) {
latest, err := docManager.Latest(alice)
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) {

Expand Down
61 changes: 60 additions & 1 deletion vdr/sql/verification_method.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,76 @@

package sql

import "gorm.io/gorm/schema"
import (
"errors"
"fmt"

"database/sql/driver"
"encoding/base64"
"gorm.io/gorm/schema"
)

var _ schema.Tabler = (*SqlVerificationMethod)(nil)

// SqlVerificationMethod is the gorm representation of the did_verificationmethod table
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)
case []uint8: // mysql driver returns []uint8 for string
*kt, err = stringToUint(string(v))
default:
err = fmt.Errorf("db type not supported: %T", v)
}
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
}
72 changes: 72 additions & 0 deletions vdr/sql/verification_method_test.go
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*
*/

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)
})
}
})
}

0 comments on commit 2dca044

Please sign in to comment.