add support for JsonWebKey2020
This commit is contained in:
committed by
Michael Muré
parent
775b693cd0
commit
2809127a08
@@ -31,6 +31,14 @@ func PublicKeyFromBytes(b []byte) (*PublicKey, error) {
|
||||
return (*PublicKey)(&ecdsa.PublicKey{Curve: elliptic.P256(), X: x, Y: y}), nil
|
||||
}
|
||||
|
||||
// PublicKeyFromXY converts x and y coordinates into a PublicKey.
|
||||
func PublicKeyFromXY(x, y *big.Int) (*PublicKey, error) {
|
||||
if !elliptic.P256().IsOnCurve(x, y) {
|
||||
return nil, fmt.Errorf("invalid P-256 public key")
|
||||
}
|
||||
return (*PublicKey)(&ecdsa.PublicKey{Curve: elliptic.P256(), X: x, Y: y}), nil
|
||||
}
|
||||
|
||||
// PublicKeyFromPublicKeyMultibase decodes the public key from its Multibase form
|
||||
func PublicKeyFromPublicKeyMultibase(multibase string) (*PublicKey, error) {
|
||||
code, bytes, err := helpers.PublicKeyMultibaseDecode(multibase)
|
||||
|
||||
@@ -8,11 +8,57 @@ import (
|
||||
|
||||
_ "github.com/INFURA/go-did/methods/did-key"
|
||||
"github.com/INFURA/go-did/verifications/ed25519"
|
||||
"github.com/INFURA/go-did/verifications/jsonwebkey"
|
||||
"github.com/INFURA/go-did/verifications/x25519"
|
||||
)
|
||||
|
||||
func TestRoundTrip(t *testing.T) {
|
||||
strDoc := `
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
strDoc string
|
||||
assertion func(t *testing.T, doc *Document)
|
||||
}{
|
||||
{
|
||||
name: "ed25519",
|
||||
strDoc: ed25519Doc,
|
||||
assertion: func(t *testing.T, doc *Document) {
|
||||
require.Equal(t, "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK", doc.ID())
|
||||
require.Equal(t, ed25519vm.Type, doc.Authentication()[0].Type())
|
||||
require.Equal(t, ed25519vm.Type, doc.Assertion()[0].Type())
|
||||
require.Equal(t, x25519vm.Type, doc.KeyAgreement()[0].Type())
|
||||
require.Equal(t, ed25519vm.Type, doc.CapabilityInvocation()[0].Type())
|
||||
require.Equal(t, ed25519vm.Type, doc.CapabilityDelegation()[0].Type())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "jsonWebKey",
|
||||
strDoc: jsonWebKeyDoc,
|
||||
assertion: func(t *testing.T, doc *Document) {
|
||||
require.Equal(t, "did:example:123", doc.ID())
|
||||
require.Len(t, doc.VerificationMethods(), 6)
|
||||
require.Equal(t, jsonwebkey.Type, doc.verificationMethods["did:example:123#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A"].Type())
|
||||
require.Equal(t, jsonwebkey.Type, doc.verificationMethods["did:example:123#4SZ-StXrp5Yd4_4rxHVTCYTHyt4zyPfN1fIuYsm6k3A"].Type())
|
||||
require.Equal(t, jsonwebkey.Type, doc.verificationMethods["did:example:123#n4cQ-I_WkHMcwXBJa7IHkYu8CMfdNcZKnKsOrnHLpFs"].Type())
|
||||
require.Equal(t, jsonwebkey.Type, doc.verificationMethods["did:example:123#_TKzHv2jFIyvdTGF1Dsgwngfdg3SH6TpDv0Ta1aOEkw"].Type())
|
||||
require.Equal(t, jsonwebkey.Type, doc.verificationMethods["did:example:123#8wgRfY3sWmzoeAL-78-oALNvNj67ZlQxd1ss_NX1hZY"].Type())
|
||||
require.Equal(t, jsonwebkey.Type, doc.verificationMethods["did:example:123#NjQ6Y_ZMj6IUK_XkgCDwtKHlNTUTVjEYOWZtxhp1n-E"].Type())
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
doc, err := FromJsonBytes([]byte(tc.strDoc))
|
||||
require.NoError(t, err)
|
||||
|
||||
tc.assertion(t, doc)
|
||||
|
||||
roundtrip, err := json.Marshal(doc)
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, tc.strDoc, string(roundtrip))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const ed25519Doc = `
|
||||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/did/v1",
|
||||
@@ -46,18 +92,108 @@ func TestRoundTrip(t *testing.T) {
|
||||
}]
|
||||
}
|
||||
`
|
||||
doc, err := FromJsonBytes([]byte(strDoc))
|
||||
require.NoError(t, err)
|
||||
|
||||
// basic testing
|
||||
require.Equal(t, "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK", doc.ID())
|
||||
require.Equal(t, ed25519vm.Type, doc.Authentication()[0].Type())
|
||||
require.Equal(t, ed25519vm.Type, doc.Assertion()[0].Type())
|
||||
require.Equal(t, x25519vm.Type, doc.KeyAgreement()[0].Type())
|
||||
require.Equal(t, ed25519vm.Type, doc.CapabilityInvocation()[0].Type())
|
||||
require.Equal(t, ed25519vm.Type, doc.CapabilityDelegation()[0].Type())
|
||||
|
||||
roundtrip, err := json.Marshal(doc)
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, strDoc, string(roundtrip))
|
||||
const jsonWebKeyDoc = `
|
||||
{
|
||||
"@context": ["https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/jws-2020/v1"],
|
||||
"id": "did:example:123",
|
||||
"verificationMethod": [
|
||||
{
|
||||
"id": "did:example:123#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A",
|
||||
"type": "JsonWebKey2020",
|
||||
"controller": "did:example:123",
|
||||
"publicKeyJwk": {
|
||||
"kty": "OKP",
|
||||
"crv": "Ed25519",
|
||||
"x": "VCpo2LMLhn6iWku8MKvSLg2ZAoC-nlOyPVQaO3FxVeQ"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "did:example:123#4SZ-StXrp5Yd4_4rxHVTCYTHyt4zyPfN1fIuYsm6k3A",
|
||||
"type": "JsonWebKey2020",
|
||||
"controller": "did:example:123",
|
||||
"publicKeyJwk": {
|
||||
"kty": "EC",
|
||||
"crv": "secp256k1",
|
||||
"x": "Z4Y3NNOxv0J6tCgqOBFnHnaZhJF6LdulT7z8A-2D5_8",
|
||||
"y": "i5a2NtJoUKXkLm6q8nOEu9WOkso1Ag6FTUT6k_LMnGk"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "did:example:123#n4cQ-I_WkHMcwXBJa7IHkYu8CMfdNcZKnKsOrnHLpFs",
|
||||
"type": "JsonWebKey2020",
|
||||
"controller": "did:example:123",
|
||||
"publicKeyJwk": {
|
||||
"kty": "RSA",
|
||||
"e": "AQAB",
|
||||
"n": "omwsC1AqEk6whvxyOltCFWheSQvv1MExu5RLCMT4jVk9khJKv8JeMXWe3bWHatjPskdf2dlaGkW5QjtOnUKL742mvr4tCldKS3ULIaT1hJInMHHxj2gcubO6eEegACQ4QSu9LO0H-LM_L3DsRABB7Qja8HecpyuspW1Tu_DbqxcSnwendamwL52V17eKhlO4uXwv2HFlxufFHM0KmCJujIKyAxjD_m3q__IiHUVHD1tDIEvLPhG9Azsn3j95d-saIgZzPLhQFiKluGvsjrSkYU5pXVWIsV-B2jtLeeLC14XcYxWDUJ0qVopxkBvdlERcNtgF4dvW4X00EHj4vCljFw"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "did:example:123#_TKzHv2jFIyvdTGF1Dsgwngfdg3SH6TpDv0Ta1aOEkw",
|
||||
"type": "JsonWebKey2020",
|
||||
"controller": "did:example:123",
|
||||
"publicKeyJwk": {
|
||||
"kty": "EC",
|
||||
"crv": "P-256",
|
||||
"x": "38M1FDts7Oea7urmseiugGW7tWc3mLpJh6rKe7xINZ8",
|
||||
"y": "nDQW6XZ7b_u2Sy9slofYLlG03sOEoug3I0aAPQ0exs4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "did:example:123#8wgRfY3sWmzoeAL-78-oALNvNj67ZlQxd1ss_NX1hZY",
|
||||
"type": "JsonWebKey2020",
|
||||
"controller": "did:example:123",
|
||||
"publicKeyJwk": {
|
||||
"kty": "EC",
|
||||
"crv": "P-384",
|
||||
"x": "GnLl6mDti7a2VUIZP5w6pcRX8q5nvEIgB3Q_5RI2p9F_QVsaAlDN7IG68Jn0dS_F",
|
||||
"y": "jq4QoAHKiIzezDp88s_cxSPXtuXYFliuCGndgU4Qp8l91xzD1spCmFIzQgVjqvcP"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "did:example:123#NjQ6Y_ZMj6IUK_XkgCDwtKHlNTUTVjEYOWZtxhp1n-E",
|
||||
"type": "JsonWebKey2020",
|
||||
"controller": "did:example:123",
|
||||
"publicKeyJwk": {
|
||||
"kty": "EC",
|
||||
"crv": "P-521",
|
||||
"x": "AVlZG23LyXYwlbjbGPMxZbHmJpDSu-IvpuKigEN2pzgWtSo--Rwd-n78nrWnZzeDc187Ln3qHlw5LRGrX4qgLQ-y",
|
||||
"y": "ANIbFeRdPHf1WYMCUjcPz-ZhecZFybOqLIJjVOlLETH7uPlyG0gEoMWnIZXhQVypPy_HtUiUzdnSEPAylYhHBTX2"
|
||||
}
|
||||
}
|
||||
],
|
||||
"authentication": [
|
||||
"did:example:123#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A",
|
||||
"did:example:123#4SZ-StXrp5Yd4_4rxHVTCYTHyt4zyPfN1fIuYsm6k3A",
|
||||
"did:example:123#n4cQ-I_WkHMcwXBJa7IHkYu8CMfdNcZKnKsOrnHLpFs",
|
||||
"did:example:123#_TKzHv2jFIyvdTGF1Dsgwngfdg3SH6TpDv0Ta1aOEkw",
|
||||
"did:example:123#8wgRfY3sWmzoeAL-78-oALNvNj67ZlQxd1ss_NX1hZY",
|
||||
"did:example:123#NjQ6Y_ZMj6IUK_XkgCDwtKHlNTUTVjEYOWZtxhp1n-E"
|
||||
],
|
||||
"assertionMethod": [
|
||||
"did:example:123#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A",
|
||||
"did:example:123#4SZ-StXrp5Yd4_4rxHVTCYTHyt4zyPfN1fIuYsm6k3A",
|
||||
"did:example:123#n4cQ-I_WkHMcwXBJa7IHkYu8CMfdNcZKnKsOrnHLpFs",
|
||||
"did:example:123#_TKzHv2jFIyvdTGF1Dsgwngfdg3SH6TpDv0Ta1aOEkw",
|
||||
"did:example:123#8wgRfY3sWmzoeAL-78-oALNvNj67ZlQxd1ss_NX1hZY",
|
||||
"did:example:123#NjQ6Y_ZMj6IUK_XkgCDwtKHlNTUTVjEYOWZtxhp1n-E"
|
||||
],
|
||||
"capabilityDelegation": [
|
||||
"did:example:123#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A",
|
||||
"did:example:123#4SZ-StXrp5Yd4_4rxHVTCYTHyt4zyPfN1fIuYsm6k3A",
|
||||
"did:example:123#n4cQ-I_WkHMcwXBJa7IHkYu8CMfdNcZKnKsOrnHLpFs",
|
||||
"did:example:123#_TKzHv2jFIyvdTGF1Dsgwngfdg3SH6TpDv0Ta1aOEkw",
|
||||
"did:example:123#8wgRfY3sWmzoeAL-78-oALNvNj67ZlQxd1ss_NX1hZY",
|
||||
"did:example:123#NjQ6Y_ZMj6IUK_XkgCDwtKHlNTUTVjEYOWZtxhp1n-E"
|
||||
],
|
||||
"capabilityInvocation": [
|
||||
"did:example:123#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A",
|
||||
"did:example:123#4SZ-StXrp5Yd4_4rxHVTCYTHyt4zyPfN1fIuYsm6k3A",
|
||||
"did:example:123#n4cQ-I_WkHMcwXBJa7IHkYu8CMfdNcZKnKsOrnHLpFs",
|
||||
"did:example:123#_TKzHv2jFIyvdTGF1Dsgwngfdg3SH6TpDv0Ta1aOEkw",
|
||||
"did:example:123#8wgRfY3sWmzoeAL-78-oALNvNj67ZlQxd1ss_NX1hZY",
|
||||
"did:example:123#NjQ6Y_ZMj6IUK_XkgCDwtKHlNTUTVjEYOWZtxhp1n-E"
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/INFURA/go-did"
|
||||
"github.com/INFURA/go-did/verifications/ed25519"
|
||||
"github.com/INFURA/go-did/verifications/jsonwebkey"
|
||||
"github.com/INFURA/go-did/verifications/multikey"
|
||||
"github.com/INFURA/go-did/verifications/x25519"
|
||||
)
|
||||
@@ -26,6 +27,8 @@ func UnmarshalJSON(data []byte) (did.VerificationMethod, error) {
|
||||
res = &multikey.MultiKey{}
|
||||
case x25519vm.Type:
|
||||
res = &x25519vm.KeyAgreementKey2020{}
|
||||
case jsonwebkey.Type:
|
||||
res = &jsonwebkey.JsonWebKey2020{}
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown verification type: %s", aux.Type)
|
||||
}
|
||||
|
||||
106
verifications/jsonwebkey/JsonWebKey2020.go
Normal file
106
verifications/jsonwebkey/JsonWebKey2020.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package jsonwebkey
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/INFURA/go-did"
|
||||
"github.com/INFURA/go-did/crypto"
|
||||
)
|
||||
|
||||
// Specification: https://www.w3.org/TR/vc-jws-2020/
|
||||
|
||||
const (
|
||||
JsonLdContext = "https://w3id.org/security/suites/jws-2020/v1"
|
||||
Type = "JsonWebKey2020"
|
||||
)
|
||||
|
||||
var _ did.VerificationMethodSignature = &JsonWebKey2020{}
|
||||
var _ did.VerificationMethodKeyAgreement = &JsonWebKey2020{}
|
||||
|
||||
type JsonWebKey2020 struct {
|
||||
id string
|
||||
pubkey crypto.PublicKey
|
||||
controller string
|
||||
}
|
||||
|
||||
func NewJsonWebKey2020(id string, pubkey crypto.PublicKey, controller did.DID) *JsonWebKey2020 {
|
||||
return &JsonWebKey2020{
|
||||
id: id,
|
||||
pubkey: pubkey,
|
||||
controller: controller.String(),
|
||||
}
|
||||
}
|
||||
|
||||
func (j JsonWebKey2020) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Controller string `json:"controller"`
|
||||
PublicKeyJWK jwk `json:"publicKeyJwk"`
|
||||
}{
|
||||
ID: j.ID(),
|
||||
Type: j.Type(),
|
||||
Controller: j.Controller(),
|
||||
PublicKeyJWK: jwk{pubkey: j.pubkey},
|
||||
})
|
||||
}
|
||||
|
||||
func (j *JsonWebKey2020) UnmarshalJSON(bytes []byte) error {
|
||||
aux := struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Controller string `json:"controller"`
|
||||
PublicKeyJWK jwk `json:"publicKeyJwk"`
|
||||
}{}
|
||||
err := json.Unmarshal(bytes, &aux)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if aux.Type != j.Type() {
|
||||
return errors.New("invalid type")
|
||||
}
|
||||
j.id = aux.ID
|
||||
if len(j.id) == 0 {
|
||||
return errors.New("invalid id")
|
||||
}
|
||||
j.controller = aux.Controller
|
||||
if !did.HasValidDIDSyntax(j.controller) {
|
||||
return errors.New("invalid controller")
|
||||
}
|
||||
|
||||
j.pubkey = aux.PublicKeyJWK.pubkey
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (j JsonWebKey2020) ID() string {
|
||||
return j.id
|
||||
}
|
||||
|
||||
func (j JsonWebKey2020) Type() string {
|
||||
return Type
|
||||
}
|
||||
|
||||
func (j JsonWebKey2020) Controller() string {
|
||||
return j.controller
|
||||
}
|
||||
|
||||
func (j JsonWebKey2020) JsonLdContext() string {
|
||||
return JsonLdContext
|
||||
}
|
||||
|
||||
func (j JsonWebKey2020) Verify(data []byte, sig []byte) (bool, error) {
|
||||
if pub, ok := j.pubkey.(crypto.SigningPublicKey); ok {
|
||||
return pub.VerifyBytes(data, sig), nil
|
||||
}
|
||||
return false, errors.New("not a signing public key")
|
||||
}
|
||||
|
||||
func (j JsonWebKey2020) PrivateKeyIsCompatible(local crypto.KeyExchangePrivateKey) bool {
|
||||
return local.PublicKeyIsCompatible(j.pubkey)
|
||||
}
|
||||
|
||||
func (j JsonWebKey2020) KeyExchange(local crypto.KeyExchangePrivateKey) ([]byte, error) {
|
||||
return local.KeyExchange(j.pubkey)
|
||||
}
|
||||
102
verifications/jsonwebkey/JsonWebKey2020_test.go
Normal file
102
verifications/jsonwebkey/JsonWebKey2020_test.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package jsonwebkey
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestJsonRoundTrip(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
str string
|
||||
}{
|
||||
{
|
||||
name: "did:example:123#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A",
|
||||
str: `{
|
||||
"id": "did:example:123#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A",
|
||||
"type": "JsonWebKey2020",
|
||||
"controller": "did:example:123",
|
||||
"publicKeyJwk": {
|
||||
"kty": "OKP",
|
||||
"crv": "Ed25519",
|
||||
"x": "VCpo2LMLhn6iWku8MKvSLg2ZAoC-nlOyPVQaO3FxVeQ"
|
||||
}}`,
|
||||
},
|
||||
{
|
||||
name: "did:example:123#4SZ-StXrp5Yd4_4rxHVTCYTHyt4zyPfN1fIuYsm6k3A",
|
||||
str: `{
|
||||
"id": "did:example:123#4SZ-StXrp5Yd4_4rxHVTCYTHyt4zyPfN1fIuYsm6k3A",
|
||||
"type": "JsonWebKey2020",
|
||||
"controller": "did:example:123",
|
||||
"publicKeyJwk": {
|
||||
"kty": "EC",
|
||||
"crv": "secp256k1",
|
||||
"x": "Z4Y3NNOxv0J6tCgqOBFnHnaZhJF6LdulT7z8A-2D5_8",
|
||||
"y": "i5a2NtJoUKXkLm6q8nOEu9WOkso1Ag6FTUT6k_LMnGk"
|
||||
}}`,
|
||||
},
|
||||
{
|
||||
name: "did:example:123#n4cQ-I_WkHMcwXBJa7IHkYu8CMfdNcZKnKsOrnHLpFs",
|
||||
str: `{
|
||||
"id": "did:example:123#n4cQ-I_WkHMcwXBJa7IHkYu8CMfdNcZKnKsOrnHLpFs",
|
||||
"type": "JsonWebKey2020",
|
||||
"controller": "did:example:123",
|
||||
"publicKeyJwk": {
|
||||
"kty": "RSA",
|
||||
"e": "AQAB",
|
||||
"n": "omwsC1AqEk6whvxyOltCFWheSQvv1MExu5RLCMT4jVk9khJKv8JeMXWe3bWHatjPskdf2dlaGkW5QjtOnUKL742mvr4tCldKS3ULIaT1hJInMHHxj2gcubO6eEegACQ4QSu9LO0H-LM_L3DsRABB7Qja8HecpyuspW1Tu_DbqxcSnwendamwL52V17eKhlO4uXwv2HFlxufFHM0KmCJujIKyAxjD_m3q__IiHUVHD1tDIEvLPhG9Azsn3j95d-saIgZzPLhQFiKluGvsjrSkYU5pXVWIsV-B2jtLeeLC14XcYxWDUJ0qVopxkBvdlERcNtgF4dvW4X00EHj4vCljFw"
|
||||
}}`,
|
||||
},
|
||||
{
|
||||
name: "did:example:123#_TKzHv2jFIyvdTGF1Dsgwngfdg3SH6TpDv0Ta1aOEkw",
|
||||
str: `{
|
||||
"id": "did:example:123#_TKzHv2jFIyvdTGF1Dsgwngfdg3SH6TpDv0Ta1aOEkw",
|
||||
"type": "JsonWebKey2020",
|
||||
"controller": "did:example:123",
|
||||
"publicKeyJwk": {
|
||||
"kty": "EC",
|
||||
"crv": "P-256",
|
||||
"x": "38M1FDts7Oea7urmseiugGW7tWc3mLpJh6rKe7xINZ8",
|
||||
"y": "nDQW6XZ7b_u2Sy9slofYLlG03sOEoug3I0aAPQ0exs4"
|
||||
}}`,
|
||||
},
|
||||
{
|
||||
name: "did:example:123#8wgRfY3sWmzoeAL-78-oALNvNj67ZlQxd1ss_NX1hZY",
|
||||
str: `{
|
||||
"id": "did:example:123#8wgRfY3sWmzoeAL-78-oALNvNj67ZlQxd1ss_NX1hZY",
|
||||
"type": "JsonWebKey2020",
|
||||
"controller": "did:example:123",
|
||||
"publicKeyJwk": {
|
||||
"kty": "EC",
|
||||
"crv": "P-384",
|
||||
"x": "GnLl6mDti7a2VUIZP5w6pcRX8q5nvEIgB3Q_5RI2p9F_QVsaAlDN7IG68Jn0dS_F",
|
||||
"y": "jq4QoAHKiIzezDp88s_cxSPXtuXYFliuCGndgU4Qp8l91xzD1spCmFIzQgVjqvcP"
|
||||
}}`,
|
||||
},
|
||||
{
|
||||
name: "did:example:123#NjQ6Y_ZMj6IUK_XkgCDwtKHlNTUTVjEYOWZtxhp1n-E",
|
||||
str: `{
|
||||
"id": "did:example:123#NjQ6Y_ZMj6IUK_XkgCDwtKHlNTUTVjEYOWZtxhp1n-E",
|
||||
"type": "JsonWebKey2020",
|
||||
"controller": "did:example:123",
|
||||
"publicKeyJwk": {
|
||||
"kty": "EC",
|
||||
"crv": "P-521",
|
||||
"x": "AVlZG23LyXYwlbjbGPMxZbHmJpDSu-IvpuKigEN2pzgWtSo--Rwd-n78nrWnZzeDc187Ln3qHlw5LRGrX4qgLQ-y",
|
||||
"y": "ANIbFeRdPHf1WYMCUjcPz-ZhecZFybOqLIJjVOlLETH7uPlyG0gEoMWnIZXhQVypPy_HtUiUzdnSEPAylYhHBTX2"
|
||||
}}`,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var jwk JsonWebKey2020
|
||||
err := json.Unmarshal([]byte(tc.str), &jwk)
|
||||
require.NoError(t, err)
|
||||
|
||||
bytes, err := json.Marshal(jwk)
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, tc.str, string(bytes))
|
||||
})
|
||||
}
|
||||
}
|
||||
120
verifications/jsonwebkey/jwk.go
Normal file
120
verifications/jsonwebkey/jwk.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package jsonwebkey
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/INFURA/go-did/crypto"
|
||||
"github.com/INFURA/go-did/crypto/ed25519"
|
||||
"github.com/INFURA/go-did/crypto/p256"
|
||||
"github.com/INFURA/go-did/crypto/x25519"
|
||||
)
|
||||
|
||||
// Specification:
|
||||
// - https://www.rfc-editor.org/rfc/rfc7517#section-4 (JWK)
|
||||
// - https://www.iana.org/assignments/jose/jose.xhtml#web-key-types (key parameters)
|
||||
|
||||
type jwk struct {
|
||||
pubkey crypto.PublicKey
|
||||
}
|
||||
|
||||
func (j jwk) MarshalJSON() ([]byte, error) {
|
||||
switch pubkey := j.pubkey.(type) {
|
||||
case *p256.PublicKey:
|
||||
return json.Marshal(struct {
|
||||
Kty string `json:"kty"`
|
||||
Crv string `json:"crv"`
|
||||
X string `json:"x"`
|
||||
Y string `json:"y"`
|
||||
}{
|
||||
Kty: "EC",
|
||||
Crv: "P-256",
|
||||
X: base64.RawURLEncoding.EncodeToString(pubkey.X.Bytes()),
|
||||
Y: base64.RawURLEncoding.EncodeToString(pubkey.Y.Bytes()),
|
||||
})
|
||||
case ed25519.PublicKey:
|
||||
return json.Marshal(struct {
|
||||
Kty string `json:"kty"`
|
||||
Crv string `json:"crv"`
|
||||
X string `json:"x"`
|
||||
}{
|
||||
Kty: "OKP",
|
||||
Crv: "Ed25519",
|
||||
X: base64.RawURLEncoding.EncodeToString(pubkey.ToBytes()),
|
||||
})
|
||||
case *x25519.PublicKey:
|
||||
return json.Marshal(struct {
|
||||
Kty string `json:"kty"`
|
||||
Crv string `json:"crv"`
|
||||
X string `json:"x"`
|
||||
}{
|
||||
Kty: "OKP",
|
||||
Crv: "X25519",
|
||||
X: base64.RawURLEncoding.EncodeToString(pubkey.ToBytes()),
|
||||
})
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported key type %T", pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
func (j *jwk) UnmarshalJSON(bytes []byte) error {
|
||||
aux := make(map[string]string)
|
||||
err := json.Unmarshal(bytes, &aux)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bigIntBase64Url := func(s string) (*big.Int, error) {
|
||||
raw, err := base64.RawURLEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return new(big.Int).SetBytes(raw), nil
|
||||
}
|
||||
|
||||
switch aux["kty"] {
|
||||
case "EC": // Elliptic curve
|
||||
x, err := bigIntBase64Url(aux["x"])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid x parameter with kty=EC: %w", err)
|
||||
}
|
||||
y, err := bigIntBase64Url(aux["y"])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid y parameter with kty=EC: %w", err)
|
||||
}
|
||||
switch aux["crv"] {
|
||||
case "P-256":
|
||||
j.pubkey, err = p256.PublicKeyFromXY(x, y)
|
||||
return err
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported Curve %s", aux["crv"])
|
||||
}
|
||||
|
||||
case "RSA":
|
||||
return fmt.Errorf("not implemented")
|
||||
|
||||
case "OKP": // Octet key pair
|
||||
x, err := base64.RawURLEncoding.DecodeString(aux["x"])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid x parameter with kty=OKP: %w", err)
|
||||
}
|
||||
switch aux["crv"] {
|
||||
case "Ed25519":
|
||||
j.pubkey, err = ed25519.PublicKeyFromBytes(x)
|
||||
return err
|
||||
case "X25519":
|
||||
j.pubkey, err = x25519.PublicKeyFromBytes(x)
|
||||
return err
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported Curve %s", aux["crv"])
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported key type %s", aux["kty"])
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,10 @@ func (m *MultiKey) UnmarshalJSON(bytes []byte) error {
|
||||
if len(m.id) == 0 {
|
||||
return errors.New("invalid id")
|
||||
}
|
||||
m.controller = aux.Controller
|
||||
if !did.HasValidDIDSyntax(m.controller) {
|
||||
return errors.New("invalid controller")
|
||||
}
|
||||
|
||||
code, pubBytes, err := helpers.PublicKeyMultibaseDecode(aux.PublicKeyMultibase)
|
||||
if err != nil {
|
||||
@@ -83,10 +87,6 @@ func (m *MultiKey) UnmarshalJSON(bytes []byte) error {
|
||||
return fmt.Errorf("invalid publicKeyMultibase: %w", err)
|
||||
}
|
||||
|
||||
m.controller = aux.Controller
|
||||
if !did.HasValidDIDSyntax(m.controller) {
|
||||
return errors.New("invalid controller")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user