feat(didkey): didkey package properly implements v0.7 of spec
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
coverage.txt
|
||||||
99
didkey/key.go
Normal file
99
didkey/key.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
// Package didkey implements the did:key method. A DID Method for Static
|
||||||
|
// Cryptographic Keys. From the w3c draft spec
|
||||||
|
// https://w3c-ccg.github.io/did-method-key/
|
||||||
|
package didkey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/libp2p/go-libp2p-core/crypto"
|
||||||
|
mb "github.com/multiformats/go-multibase"
|
||||||
|
varint "github.com/multiformats/go-varint"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// KeyPrefix indicates a decentralized identifier that uses the key method
|
||||||
|
KeyPrefix = "did:key"
|
||||||
|
// MulticodecKindRSAPubKey IS NOT A REAL MULTICODEC PREFIX.
|
||||||
|
// pulled from: https://github.com/w3c-ccg/lds-ed25519-2018/issues/3 because
|
||||||
|
// it's only slighly better than picking a random available byte prefix
|
||||||
|
MulticodecKindRSAPubKey = 0x5d
|
||||||
|
// MulticodecKindEd25519PubKey ed25519-pub
|
||||||
|
MulticodecKindEd25519PubKey = 0xed
|
||||||
|
)
|
||||||
|
|
||||||
|
// ID is a DID:key identifier
|
||||||
|
type ID struct {
|
||||||
|
crypto.PubKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// MulticodecType indicates the type for this multicodec
|
||||||
|
func (id ID) MulticodecType() uint64 {
|
||||||
|
switch id.Type() {
|
||||||
|
case crypto.RSA:
|
||||||
|
return MulticodecKindRSAPubKey
|
||||||
|
case crypto.Ed25519:
|
||||||
|
return MulticodecKindEd25519PubKey
|
||||||
|
default:
|
||||||
|
panic("unexpected crypto type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns this did:key formatted as a string
|
||||||
|
func (id ID) String() string {
|
||||||
|
raw, err := id.Raw()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
t := id.MulticodecType()
|
||||||
|
size := varint.UvarintSize(t)
|
||||||
|
data := make([]byte, size+len(raw))
|
||||||
|
n := varint.PutUvarint(data, t)
|
||||||
|
copy(data[n:], raw)
|
||||||
|
|
||||||
|
b58BKeyStr, err := mb.Encode(mb.Base58BTC, data)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s:%s", KeyPrefix, b58BKeyStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse turns a string into a key method ID
|
||||||
|
func Parse(keystr string) (ID, error) {
|
||||||
|
var id ID
|
||||||
|
if !strings.HasPrefix(keystr, KeyPrefix) {
|
||||||
|
return id, fmt.Errorf("decentralized identifier is not a 'key' type")
|
||||||
|
}
|
||||||
|
|
||||||
|
keystr = strings.TrimPrefix(keystr, KeyPrefix+":")
|
||||||
|
|
||||||
|
_, data, err := mb.Decode(keystr)
|
||||||
|
if err != nil {
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
keyType, n, err := varint.FromUvarint(data)
|
||||||
|
if err != nil {
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch keyType {
|
||||||
|
case MulticodecKindRSAPubKey:
|
||||||
|
pub, err := crypto.UnmarshalRsaPublicKey(data[n:])
|
||||||
|
if err != nil {
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
return ID{pub}, nil
|
||||||
|
case MulticodecKindEd25519PubKey:
|
||||||
|
pub, err := crypto.UnmarshalEd25519PublicKey(data[n:])
|
||||||
|
if err != nil {
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
return ID{pub}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return id, fmt.Errorf("unrecognized key type multicodec prefix: %x", data[0])
|
||||||
|
}
|
||||||
28
didkey/key_test.go
Normal file
28
didkey/key_test.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package didkey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestID(t *testing.T) {
|
||||||
|
keyStrED := "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH"
|
||||||
|
id, err := Parse(keyStrED)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if id.String() != keyStrED {
|
||||||
|
t.Errorf("string mismatch.\nwant: %q\ngot: %q", keyStrED, id.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
keyStrRSA := "did:key:zDQw3CC91XtvcBm5kY3FGBFgJarujMEKcarucbThDbGWVo3cAQPgvXuH5g9kxe9PNVHhAQpRQzTZQ3Pchra5pqKt6SgwTdgHXJtcWwp2pqkAmuNqhUj22naAg526RvU8u5ZE3tidrxh8zajWsrXriFwkVtZfTbEBGKewUMRVhttA3hc8Rpa6gL5HRqZ44Uq1fdKzdsBMTfG5ohhRzesiwoEtRyb3w4SPhdVYrD5Rd2KJPyTUqCvuuFwLAJJcDaPkp36RnRnFbbtZEydPtdMscnJTmj5nD9kpSmkTHU2riMVLcGnJWRHkHWZPnfbtuXcQVEwiBSjpJVRcCFG6LDbgTqt4s6BYxJqT5zriEaigzvFuj9V4s7QSJCs1Sj92tgDmE6YyTSR7UN9di2oHDCzk"
|
||||||
|
|
||||||
|
id, err = Parse(keyStrRSA)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if id.String() != keyStrRSA {
|
||||||
|
t.Errorf("string mismatch.\nwant: %q\ngot: %q", keyStrRSA, id.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func ExampleWalkthrough() {
|
func ExampleWalkthrough() {
|
||||||
source, err := ucan.NewPrivKeyUCANSource(keyOne)
|
source, err := ucan.NewPrivKeySource(keyOne)
|
||||||
panicIfError(err)
|
panicIfError(err)
|
||||||
|
|
||||||
subjectDID, err := ucan.DIDStringFromPublicKey(keyOne.GetPublic())
|
subjectDID, err := ucan.DIDStringFromPublicKey(keyOne.GetPublic())
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -7,7 +7,9 @@ require (
|
|||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||||
github.com/ipfs/go-cid v0.0.7
|
github.com/ipfs/go-cid v0.0.7
|
||||||
github.com/libp2p/go-libp2p-core v0.7.0
|
github.com/libp2p/go-libp2p-core v0.7.0
|
||||||
|
github.com/multiformats/go-multibase v0.0.3
|
||||||
github.com/multiformats/go-multihash v0.0.14
|
github.com/multiformats/go-multihash v0.0.14
|
||||||
|
github.com/multiformats/go-varint v0.0.6
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
|
||||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect
|
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
40
token.go
40
token.go
@@ -99,7 +99,6 @@ type Fact struct {
|
|||||||
// func (fct *Fact) UnmarshalJSON(p []byte) error {
|
// func (fct *Fact) UnmarshalJSON(p []byte) error {
|
||||||
// var str string
|
// var str string
|
||||||
// if json.Unmarshal(p, &str); err == nil {
|
// if json.Unmarshal(p, &str); err == nil {
|
||||||
|
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
@@ -110,39 +109,42 @@ type CIDBytesResolver interface {
|
|||||||
ResolveCIDBytes(ctx context.Context, id cid.Cid) ([]byte, error)
|
ResolveCIDBytes(ctx context.Context, id cid.Cid) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UCANSource creates tokens, and provides a verification key for all tokens
|
// Source creates tokens, and provides a verification key for all tokens it
|
||||||
// it creates
|
// creates
|
||||||
//
|
//
|
||||||
// implementations of UCANSource must conform to the assertion test defined
|
// implementations of Source must conform to the assertion test defined in the
|
||||||
// in the spec subpackage
|
// spec subpackage
|
||||||
type UCANSource interface {
|
type Source interface {
|
||||||
NewOriginUCAN(subjectDID string, att Attenuations, fct []Fact, notBefore, expires time.Time) (*UCAN, error)
|
NewOriginUCAN(subjectDID string, att Attenuations, fct []Fact, notBefore, expires time.Time) (*UCAN, error)
|
||||||
NewAttenuatedUCAN(parent *UCAN, subjectDID string, att Attenuations, fct []Fact, notBefore, expires time.Time) (*UCAN, error)
|
NewAttenuatedUCAN(parent *UCAN, subjectDID string, att Attenuations, fct []Fact, notBefore, expires time.Time) (*UCAN, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type pkUCANSource struct {
|
type pkSource struct {
|
||||||
pk crypto.PrivKey
|
pk crypto.PrivKey
|
||||||
issuerDID string
|
issuerDID string
|
||||||
signingMethod jwt.SigningMethod
|
signingMethod jwt.SigningMethod
|
||||||
verifyKey *rsa.PublicKey
|
|
||||||
signKey *rsa.PrivateKey
|
verifyKey *rsa.PublicKey
|
||||||
|
signKey *rsa.PrivateKey
|
||||||
|
|
||||||
ap AttenuationConstructor
|
ap AttenuationConstructor
|
||||||
resolver CIDBytesResolver
|
resolver CIDBytesResolver
|
||||||
store TokenStore
|
store TokenStore
|
||||||
}
|
}
|
||||||
|
|
||||||
// assert pkUCANSource implements tokens at compile time
|
// assert pkSource implements tokens at compile time
|
||||||
var _ UCANSource = (*pkUCANSource)(nil)
|
var _ Source = (*pkSource)(nil)
|
||||||
|
|
||||||
// NewPrivKeyUCANSource creates an authentication interface backed by a single
|
// NewPrivKeySource creates an authentication interface backed by a single
|
||||||
// private key. Intended for a node running as remote, or providing a public API
|
// private key. Intended for a node running as remote, or providing a public API
|
||||||
func NewPrivKeyUCANSource(privKey crypto.PrivKey) (UCANSource, error) {
|
func NewPrivKeySource(privKey crypto.PrivKey) (Source, error) {
|
||||||
methodStr := ""
|
methodStr := ""
|
||||||
keyType := privKey.Type().String()
|
keyType := privKey.Type()
|
||||||
switch keyType {
|
switch keyType {
|
||||||
case "RSA":
|
case crypto.RSA:
|
||||||
methodStr = "RS256"
|
methodStr = "RS256"
|
||||||
|
case crypto.Ed25519:
|
||||||
|
methodStr = "EdDSA"
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported key type for token creation: %q", keyType)
|
return nil, fmt.Errorf("unsupported key type for token creation: %q", keyType)
|
||||||
}
|
}
|
||||||
@@ -176,7 +178,7 @@ func NewPrivKeyUCANSource(privKey crypto.PrivKey) (UCANSource, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &pkUCANSource{
|
return &pkSource{
|
||||||
pk: privKey,
|
pk: privKey,
|
||||||
signingMethod: signingMethod,
|
signingMethod: signingMethod,
|
||||||
verifyKey: verifyKey,
|
verifyKey: verifyKey,
|
||||||
@@ -185,11 +187,11 @@ func NewPrivKeyUCANSource(privKey crypto.PrivKey) (UCANSource, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *pkUCANSource) NewOriginUCAN(subjectDID string, att Attenuations, fct []Fact, nbf, exp time.Time) (*UCAN, error) {
|
func (a *pkSource) NewOriginUCAN(subjectDID string, att Attenuations, fct []Fact, nbf, exp time.Time) (*UCAN, error) {
|
||||||
return a.newUCAN(subjectDID, nil, att, fct, nbf, exp)
|
return a.newUCAN(subjectDID, nil, att, fct, nbf, exp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *pkUCANSource) NewAttenuatedUCAN(parent *UCAN, subjectDID string, att Attenuations, fct []Fact, nbf, exp time.Time) (*UCAN, error) {
|
func (a *pkSource) NewAttenuatedUCAN(parent *UCAN, subjectDID string, att Attenuations, fct []Fact, nbf, exp time.Time) (*UCAN, error) {
|
||||||
if !parent.Attenuations.Contains(att) {
|
if !parent.Attenuations.Contains(att) {
|
||||||
return nil, fmt.Errorf("scope of ucan attenuations must be less than it's parent")
|
return nil, fmt.Errorf("scope of ucan attenuations must be less than it's parent")
|
||||||
}
|
}
|
||||||
@@ -197,7 +199,7 @@ func (a *pkUCANSource) NewAttenuatedUCAN(parent *UCAN, subjectDID string, att At
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateToken returns a new JWT token
|
// CreateToken returns a new JWT token
|
||||||
func (a *pkUCANSource) newUCAN(subjectDID string, prf []Proof, att Attenuations, fct []Fact, nbf, exp time.Time) (*UCAN, error) {
|
func (a *pkSource) newUCAN(subjectDID string, prf []Proof, att Attenuations, fct []Fact, nbf, exp time.Time) (*UCAN, error) {
|
||||||
// create a signer for rsa 256
|
// create a signer for rsa 256
|
||||||
t := jwt.New(a.signingMethod)
|
t := jwt.New(a.signingMethod)
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPrivKeySource(t *testing.T) {
|
func TestPrivKeySource(t *testing.T) {
|
||||||
source, err := ucan.NewPrivKeyUCANSource(keyOne)
|
source, err := ucan.NewPrivKeySource(keyOne)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user