Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend DID verification method model with key usage #3209

Merged
merged 4 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
8 changes: 6 additions & 2 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 Down
57 changes: 56 additions & 1 deletion vdr/sql/verification_method.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,72 @@

package sql

import "gorm.io/gorm/schema"
import (
"database/sql/driver"
"encoding/base64"
"errors"
"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)
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
}
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)
})
}
})
}
Loading