Skip to content

Commit

Permalink
more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
reinkrul committed Oct 13, 2023
1 parent 63abfa7 commit a85b27a
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 23 deletions.
40 changes: 33 additions & 7 deletions vdr/didkey/resolver.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
/*
* Copyright (C) 2023 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 didkey

import (
Expand All @@ -10,6 +28,7 @@ import (
"encoding/binary"
"errors"
"fmt"
"github.com/lestrrat-go/jwx/x25519"
"github.com/multiformats/go-multicodec"
ssi "github.com/nuts-foundation/go-did"
"github.com/nuts-foundation/go-did/did"
Expand Down Expand Up @@ -48,32 +67,39 @@ func (r Resolver) Resolve(id did.DID, _ *resolver.ResolveMetadata) (*did.Documen
reader := bytes.NewReader(mcBytes)
keyType, err := binary.ReadUvarint(reader)
if err != nil {
return nil, nil, fmt.Errorf("did:key: invalid base58btc: %w", err)
return nil, nil, fmt.Errorf("did:key: invalid multicodec value: %w", err)
}
// See https://w3c-ccg.github.io/did-method-key/#signature-method-creation-algorithm
var key crypto.PublicKey
mcBytes, _ = io.ReadAll(reader)
keyLength := len(mcBytes)

switch multicodec.Code(keyType) {
case multicodec.Secp256k1Pub:
// lestrrat/jwk.New() is missing support for secp256k1
return nil, nil, errors.New("did:key: secp256k1 public keys are not supported")
case multicodec.Bls12_381G2Pub:
return nil, nil, errors.New("did:key: bls12381 public keys are not supported")
case multicodec.X25519Pub:
if keyLength != 32 {
return nil, nil, errInvalidPublicKeyLength
}
key = x25519.PublicKey(mcBytes)
case multicodec.Ed25519Pub:
if keyLength != 32 {
return nil, nil, errInvalidPublicKeyLength
}
key = ed25519.PublicKey(mcBytes)
case multicodec.Secp256k1Pub:
// lestrrat/jwk.New() is missing support for secp256k1
return nil, nil, errors.New("did:key: secp256k1 public keys are not supported")
case multicodec.P256Pub:
if key, err = unmarshalEC(elliptic.P256(), 33, mcBytes); err != nil {
return nil, nil, err
}
case multicodec.P384Pub:
if key, err = unmarshalEC(elliptic.P384(), 33, mcBytes); err != nil {
if key, err = unmarshalEC(elliptic.P384(), 49, mcBytes); err != nil {
return nil, nil, err
}
case multicodec.P521Pub:
if key, err = unmarshalEC(elliptic.P521(), 33, mcBytes); err != nil {
if key, err = unmarshalEC(elliptic.P521(), -1, mcBytes); err != nil {
return nil, nil, err
}
case multicodec.RsaPub:
Expand Down Expand Up @@ -107,7 +133,7 @@ func (r Resolver) Resolve(id did.DID, _ *resolver.ResolveMetadata) (*did.Documen
}

func unmarshalEC(curve elliptic.Curve, expectedLen int, pubKeyBytes []byte) (ecdsa.PublicKey, error) {
if len(pubKeyBytes) != expectedLen {
if expectedLen != -1 && len(pubKeyBytes) != expectedLen {
return ecdsa.PublicKey{}, errInvalidPublicKeyLength
}
x, y := elliptic.UnmarshalCompressed(curve, pubKeyBytes)
Expand Down
138 changes: 122 additions & 16 deletions vdr/didkey/resolver_test.go
Original file line number Diff line number Diff line change
@@ -1,42 +1,81 @@
/*
* Copyright (C) 2023 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 didkey

import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"encoding/binary"
"encoding/json"
"github.com/multiformats/go-multicodec"
"github.com/nuts-foundation/go-did/did"
"github.com/shengdoushi/base58"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)

func TestResolver_Resolve(t *testing.T) {
func TestSpecificationTestVectors(t *testing.T) {
resolver := Resolver{}
type testCase struct {
name string
did string
jwk map[string]interface{}
name string
did string
jwk map[string]interface{}
error string
}

// Fixtures were taken from https://github.com/digitalbazaar/did-method-key/blob/main/test/driver.spec.js
testCases := []testCase{
// Taken from https://w3c-ccg.github.io/did-method-key/#ed25519-x25519
{
name: "ed25519 #1",
did: "did:key:z6MknCCLeeHBUaHu4aHSVLDCYQW9gjVJ7a63FpMvtuVMy53T",
name: "ed25519",
did: "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
jwk: map[string]interface{}{
"kty": "OKP",
"crv": "Ed25519",
"x": "cwGXz9hryEvuEo-cBcLTBWnnr9kBjx2_1xTMndtgth4",
"x": "O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik",
},
},
// Taken from https://w3c-ccg.github.io/did-method-key/#x25519
{
name: "ed25519 #2",
did: "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
name: "x25519",
did: "did:key:z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F",
jwk: map[string]interface{}{
"crv": "X25519",
"kty": "OKP",
"crv": "Ed25519",
"x": "lJZrfAjkBXdfjebMHEUI9usidAPhAlssitLXR3OYxbI",
"x": "L-V9o0fNYkMVKNqsX7spBzD_9oSvxM_C7ZCZX1jLO3Q",
},
},
// Taken from https://w3c-ccg.github.io/did-method-key/#secp256k1
{
name: "secp256k1",
did: "did:key:zQ3shbgnTGcgBpXPdBjDur3ATMDWhS7aPs6FRFkWR19Lb9Zwz",
error: "did:key: secp256k1 public keys are not supported",
},
// Taken from https://w3c-ccg.github.io/did-method-key/#bls-12381
{
name: "bls12381",
did: "did:key:zUC7K4ndUaGZgV7Cp2yJy6JtMoUHY6u7tkcSYUvPrEidqBmLCTLmi6d5WvwnUqejscAkERJ3bfjEiSYtdPkRSE8kSa11hFBr4sTgnbZ95SJj19PN2jdvJjyzpSZgxkyyxNnBNnY",
error: "did:key: bls12381 public keys are not supported",
},
// Taken from https://w3c-ccg.github.io/did-method-key/#p-256
{
name: "secp256",
did: "did:key:zDnaeucDGfhXHoJVqot3p21RuupNJ2fZrs8Lb1GV83VnSo2jR",
Expand All @@ -47,18 +86,56 @@ func TestResolver_Resolve(t *testing.T) {
"y": "l5Jr9_48oPJWHwuVmH_VZVquGe-U8RtnR-McN4tdYhs",
},
},
// Taken from https://w3c-ccg.github.io/did-method-key/#p-384
{
name: "secp384",
did: "did:key:z82Lm1MpAkeJcix9K8TMiLd5NMAhnwkjjCBeWHXyu3U4oT2MVJJKXkcVBgjGhnLBn2Kaau9",
jwk: map[string]interface{}{
"kty": "EC",
"crv": "P-384",
"x": "lInTxl8fjLKp_UCrxI0WDklahi-7-_6JbtiHjiRvMvhedhKVdHBfi2HCY8t_QJyc",
"y": "y6N1IC-2mXxHreETBW7K3mBcw0qGr3CWHCs-yl09yCQRLcyfGv7XhqAngHOu51Zv",
},
},
// Taken from https://w3c-ccg.github.io/did-method-key/#p-521
{
name: "secp256k1",
did: "did:key:zQ3shbgnTGcgBpXPdBjDur3ATMDWhS7aPs6FRFkWR19Lb9Zwz",
name: "secp521",
did: "did:key:z2J9gaYxrKVpdoG9A4gRnmpnRCcxU6agDtFVVBVdn1JedouoZN7SzcyREXXzWgt3gGiwpoHq7K68X4m32D8HgzG8wv3sY5j7",
jwk: map[string]interface{}{
"kty": "EC",
"crv": "secp256k1",
"crv": "P-521",
"x": "ASUHPMyichQ0QbHZ9ofNx_l4y7luncn5feKLo3OpJ2nSbZoC7mffolj5uy7s6KSKXFmnNWxGJ42IOrjZ47qqwqyS",
"y": "AW9ziIC4ZQQVSNmLlp59yYKrjRY0_VqO-GOIYQ9tYpPraBKUloEId6cI_vynCzlZWZtWpgOM3HPhYEgawQ703RjC",
},
},
// Taken from https://w3c-ccg.github.io/did-method-key/#rsa-2048
{
name: "rsa2048",
did: "did:key:z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i",
jwk: map[string]interface{}{
"kty": "RSA",
"e": "AQAB",
"n": "sbX82NTV6IylxCh7MfV4hlyvaniCajuP97GyOqSvTmoEdBOflFvZ06kR_9D6ctt45Fk6hskfnag2GG69NALVH2o4RCR6tQiLRpKcMRtDYE_thEmfBvDzm_VVkOIYfxu-Ipuo9J_S5XDNDjczx2v-3oDh5-CIHkU46hvFeCvpUS-L8TJSbgX0kjVk_m4eIb9wh63rtmD6Uz_KBtCo5mmR4TEtcLZKYdqMp3wCjN-TlgHiz_4oVXWbHUefCEe8rFnX1iQnpDHU49_SaXQoud1jCaexFn25n-Aa8f8bc5Vm-5SeRwidHa6ErvEhTvf1dz6GoNPp2iRvm-wJ1gxwWJEYPQ",
},
},
// Taken from https://w3c-ccg.github.io/did-method-key/#rsa-4096
{
name: "rsa4096",
did: "did:key:zgghBUVkqmWS8e1ioRVp2WN9Vw6x4NvnE9PGAyQsPqM3fnfPf8EdauiRVfBTcVDyzhqM5FFC7ekAvuV1cJHawtfgB9wDcru1hPDobk3hqyedijhgWmsYfJCmodkiiFnjNWATE7PvqTyoCjcmrc8yMRXmFPnoASyT5beUd4YZxTE9VfgmavcPy3BSouNmASMQ8xUXeiRwjb7xBaVTiDRjkmyPD7NYZdXuS93gFhyDFr5b3XLg7Rfj9nHEqtHDa7NmAX7iwDAbMUFEfiDEf9hrqZmpAYJracAjTTR8Cvn6mnDXMLwayNG8dcsXFodxok2qksYF4D8ffUxMRmyyQVQhhhmdSi4YaMPqTnC1J6HTG9Yfb98yGSVaWi4TApUhLXFow2ZvB6vqckCNhjCRL2R4MDUSk71qzxWHgezKyDeyThJgdxydrn1osqH94oSeA346eipkJvKqYREXBKwgB5VL6WF4qAK6sVZxJp2dQBfCPVZ4EbsBQaJXaVK7cNcWG8tZBFWZ79gG9Cu6C4u8yjBS8Ux6dCcJPUTLtixQu4z2n5dCsVSNdnP1EEs8ZerZo5pBgc68w4Yuf9KL3xVxPnAB1nRCBfs9cMU6oL1EdyHbqrTfnjE8HpY164akBqe92LFVsk8RusaGsVPrMekT8emTq5y8v8CabuZg5rDs3f9NPEtogjyx49wiub1FecM5B7QqEcZSYiKHgF4mfkteT2",
jwk: map[string]interface{}{
"kty": "RSA",
"e": "AQAB",
"n": "qMCkFFRFWtzUyZeK8mgJdyM6SEQcXC5E6JwCRVDld-jlJs8sXNOE_vliexq34wZRQ4hk53-JPFlvZ_QjRgIxdUxSMiZ3S5hlNVvvRaue6SMakA9ugQhnfXaWORro0UbPuHLms-bg5StDP8-8tIezu9c1H1FjwPcdbV6rAvKhyhnsM10qP3v2CPbdE0q3FOsihoKuTelImtO110E7N6fLn4U3EYbC4OyViqlrP1o_1M-R-tiM1cb4pD7XKJnIs6ryZdfOQSPBJwjNqSdN6Py_tdrFgPDTyacSSdpTVADOM2IMAoYbhV1N5APhnjOHBRFyKkF1HffQKpmXQLBqvUNNjuhmpVKWBtrTdcCKrglFXiw0cKGHKxIirjmiOlB_HYHg5UdosyE3_1Txct2U7-WBB6QXak1UgxCzgKYBDI8UPA0RlkUuHHP_Zg0fVXrXIInHO04MYxUeSps5qqyP6dJBu_v_BDn3zUq6LYFwJ_-xsU7zbrKYB4jaRlHPoCj_eDC-rSA2uQ4KXHBB8_aAqNFC9ukWxc26Ifz9dF968DLuL30bi-ZAa2oUh492Pw1bg89J7i4qTsOOfpQvGyDV7TGhKuUG3Hbumfr2w16S-_3EI2RIyd1nYsflE6ZmCkZQMG_lwDAFXaqfyGKEDouJuja4XH8r4fGWeGTrozIoniXT1HU",
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
doc, md, err := resolver.Resolve(did.MustParseDID(tc.did), nil)
if tc.error != "" {
require.EqualError(t, err, tc.error)
return
}
require.NoError(t, err)
require.NotNil(t, doc)
require.NotNil(t, md)
Expand All @@ -77,7 +154,21 @@ func TestResolver_Resolve(t *testing.T) {
assert.Equal(t, tc.jwk, jwkAsMap)
})
}
}

func TestResolver_Resolve(t *testing.T) {
t.Run("did:key does not start with 'z' (invalid multibase encoding)", func(t *testing.T) {
_, _, err := Resolver{}.Resolve(did.MustParseDID("did:key:foo"), nil)
require.EqualError(t, err, "did:key does not start with 'z'")
})
t.Run("did:key is not valid base58btc encoded 'z'", func(t *testing.T) {
_, _, err := Resolver{}.Resolve(did.MustParseDID("did:key:z291830129"), nil)
require.EqualError(t, err, "did:key: invalid base58btc: invalid base58 string")
})
t.Run("did:key invalid multicodec key type", func(t *testing.T) {
_, _, err := Resolver{}.Resolve(did.MustParseDID("did:key:z"), nil)
require.EqualError(t, err, "did:key: invalid multicodec value: EOF")
})
t.Run("verify created DID document", func(t *testing.T) {
const expected = `
{
Expand Down Expand Up @@ -115,7 +206,7 @@ func TestResolver_Resolve(t *testing.T) {
]
}
`
doc, md, err := resolver.Resolve(did.MustParseDID("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"), nil)
doc, md, err := Resolver{}.Resolve(did.MustParseDID("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"), nil)
require.NoError(t, err)
require.NotNil(t, doc)
require.NotNil(t, md)
Expand All @@ -131,3 +222,18 @@ func TestResolver_Resolve(t *testing.T) {
func TestNewResolver(t *testing.T) {
assert.NotNil(t, NewResolver())
}

func createDIDKey(keyType multicodec.Code, data []byte) string {
mcBytes := append(binary.AppendUvarint([]byte{}, uint64(keyType)), data...)
return "did:key:z" + string(base58.Encode(mcBytes, base58.BitcoinAlphabet))
}

func TestRoundTrip(t *testing.T) {
t.Run("secp384", func(t *testing.T) {
keyPair, _ := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
data := elliptic.MarshalCompressed(elliptic.P384(), keyPair.PublicKey.X, keyPair.PublicKey.Y)
key := createDIDKey(multicodec.P384Pub, data)
_, _, err := Resolver{}.Resolve(did.MustParseDID(key), nil)
require.NoError(t, err)
})
}

0 comments on commit a85b27a

Please sign in to comment.