feat(didkey): didkey package properly implements v0.7 of spec

Merge pull request #2 from qri-io/proof_chain_verify
This commit is contained in:
Brendan O'Brien
2020-10-15 12:16:23 -04:00
committed by GitHub
7 changed files with 153 additions and 21 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
coverage.txt

99
didkey/key.go Normal file
View 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
View 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())
}
}

View File

@@ -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
View File

@@ -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
)

View File

@@ -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)

View File

@@ -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)
}