fix: support ED25519 keys
This commit is contained in:
@@ -4,6 +4,9 @@
|
|||||||
package didkey
|
package didkey
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -15,10 +18,8 @@ import (
|
|||||||
const (
|
const (
|
||||||
// KeyPrefix indicates a decentralized identifier that uses the key method
|
// KeyPrefix indicates a decentralized identifier that uses the key method
|
||||||
KeyPrefix = "did:key"
|
KeyPrefix = "did:key"
|
||||||
// MulticodecKindRSAPubKey IS NOT A REAL MULTICODEC PREFIX.
|
// MulticodecKindRSAPubKey rsa-x509-pub https://github.com/multiformats/multicodec/pull/226
|
||||||
// pulled from: https://github.com/w3c-ccg/lds-ed25519-2018/issues/3 because
|
MulticodecKindRSAPubKey = 0x1205
|
||||||
// it's only slighly better than picking a random available byte prefix
|
|
||||||
MulticodecKindRSAPubKey = 0x5d
|
|
||||||
// MulticodecKindEd25519PubKey ed25519-pub
|
// MulticodecKindEd25519PubKey ed25519-pub
|
||||||
MulticodecKindEd25519PubKey = 0xed
|
MulticodecKindEd25519PubKey = 0xed
|
||||||
)
|
)
|
||||||
@@ -28,6 +29,16 @@ type ID struct {
|
|||||||
crypto.PubKey
|
crypto.PubKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewID constructs an Identifier from a public key
|
||||||
|
func NewID(pub crypto.PubKey) (ID, error) {
|
||||||
|
switch pub.Type() {
|
||||||
|
case crypto.Ed25519, crypto.RSA:
|
||||||
|
return ID{PubKey: pub}, nil
|
||||||
|
default:
|
||||||
|
return ID{}, fmt.Errorf("unsupported key type: %s", pub.Type())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MulticodecType indicates the type for this multicodec
|
// MulticodecType indicates the type for this multicodec
|
||||||
func (id ID) MulticodecType() uint64 {
|
func (id ID) MulticodecType() uint64 {
|
||||||
switch id.Type() {
|
switch id.Type() {
|
||||||
@@ -61,6 +72,31 @@ func (id ID) String() string {
|
|||||||
return fmt.Sprintf("%s:%s", KeyPrefix, b58BKeyStr)
|
return fmt.Sprintf("%s:%s", KeyPrefix, b58BKeyStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VerifyKey returns the backing implementation for a public key, one of:
|
||||||
|
// *rsa.PublicKey, ed25519.PublicKey
|
||||||
|
func (id ID) VerifyKey() (interface{}, error) {
|
||||||
|
rawPubBytes, err := id.PubKey.Raw()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch id.PubKey.Type() {
|
||||||
|
case crypto.RSA:
|
||||||
|
verifyKeyiface, err := x509.ParsePKIXPublicKey(rawPubBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
verifyKey, ok := verifyKeyiface.(*rsa.PublicKey)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("public key is not an RSA key. got type: %T", verifyKeyiface)
|
||||||
|
}
|
||||||
|
return verifyKey, nil
|
||||||
|
case crypto.Ed25519:
|
||||||
|
return ed25519.PublicKey(rawPubBytes), nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unrecognized Public Key type: %s", id.PubKey.Type())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Parse turns a string into a key method ID
|
// Parse turns a string into a key method ID
|
||||||
func Parse(keystr string) (ID, error) {
|
func Parse(keystr string) (ID, error) {
|
||||||
var id ID
|
var id ID
|
||||||
@@ -70,9 +106,13 @@ func Parse(keystr string) (ID, error) {
|
|||||||
|
|
||||||
keystr = strings.TrimPrefix(keystr, KeyPrefix+":")
|
keystr = strings.TrimPrefix(keystr, KeyPrefix+":")
|
||||||
|
|
||||||
_, data, err := mb.Decode(keystr)
|
enc, data, err := mb.Decode(keystr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return id, err
|
return id, fmt.Errorf("decoding multibase: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if enc != mb.Base58BTC {
|
||||||
|
return id, fmt.Errorf("unexpected multibase encoding: %s", mb.EncodingToStr[enc])
|
||||||
}
|
}
|
||||||
|
|
||||||
keyType, n, err := varint.FromUvarint(data)
|
keyType, n, err := varint.FromUvarint(data)
|
||||||
|
|||||||
@@ -56,9 +56,9 @@ func Example() {
|
|||||||
panicIfError(err)
|
panicIfError(err)
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
// cid of root UCAN: bafkreidhsvhlctwylgeibl2eeapdvbl3qm3mbqcqhxhvy4grmr25ji77hu
|
// cid of root UCAN: bafkreih6guuxohv47s2e366l6jn6stlsukgoerkdvtsni3kxr4jjmkaf3y
|
||||||
// scope of ucan attenuations must be less than it's parent
|
// scope of ucan attenuations must be less than it's parent
|
||||||
// cid of derived UCAN: bafkreifglbwtr27fbzmv3uardlygvggr722fckusfvfyfsonwkroca7efu
|
// cid of derived UCAN: bafkreihpk5474uoolkqrge3yk5uy2s7rarhn5xwxfoiobcy6ye7vfxetgm
|
||||||
}
|
}
|
||||||
|
|
||||||
func panicIfError(err error) {
|
func panicIfError(err error) {
|
||||||
|
|||||||
4
go.mod
4
go.mod
@@ -10,6 +10,6 @@ require (
|
|||||||
github.com/multiformats/go-multibase v0.0.3
|
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
|
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-20210322153248-0c34fe9e7dc2 // indirect
|
||||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect
|
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
12
go.sum
12
go.sum
@@ -82,8 +82,8 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|||||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
@@ -95,6 +95,7 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
|
|||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -105,10 +106,13 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A=
|
||||||
|
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
|||||||
115
token.go
115
token.go
@@ -10,18 +10,18 @@ package ucan
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/ed25519"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt"
|
"github.com/golang-jwt/jwt"
|
||||||
"github.com/ipfs/go-cid"
|
"github.com/ipfs/go-cid"
|
||||||
"github.com/libp2p/go-libp2p-core/crypto"
|
"github.com/libp2p/go-libp2p-core/crypto"
|
||||||
mh "github.com/multiformats/go-multihash"
|
mh "github.com/multiformats/go-multihash"
|
||||||
|
"github.com/qri-io/ucan/didkey"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrInvalidToken indicates an access token is invalid
|
// ErrInvalidToken indicates an access token is invalid
|
||||||
@@ -125,12 +125,8 @@ type pkSource struct {
|
|||||||
issuerDID string
|
issuerDID string
|
||||||
signingMethod jwt.SigningMethod
|
signingMethod jwt.SigningMethod
|
||||||
|
|
||||||
verifyKey *rsa.PublicKey
|
verifyKey interface{} // one of: *rsa.PublicKey, *edsa.PublicKey
|
||||||
signKey *rsa.PrivateKey
|
signKey interface{} // one of: *rsa.PrivateKey,
|
||||||
|
|
||||||
ap AttenuationConstructorFunc
|
|
||||||
resolver CIDBytesResolver
|
|
||||||
store TokenStore
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// assert pkSource implements tokens at compile time
|
// assert pkSource implements tokens at compile time
|
||||||
@@ -139,41 +135,51 @@ var _ Source = (*pkSource)(nil)
|
|||||||
// NewPrivKeySource 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 NewPrivKeySource(privKey crypto.PrivKey) (Source, error) {
|
func NewPrivKeySource(privKey crypto.PrivKey) (Source, error) {
|
||||||
methodStr := ""
|
rawPrivBytes, err := privKey.Raw()
|
||||||
keyType := privKey.Type()
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting private key bytes: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
methodStr = ""
|
||||||
|
keyType = privKey.Type()
|
||||||
|
signKey interface{}
|
||||||
|
verifyKey interface{}
|
||||||
|
)
|
||||||
|
|
||||||
switch keyType {
|
switch keyType {
|
||||||
case crypto.RSA:
|
case crypto.RSA:
|
||||||
methodStr = "RS256"
|
methodStr = "RS256"
|
||||||
|
// TODO(b5) - detect if key is encoded as PEM block, here we're assuming it is
|
||||||
|
signKey, err = x509.ParsePKCS1PrivateKey(rawPrivBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rawPubBytes, err := privKey.GetPublic().Raw()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting raw public key bytes: %w", err)
|
||||||
|
}
|
||||||
|
verifyKeyiface, err := x509.ParsePKIXPublicKey(rawPubBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing public key bytes: %w", err)
|
||||||
|
}
|
||||||
|
var ok bool
|
||||||
|
verifyKey, ok = verifyKeyiface.(*rsa.PublicKey)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("public key is not an RSA key. got type: %T", verifyKeyiface)
|
||||||
|
}
|
||||||
case crypto.Ed25519:
|
case crypto.Ed25519:
|
||||||
methodStr = "EdDSA"
|
methodStr = "EdDSA"
|
||||||
|
signKey = ed25519.PrivateKey(rawPrivBytes)
|
||||||
|
rawPubBytes, err := privKey.GetPublic().Raw()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting raw public key bytes: %w", err)
|
||||||
|
}
|
||||||
|
verifyKey = ed25519.PublicKey(rawPubBytes)
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
signingMethod := jwt.GetSigningMethod(methodStr)
|
|
||||||
|
|
||||||
rawPrivBytes, err := privKey.Raw()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
signKey, err := x509.ParsePKCS1PrivateKey(rawPrivBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rawPubBytes, err := privKey.GetPublic().Raw()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
verifyKeyiface, err := x509.ParsePKIXPublicKey(rawPubBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
verifyKey, ok := verifyKeyiface.(*rsa.PublicKey)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("public key is not an RSA key. got type: %T", verifyKeyiface)
|
|
||||||
}
|
|
||||||
|
|
||||||
issuerDID, err := DIDStringFromPublicKey(privKey.GetPublic())
|
issuerDID, err := DIDStringFromPublicKey(privKey.GetPublic())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -181,7 +187,7 @@ func NewPrivKeySource(privKey crypto.PrivKey) (Source, error) {
|
|||||||
|
|
||||||
return &pkSource{
|
return &pkSource{
|
||||||
pk: privKey,
|
pk: privKey,
|
||||||
signingMethod: signingMethod,
|
signingMethod: jwt.GetSigningMethod(methodStr),
|
||||||
verifyKey: verifyKey,
|
verifyKey: verifyKey,
|
||||||
signKey: signKey,
|
signKey: signKey,
|
||||||
issuerDID: issuerDID,
|
issuerDID: issuerDID,
|
||||||
@@ -253,16 +259,16 @@ func (a *pkSource) newToken(subjectDID string, prf []Proof, att Attenuations, fc
|
|||||||
// DIDPubKeyResolver turns did:key Decentralized IDentifiers into a public key,
|
// DIDPubKeyResolver turns did:key Decentralized IDentifiers into a public key,
|
||||||
// possibly using a network request
|
// possibly using a network request
|
||||||
type DIDPubKeyResolver interface {
|
type DIDPubKeyResolver interface {
|
||||||
ResolveDIDKey(ctx context.Context, did string) (crypto.PubKey, error)
|
ResolveDIDKey(ctx context.Context, did string) (didkey.ID, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DIDStringFromPublicKey creates a did:key identifier string from a public key
|
// DIDStringFromPublicKey creates a did:key identifier string from a public key
|
||||||
func DIDStringFromPublicKey(pub crypto.PubKey) (string, error) {
|
func DIDStringFromPublicKey(pub crypto.PubKey) (string, error) {
|
||||||
rawPubBytes, err := pub.Raw()
|
id, err := didkey.NewID(pub)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("did:key:%s", base64.URLEncoding.EncodeToString(rawPubBytes)), nil
|
return id.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// StringDIDPubKeyResolver implements the DIDPubKeyResolver interface without
|
// StringDIDPubKeyResolver implements the DIDPubKeyResolver interface without
|
||||||
@@ -271,18 +277,8 @@ func DIDStringFromPublicKey(pub crypto.PubKey) (string, error) {
|
|||||||
type StringDIDPubKeyResolver struct{}
|
type StringDIDPubKeyResolver struct{}
|
||||||
|
|
||||||
// ResolveDIDKey extracts a public key from a did:key string
|
// ResolveDIDKey extracts a public key from a did:key string
|
||||||
func (StringDIDPubKeyResolver) ResolveDIDKey(ctx context.Context, didStr string) (crypto.PubKey, error) {
|
func (StringDIDPubKeyResolver) ResolveDIDKey(ctx context.Context, didStr string) (didkey.ID, error) {
|
||||||
// id, err := did.Parse(didStr)
|
return didkey.Parse(didStr)
|
||||||
// if err != nil {
|
|
||||||
// return nil, fmt.Errorf("invalid DID: %w", err)
|
|
||||||
// }
|
|
||||||
|
|
||||||
data, err := base64.URLEncoding.DecodeString(strings.TrimPrefix(didStr, "did:key:"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return crypto.UnmarshalRsaPublicKey(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TokenParser parses a raw string into a Token
|
// TokenParser parses a raw string into a Token
|
||||||
@@ -309,7 +305,7 @@ func (p *TokenParser) ParseAndVerify(ctx context.Context, raw string) (*Token, e
|
|||||||
func (p *TokenParser) parseAndVerify(ctx context.Context, raw string, child *Token) (*Token, error) {
|
func (p *TokenParser) parseAndVerify(ctx context.Context, raw string, child *Token) (*Token, error) {
|
||||||
tok, err := jwt.Parse(raw, p.matchVerifyKeyFunc(ctx))
|
tok, err := jwt.Parse(raw, p.matchVerifyKeyFunc(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("parsing UCAN: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mc, ok := tok.Claims.(jwt.MapClaims)
|
mc, ok := tok.Claims.(jwt.MapClaims)
|
||||||
@@ -366,24 +362,11 @@ func (p *TokenParser) matchVerifyKeyFunc(ctx context.Context) func(tok *jwt.Toke
|
|||||||
return nil, fmt.Errorf(`"iss" claims key is required`)
|
return nil, fmt.Errorf(`"iss" claims key is required`)
|
||||||
}
|
}
|
||||||
|
|
||||||
pubKey, err := p.didr.ResolveDIDKey(ctx, iss)
|
id, err := p.didr.ResolveDIDKey(ctx, iss)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rawPubBytes, err := pubKey.Raw()
|
return id.VerifyKey()
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
verifyKeyiface, err := x509.ParsePKIXPublicKey(rawPubBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
verifyKey, ok := verifyKeyiface.(*rsa.PublicKey)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("public key is not an RSA key. got type: %T", verifyKeyiface)
|
|
||||||
}
|
|
||||||
|
|
||||||
return verifyKey, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func TestPrivKeySource(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
expect := `eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInVjdiI6IjAuNC4wIn0.eyJpc3MiOiJkaWQ6a2V5Ok1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBb2FkUjRtY1U3QzBBbWg1bHRfM0hObVEyYVlDOEotYU5mNTJUNEtrLTBzbHh6LVc1LXhrREJ0NUR4RUZuSmVKNGJTMV9ZWkt3UkxKQjYzU0phcWZjMXhUTUFYMnJmcW44d3NwUmd2MEFReGU4RV9icGkzZTUyNnU2UU1VRjdYbDRKN2JkbVlZT0lCUDVCSk83eU1pX2pfU3FWaVdmOG82Y3BJTEF3dXpUNTY2X0ttUWFOclM5QmVNUHQ5NTJZUk1lejZlMFoycXR0aVRQS3hmalJ3b0VwRklldDVhZTFZY0p2VDBLQnJiZEYwNXhDc2F6RUoxSm52eUlSamNiUE9FYVljUjNPZnAxdW8ySTRKdVczQ2FKeHNqMU8yNnZyLWRUSzlqcGVFVTl5X1dUU1lNOUVsazBwZ0xZZ1M4ZHE4aTYwNDVnejByemU4QzV2YkZoSFZwa1ZRSURBUUFCIiwic3ViIjoiZGlkOmtleTpNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQW9hZFI0bWNVN0MwQW1oNWx0XzNITm1RMmFZQzhKLWFOZjUyVDRLay0wc2x4ei1XNS14a0RCdDVEeEVGbkplSjRiUzFfWVpLd1JMSkI2M1NKYXFmYzF4VE1BWDJyZnFuOHdzcFJndjBBUXhlOEVfYnBpM2U1MjZ1NlFNVUY3WGw0SjdiZG1ZWU9JQlA1QkpPN3lNaV9qX1NxVmlXZjhvNmNwSUxBd3V6VDU2Nl9LbVFhTnJTOUJlTVB0OTUyWVJNZXo2ZTBaMnF0dGlUUEt4ZmpSd29FcEZJZXQ1YWUxWWNKdlQwS0JyYmRGMDV4Q3NhekVKMUpudnlJUmpjYlBPRWFZY1IzT2ZwMXVvMkk0SnVXM0NhSnhzajFPMjZ2ci1kVEs5anBlRVU5eV9XVFNZTTlFbGswcGdMWWdTOGRxOGk2MDQ1Z3owcnplOEM1dmJGaEhWcGtWUUlEQVFBQiIsImF0dCI6W3siYXBpIjoiKiIsImNhcCI6IlNVUEVSX1VTRVIifSx7ImNhcCI6IlNVUEVSX1VTRVIiLCJkYXRhc2V0IjoiYjU6d29ybGRfYmFua19wb3B1bGF0aW9uOioifV19.Z32-i-pGAtPRsG0JW4ZS8-c17x3mX3kFrmZ0BYhyWk2JH4QMwXFRtkUl8xVQtrC3JigeQeaDiz-WTUSFqJIs5dunL1Xf_SXqq8SZ7NCh6u6OEo2L1BnQkwdO8kDsFoiF42byWDBwzHRog0N-pRXgMhlo8si6Pek4KAZokQ5F-8FuLb3MXXxc9-FnhGRsKgGt_bNWS322h5gXCaXJAzbdAHwGSlORCCJI4CrbWUHs03i4viun2Ht01JO-p4ySlut6YyQ_vW4NGNSAAXGeR-ggkB0B6TGgt695CxX1zgQKV7X6JZx-NF_J-OXCIWngCfr6VdRv1_ADce9s1ODEm2N7eA`
|
expect := `eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInVjdiI6IjAuNC4wIn0.eyJpc3MiOiJkaWQ6a2V5OnoyTUd3NGdrODRVU290YVdmNEFrSjgzRGNucmZnR2FjZUY4NktRWFJZTWZRN3hxblVGRVREN3RGNGprTkd2QUV0dlZ6Rm5RdTkydEZ3ZkNyb2ZWZ0c2WnkyZVlKZURSckVVZ1c5WXZCazJXSHVUUnJFZ0FTZnBxR2tKUkdnR3FiNmNidjFxR2N2Nm1FSFF5YlR5M2JOeXFxd3VRWXA3c3BuSlFObVJhUGk0Z3F1czVEWnVOc2NRZjFSMXhCdVN6WHk1YnNCaFlSZzdFcDZmNkJad0U2cHZCamt5ellqWERVYlF2a0VpeG0zcHR3RTRnZ2VZU21oUXFxbU1ac1lwcG1lY1VFMjhuTTdFekx2Q3hRZEZ1QndXZ2U3QURVYzdxVGYxeXNpUzl1YXdOTnA1aER2aHl2cXRDaWg3a3FvTHVzTGVnd2pHZTJTcDhDcUZmdUNRNWgxdHh4WHozdEdtRGZEUDE3Nm15R1htc3R0eDV5MjVTOXpwejg1ZEc4WnRrRnZ4bXNjOFltaXZlRUMycWFkY2FrWEoiLCJzdWIiOiJkaWQ6a2V5OnoyTUd3NGdrODRVU290YVdmNEFrSjgzRGNucmZnR2FjZUY4NktRWFJZTWZRN3hxblVGRVREN3RGNGprTkd2QUV0dlZ6Rm5RdTkydEZ3ZkNyb2ZWZ0c2WnkyZVlKZURSckVVZ1c5WXZCazJXSHVUUnJFZ0FTZnBxR2tKUkdnR3FiNmNidjFxR2N2Nm1FSFF5YlR5M2JOeXFxd3VRWXA3c3BuSlFObVJhUGk0Z3F1czVEWnVOc2NRZjFSMXhCdVN6WHk1YnNCaFlSZzdFcDZmNkJad0U2cHZCamt5ellqWERVYlF2a0VpeG0zcHR3RTRnZ2VZU21oUXFxbU1ac1lwcG1lY1VFMjhuTTdFekx2Q3hRZEZ1QndXZ2U3QURVYzdxVGYxeXNpUzl1YXdOTnA1aER2aHl2cXRDaWg3a3FvTHVzTGVnd2pHZTJTcDhDcUZmdUNRNWgxdHh4WHozdEdtRGZEUDE3Nm15R1htc3R0eDV5MjVTOXpwejg1ZEc4WnRrRnZ4bXNjOFltaXZlRUMycWFkY2FrWEoiLCJhdHQiOlt7ImFwaSI6IioiLCJjYXAiOiJTVVBFUl9VU0VSIn0seyJjYXAiOiJTVVBFUl9VU0VSIiwiZGF0YXNldCI6ImI1OndvcmxkX2JhbmtfcG9wdWxhdGlvbjoqIn1dfQ.SwctK9sJpUtgyOSKN3BMI1BfNT_X6xxy5Oy6aTCvGvdj-vJIxsioz2Po3TlFxpmo9uSJ_nGoUTW7fr7Hc5m8qqrUFnPrtPXOnSNluExh9sh0aXY1ACGNoDknhbnsU_5nkEhDSTOMQ0Y0MoqvkV_Ti-7ZjISEuGukvl4-FFXZVGl6zY7ii8-J3RQu60cMbR4xK-VnlXcZUnhREr8JQtPKfBnmjIvbPclnaOdXLSACgEPXsAX4gekYAb2HR8CduHmaFWqAq_Th-QbqgQVmqUw-VIwtwuxxfxpc-PTNV4L0eDH5jO9i7jVzU3KaWM8NzQ2vux_zhscjBQFR2SmpQuGO7A`
|
||||||
if expect != root.Raw {
|
if expect != root.Raw {
|
||||||
t.Errorf("token mismatch. expected: %q.\ngot: %q", expect, root.Raw)
|
t.Errorf("token mismatch. expected: %q.\ngot: %q", expect, root.Raw)
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,7 @@ func TestPrivKeySource(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cidStr := mustCidString(t, derivedToken)
|
cidStr := mustCidString(t, derivedToken)
|
||||||
expectCID := "bafkreifglbwtr27fbzmv3uardlygvggr722fckusfvfyfsonwkroca7efu"
|
expectCID := "bafkreihpk5474uoolkqrge3yk5uy2s7rarhn5xwxfoiobcy6ye7vfxetgm"
|
||||||
|
|
||||||
if expectCID != cidStr {
|
if expectCID != cidStr {
|
||||||
t.Errorf("derived token CID mismatch. expected: %q.\ngot: %q", expectCID, cidStr)
|
t.Errorf("derived token CID mismatch. expected: %q.\ngot: %q", expectCID, cidStr)
|
||||||
@@ -90,6 +90,35 @@ func TestPrivKeySource(t *testing.T) {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestED25519PrivKeySource(t *testing.T) {
|
||||||
|
keyOne, _, err := crypto.GenerateKeyPair(crypto.Ed25519, 123)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
source, err := ucan.NewPrivKeySource(keyOne)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
subjectDID, err := ucan.DIDStringFromPublicKey(keyOne.GetPublic())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
zero := time.Time{}
|
||||||
|
|
||||||
|
// create a root UCAN
|
||||||
|
origin, err := source.NewOriginToken(subjectDID, nil, nil, zero, zero)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = origin.CID(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTokenSource(t *testing.T) {
|
func TestTokenSource(t *testing.T) {
|
||||||
// ucan_spec.AssertTokenSourceSpec(t, func(ctx context.Context) ucan.TokenSource {
|
// ucan_spec.AssertTokenSourceSpec(t, func(ctx context.Context) ucan.TokenSource {
|
||||||
// source, err := ucan.NewPrivKeyTokenSource(peerInfo.PrivKey)
|
// source, err := ucan.NewPrivKeyTokenSource(peerInfo.PrivKey)
|
||||||
@@ -101,7 +130,7 @@ func TestTokenSource(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTokenParse(t *testing.T) {
|
func TestTokenParse(t *testing.T) {
|
||||||
raw := `eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInVjdiI6IjAuNC4wIn0.eyJpc3MiOiJkaWQ6a2V5Ok1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBb2FkUjRtY1U3QzBBbWg1bHRfM0hObVEyYVlDOEotYU5mNTJUNEtrLTBzbHh6LVc1LXhrREJ0NUR4RUZuSmVKNGJTMV9ZWkt3UkxKQjYzU0phcWZjMXhUTUFYMnJmcW44d3NwUmd2MEFReGU4RV9icGkzZTUyNnU2UU1VRjdYbDRKN2JkbVlZT0lCUDVCSk83eU1pX2pfU3FWaVdmOG82Y3BJTEF3dXpUNTY2X0ttUWFOclM5QmVNUHQ5NTJZUk1lejZlMFoycXR0aVRQS3hmalJ3b0VwRklldDVhZTFZY0p2VDBLQnJiZEYwNXhDc2F6RUoxSm52eUlSamNiUE9FYVljUjNPZnAxdW8ySTRKdVczQ2FKeHNqMU8yNnZyLWRUSzlqcGVFVTl5X1dUU1lNOUVsazBwZ0xZZ1M4ZHE4aTYwNDVnejByemU4QzV2YkZoSFZwa1ZRSURBUUFCIiwic3ViIjoiZGlkOmtleTpNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQW9hZFI0bWNVN0MwQW1oNWx0XzNITm1RMmFZQzhKLWFOZjUyVDRLay0wc2x4ei1XNS14a0RCdDVEeEVGbkplSjRiUzFfWVpLd1JMSkI2M1NKYXFmYzF4VE1BWDJyZnFuOHdzcFJndjBBUXhlOEVfYnBpM2U1MjZ1NlFNVUY3WGw0SjdiZG1ZWU9JQlA1QkpPN3lNaV9qX1NxVmlXZjhvNmNwSUxBd3V6VDU2Nl9LbVFhTnJTOUJlTVB0OTUyWVJNZXo2ZTBaMnF0dGlUUEt4ZmpSd29FcEZJZXQ1YWUxWWNKdlQwS0JyYmRGMDV4Q3NhekVKMUpudnlJUmpjYlBPRWFZY1IzT2ZwMXVvMkk0SnVXM0NhSnhzajFPMjZ2ci1kVEs5anBlRVU5eV9XVFNZTTlFbGswcGdMWWdTOGRxOGk2MDQ1Z3owcnplOEM1dmJGaEhWcGtWUUlEQVFBQiIsImF0dCI6W3siYXBpIjoiKiIsImNhcCI6IlNVUEVSX1VTRVIifSx7ImNhcCI6IlNVUEVSX1VTRVIiLCJkYXRhc2V0IjoiYjU6d29ybGRfYmFua19wb3B1bGF0aW9uOioifV19.Z32-i-pGAtPRsG0JW4ZS8-c17x3mX3kFrmZ0BYhyWk2JH4QMwXFRtkUl8xVQtrC3JigeQeaDiz-WTUSFqJIs5dunL1Xf_SXqq8SZ7NCh6u6OEo2L1BnQkwdO8kDsFoiF42byWDBwzHRog0N-pRXgMhlo8si6Pek4KAZokQ5F-8FuLb3MXXxc9-FnhGRsKgGt_bNWS322h5gXCaXJAzbdAHwGSlORCCJI4CrbWUHs03i4viun2Ht01JO-p4ySlut6YyQ_vW4NGNSAAXGeR-ggkB0B6TGgt695CxX1zgQKV7X6JZx-NF_J-OXCIWngCfr6VdRv1_ADce9s1ODEm2N7eA`
|
raw := `eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInVjdiI6IjAuNC4wIn0.eyJpc3MiOiJkaWQ6a2V5OnoyTUd3NGdrODRVU290YVdmNEFrSjgzRGNucmZnR2FjZUY4NktRWFJZTWZRN3hxblVGRVREN3RGNGprTkd2QUV0dlZ6Rm5RdTkydEZ3ZkNyb2ZWZ0c2WnkyZVlKZURSckVVZ1c5WXZCazJXSHVUUnJFZ0FTZnBxR2tKUkdnR3FiNmNidjFxR2N2Nm1FSFF5YlR5M2JOeXFxd3VRWXA3c3BuSlFObVJhUGk0Z3F1czVEWnVOc2NRZjFSMXhCdVN6WHk1YnNCaFlSZzdFcDZmNkJad0U2cHZCamt5ellqWERVYlF2a0VpeG0zcHR3RTRnZ2VZU21oUXFxbU1ac1lwcG1lY1VFMjhuTTdFekx2Q3hRZEZ1QndXZ2U3QURVYzdxVGYxeXNpUzl1YXdOTnA1aER2aHl2cXRDaWg3a3FvTHVzTGVnd2pHZTJTcDhDcUZmdUNRNWgxdHh4WHozdEdtRGZEUDE3Nm15R1htc3R0eDV5MjVTOXpwejg1ZEc4WnRrRnZ4bXNjOFltaXZlRUMycWFkY2FrWEoiLCJzdWIiOiJkaWQ6a2V5OnoyTUd3NGdrODRVU290YVdmNEFrSjgzRGNucmZnR2FjZUY4NktRWFJZTWZRN3hxblVGRVREN3RGNGprTkd2QUV0dlZ6Rm5RdTkydEZ3ZkNyb2ZWZ0c2WnkyZVlKZURSckVVZ1c5WXZCazJXSHVUUnJFZ0FTZnBxR2tKUkdnR3FiNmNidjFxR2N2Nm1FSFF5YlR5M2JOeXFxd3VRWXA3c3BuSlFObVJhUGk0Z3F1czVEWnVOc2NRZjFSMXhCdVN6WHk1YnNCaFlSZzdFcDZmNkJad0U2cHZCamt5ellqWERVYlF2a0VpeG0zcHR3RTRnZ2VZU21oUXFxbU1ac1lwcG1lY1VFMjhuTTdFekx2Q3hRZEZ1QndXZ2U3QURVYzdxVGYxeXNpUzl1YXdOTnA1aER2aHl2cXRDaWg3a3FvTHVzTGVnd2pHZTJTcDhDcUZmdUNRNWgxdHh4WHozdEdtRGZEUDE3Nm15R1htc3R0eDV5MjVTOXpwejg1ZEc4WnRrRnZ4bXNjOFltaXZlRUMycWFkY2FrWEoiLCJhdHQiOlt7ImFwaSI6IioiLCJjYXAiOiJTVVBFUl9VU0VSIn0seyJjYXAiOiJTVVBFUl9VU0VSIiwiZGF0YXNldCI6ImI1OndvcmxkX2JhbmtfcG9wdWxhdGlvbjoqIn1dfQ.SwctK9sJpUtgyOSKN3BMI1BfNT_X6xxy5Oy6aTCvGvdj-vJIxsioz2Po3TlFxpmo9uSJ_nGoUTW7fr7Hc5m8qqrUFnPrtPXOnSNluExh9sh0aXY1ACGNoDknhbnsU_5nkEhDSTOMQ0Y0MoqvkV_Ti-7ZjISEuGukvl4-FFXZVGl6zY7ii8-J3RQu60cMbR4xK-VnlXcZUnhREr8JQtPKfBnmjIvbPclnaOdXLSACgEPXsAX4gekYAb2HR8CduHmaFWqAq_Th-QbqgQVmqUw-VIwtwuxxfxpc-PTNV4L0eDH5jO9i7jVzU3KaWM8NzQ2vux_zhscjBQFR2SmpQuGO7A`
|
||||||
|
|
||||||
caps := ucan.NewNestedCapabilities("SUPER_USER", "OVERWRITE", "SOFT_DELETE", "REVISE", "CREATE")
|
caps := ucan.NewNestedCapabilities("SUPER_USER", "OVERWRITE", "SOFT_DELETE", "REVISE", "CREATE")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user