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() {
|
||||
source, err := ucan.NewPrivKeyUCANSource(keyOne)
|
||||
source, err := ucan.NewPrivKeySource(keyOne)
|
||||
panicIfError(err)
|
||||
|
||||
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/ipfs/go-cid v0.0.7
|
||||
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-varint v0.0.6
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect
|
||||
)
|
||||
|
||||
36
token.go
36
token.go
@@ -99,7 +99,6 @@ type Fact struct {
|
||||
// func (fct *Fact) UnmarshalJSON(p []byte) error {
|
||||
// var str string
|
||||
// if json.Unmarshal(p, &str); err == nil {
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -110,20 +109,21 @@ type CIDBytesResolver interface {
|
||||
ResolveCIDBytes(ctx context.Context, id cid.Cid) ([]byte, error)
|
||||
}
|
||||
|
||||
// UCANSource creates tokens, and provides a verification key for all tokens
|
||||
// it creates
|
||||
// Source creates tokens, and provides a verification key for all tokens it
|
||||
// creates
|
||||
//
|
||||
// implementations of UCANSource must conform to the assertion test defined
|
||||
// in the spec subpackage
|
||||
type UCANSource interface {
|
||||
// implementations of Source must conform to the assertion test defined in the
|
||||
// spec subpackage
|
||||
type Source interface {
|
||||
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)
|
||||
}
|
||||
|
||||
type pkUCANSource struct {
|
||||
type pkSource struct {
|
||||
pk crypto.PrivKey
|
||||
issuerDID string
|
||||
signingMethod jwt.SigningMethod
|
||||
|
||||
verifyKey *rsa.PublicKey
|
||||
signKey *rsa.PrivateKey
|
||||
|
||||
@@ -132,17 +132,19 @@ type pkUCANSource struct {
|
||||
store TokenStore
|
||||
}
|
||||
|
||||
// assert pkUCANSource implements tokens at compile time
|
||||
var _ UCANSource = (*pkUCANSource)(nil)
|
||||
// assert pkSource implements tokens at compile time
|
||||
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
|
||||
func NewPrivKeyUCANSource(privKey crypto.PrivKey) (UCANSource, error) {
|
||||
func NewPrivKeySource(privKey crypto.PrivKey) (Source, error) {
|
||||
methodStr := ""
|
||||
keyType := privKey.Type().String()
|
||||
keyType := privKey.Type()
|
||||
switch keyType {
|
||||
case "RSA":
|
||||
case crypto.RSA:
|
||||
methodStr = "RS256"
|
||||
case crypto.Ed25519:
|
||||
methodStr = "EdDSA"
|
||||
default:
|
||||
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 &pkUCANSource{
|
||||
return &pkSource{
|
||||
pk: privKey,
|
||||
signingMethod: signingMethod,
|
||||
verifyKey: verifyKey,
|
||||
@@ -185,11 +187,11 @@ func NewPrivKeyUCANSource(privKey crypto.PrivKey) (UCANSource, error) {
|
||||
}, 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)
|
||||
}
|
||||
|
||||
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) {
|
||||
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
|
||||
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
|
||||
t := jwt.New(a.signingMethod)
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ func init() {
|
||||
}
|
||||
|
||||
func TestPrivKeySource(t *testing.T) {
|
||||
source, err := ucan.NewPrivKeyUCANSource(keyOne)
|
||||
source, err := ucan.NewPrivKeySource(keyOne)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user