@@ -82,7 +82,7 @@ func FromSealedReader(r io.Reader) (*Token, error) {
|
|||||||
return tkn, nil
|
return tkn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode marshals a View to the format specified by the provided
|
// Encode marshals a Token to the format specified by the provided
|
||||||
// codec.Encoder.
|
// codec.Encoder.
|
||||||
func (t *Token) Encode(privKey crypto.PrivKey, encFn codec.Encoder) ([]byte, error) {
|
func (t *Token) Encode(privKey crypto.PrivKey, encFn codec.Encoder) ([]byte, error) {
|
||||||
node, err := t.toIPLD(privKey)
|
node, err := t.toIPLD(privKey)
|
||||||
@@ -103,7 +103,7 @@ func (t *Token) EncodeWriter(w io.Writer, privKey crypto.PrivKey, encFn codec.En
|
|||||||
return ipld.EncodeStreaming(w, node, encFn)
|
return ipld.EncodeStreaming(w, node, encFn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToDagCbor marshals the View to the DAG-CBOR format.
|
// ToDagCbor marshals the Token to the DAG-CBOR format.
|
||||||
func (t *Token) ToDagCbor(privKey crypto.PrivKey) ([]byte, error) {
|
func (t *Token) ToDagCbor(privKey crypto.PrivKey) ([]byte, error) {
|
||||||
return t.Encode(privKey, dagcbor.Encode)
|
return t.Encode(privKey, dagcbor.Encode)
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ func (t *Token) ToDagCborWriter(w io.Writer, privKey crypto.PrivKey) error {
|
|||||||
return t.EncodeWriter(w, privKey, dagcbor.Encode)
|
return t.EncodeWriter(w, privKey, dagcbor.Encode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToDagJson marshals the View to the DAG-JSON format.
|
// ToDagJson marshals the Token to the DAG-JSON format.
|
||||||
func (t *Token) ToDagJson(privKey crypto.PrivKey) ([]byte, error) {
|
func (t *Token) ToDagJson(privKey crypto.PrivKey) ([]byte, error) {
|
||||||
return t.Encode(privKey, dagjson.Encode)
|
return t.Encode(privKey, dagjson.Encode)
|
||||||
}
|
}
|
||||||
@@ -124,16 +124,16 @@ func (t *Token) ToDagJsonWriter(w io.Writer, privKey crypto.PrivKey) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Decode unmarshals the input data using the format specified by the
|
// Decode unmarshals the input data using the format specified by the
|
||||||
// provided codec.Decoder into a View.
|
// provided codec.Decoder into a Token.
|
||||||
//
|
//
|
||||||
// An error is returned if the conversion fails, or if the resulting
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
// View is invalid.
|
// Token is invalid.
|
||||||
func Decode(b []byte, decFn codec.Decoder) (*Token, error) {
|
func Decode(b []byte, decFn codec.Decoder) (*Token, error) {
|
||||||
node, err := ipld.Decode(b, decFn)
|
node, err := ipld.Decode(b, decFn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return fromIPLD(node)
|
return FromIPLD(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeReader is the same as Decode, but accept an io.Reader.
|
// DecodeReader is the same as Decode, but accept an io.Reader.
|
||||||
@@ -142,13 +142,13 @@ func DecodeReader(r io.Reader, decFn codec.Decoder) (*Token, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return fromIPLD(node)
|
return FromIPLD(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromDagCbor unmarshals the input data into a View.
|
// FromDagCbor unmarshals the input data into a Token.
|
||||||
//
|
//
|
||||||
// An error is returned if the conversion fails, or if the resulting
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
// View is invalid.
|
// Token is invalid.
|
||||||
func FromDagCbor(data []byte) (*Token, error) {
|
func FromDagCbor(data []byte) (*Token, error) {
|
||||||
pay, err := envelope.FromDagCbor[*tokenPayloadModel](data)
|
pay, err := envelope.FromDagCbor[*tokenPayloadModel](data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -168,10 +168,10 @@ func FromDagCborReader(r io.Reader) (*Token, error) {
|
|||||||
return DecodeReader(r, dagcbor.Decode)
|
return DecodeReader(r, dagcbor.Decode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromDagJson unmarshals the input data into a View.
|
// FromDagJson unmarshals the input data into a Token.
|
||||||
//
|
//
|
||||||
// An error is returned if the conversion fails, or if the resulting
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
// View is invalid.
|
// Token is invalid.
|
||||||
func FromDagJson(data []byte) (*Token, error) {
|
func FromDagJson(data []byte) (*Token, error) {
|
||||||
return Decode(data, dagjson.Decode)
|
return Decode(data, dagjson.Decode)
|
||||||
}
|
}
|
||||||
@@ -181,7 +181,8 @@ func FromDagJsonReader(r io.Reader) (*Token, error) {
|
|||||||
return DecodeReader(r, dagjson.Decode)
|
return DecodeReader(r, dagjson.Decode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fromIPLD(node datamodel.Node) (*Token, error) {
|
// FromIPLD decode the given IPLD representation into a Token.
|
||||||
|
func FromIPLD(node datamodel.Node) (*Token, error) {
|
||||||
pay, err := envelope.FromIPLD[*tokenPayloadModel](node)
|
pay, err := envelope.FromIPLD[*tokenPayloadModel](node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -1,105 +1,19 @@
|
|||||||
package tokens
|
package tokens
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/ipld/go-ipld-prime"
|
|
||||||
"github.com/ipld/go-ipld-prime/codec"
|
|
||||||
"github.com/ipld/go-ipld-prime/codec/cbor"
|
|
||||||
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
|
||||||
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
|
||||||
"github.com/ipld/go-ipld-prime/codec/json"
|
|
||||||
"github.com/ipld/go-ipld-prime/codec/raw"
|
|
||||||
"github.com/ipld/go-ipld-prime/datamodel"
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
|
|
||||||
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EnvelopeInfo describes the fields of an Envelope enclosing a UCAN token.
|
type Info = envelope.Info
|
||||||
type EnvelopeInfo struct {
|
|
||||||
Signature []byte
|
// Inspect inspects the given token IPLD representation and extract some envelope facts.
|
||||||
Tag string
|
func Inspect(node datamodel.Node) (Info, error) {
|
||||||
VarsigHeader []byte
|
return envelope.Inspect(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
// InspectEnvelope accepts arbitrary data and attempts to decode it as a
|
// FindTag inspect the given token IPLD representation and extract the token tag.
|
||||||
// UCAN token's Envelope.
|
func FindTag(node datamodel.Node) (string, error) {
|
||||||
func Inspect(data []byte) (EnvelopeInfo, error) {
|
return envelope.FindTag(node)
|
||||||
undef := EnvelopeInfo{}
|
|
||||||
|
|
||||||
node, err := decodeAny(data)
|
|
||||||
if err != nil {
|
|
||||||
return undef, err
|
|
||||||
}
|
|
||||||
|
|
||||||
info, err := envelope.Inspect(node)
|
|
||||||
if err != nil {
|
|
||||||
return undef, err
|
|
||||||
}
|
|
||||||
|
|
||||||
iterator := info.SigPayloadNode.MapIterator()
|
|
||||||
foundVarsigHeader := false
|
|
||||||
foundTokenPayload := false
|
|
||||||
tag := ""
|
|
||||||
i := 0
|
|
||||||
|
|
||||||
for !iterator.Done() {
|
|
||||||
k, _, err := iterator.Next()
|
|
||||||
if err != nil {
|
|
||||||
return undef, err
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := k.AsString()
|
|
||||||
if err != nil {
|
|
||||||
return undef, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if key == envelope.VarsigHeaderKey {
|
|
||||||
foundVarsigHeader = true
|
|
||||||
i++
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(key, envelope.UCANTagPrefix) {
|
|
||||||
tag = key
|
|
||||||
foundTokenPayload = true
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if i != 2 {
|
|
||||||
return undef, fmt.Errorf("expected two and only two fields in SigPayload: %d", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !foundVarsigHeader {
|
|
||||||
return undef, errors.New("failed to find VarsigHeader field")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !foundTokenPayload {
|
|
||||||
return undef, errors.New("failed to find TokenPayload field")
|
|
||||||
}
|
|
||||||
|
|
||||||
return EnvelopeInfo{
|
|
||||||
Signature: info.Signature,
|
|
||||||
Tag: tag,
|
|
||||||
VarsigHeader: info.VarsigHeader,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeAny(data []byte) (datamodel.Node, error) {
|
|
||||||
for _, decoder := range []codec.Decoder{
|
|
||||||
dagcbor.Decode,
|
|
||||||
dagjson.Decode,
|
|
||||||
cbor.Decode,
|
|
||||||
json.Decode,
|
|
||||||
raw.Decode,
|
|
||||||
} {
|
|
||||||
if node, err := ipld.Decode(data, decoder); err == nil {
|
|
||||||
return node, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New("failed to decode (any) the provided data")
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
package tokens_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/ucan-wg/go-ucan/tokens"
|
|
||||||
"gotest.tools/v3/golden"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInspect(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
for _, filename := range []string{
|
|
||||||
"example.dagcbor",
|
|
||||||
"example.dagjson",
|
|
||||||
} {
|
|
||||||
t.Run(filename, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
data := golden.Get(t, filename)
|
|
||||||
expSig, err := base64.RawStdEncoding.DecodeString("fPqfwL3iFpbw9SvBiq0DIbUurv9o6c36R08tC/yslGrJcwV51ghzWahxdetpEf6T5LCszXX9I/K8khvnmAxjAg")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
info, err := tokens.Inspect(data)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, expSig, info.Signature)
|
|
||||||
assert.Equal(t, "ucan/example@v1.0.0-rc.1", info.Tag)
|
|
||||||
assert.Equal(t, []byte{0x34, 0xed, 0x1, 0x71}, info.VarsigHeader)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,8 +16,9 @@ import (
|
|||||||
"github.com/ipld/go-ipld-prime/schema"
|
"github.com/ipld/go-ipld-prime/schema"
|
||||||
"github.com/libp2p/go-libp2p/core/crypto"
|
"github.com/libp2p/go-libp2p/core/crypto"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
|
||||||
"gotest.tools/v3/golden"
|
"gotest.tools/v3/golden"
|
||||||
|
|
||||||
|
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ package envelope
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/ipld/go-ipld-prime"
|
"github.com/ipld/go-ipld-prime"
|
||||||
"github.com/ipld/go-ipld-prime/codec"
|
"github.com/ipld/go-ipld-prime/codec"
|
||||||
@@ -92,19 +94,7 @@ func DecodeReader[T Tokener](r io.Reader, decFn codec.Decoder) (T, error) {
|
|||||||
// An error is returned if the conversion fails, or if the resulting
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
// Tokener is invalid.
|
// Tokener is invalid.
|
||||||
func FromDagCbor[T Tokener](b []byte) (T, error) {
|
func FromDagCbor[T Tokener](b []byte) (T, error) {
|
||||||
undef := *new(T)
|
return Decode[T](b, dagcbor.Decode)
|
||||||
|
|
||||||
node, err := ipld.Decode(b, dagcbor.Decode)
|
|
||||||
if err != nil {
|
|
||||||
return undef, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tkn, err := fromIPLD[T](node)
|
|
||||||
if err != nil {
|
|
||||||
return undef, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tkn, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromDagCborReader is the same as FromDagCbor, but accept an io.Reader.
|
// FromDagCborReader is the same as FromDagCbor, but accept an io.Reader.
|
||||||
@@ -130,93 +120,81 @@ func FromDagJsonReader[T Tokener](r io.Reader) (T, error) {
|
|||||||
// An error is returned if the conversion fails, or if the resulting
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
// Tokener is invalid.
|
// Tokener is invalid.
|
||||||
func FromIPLD[T Tokener](node datamodel.Node) (T, error) {
|
func FromIPLD[T Tokener](node datamodel.Node) (T, error) {
|
||||||
undef := *new(T)
|
zero := *new(T)
|
||||||
|
|
||||||
tkn, err := fromIPLD[T](node)
|
|
||||||
if err != nil {
|
|
||||||
return undef, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tkn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fromIPLD[T Tokener](node datamodel.Node) (T, error) {
|
|
||||||
undef := *new(T)
|
|
||||||
|
|
||||||
info, err := Inspect(node)
|
info, err := Inspect(node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, err
|
return zero, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenPayloadNode, err := info.SigPayloadNode.LookupByString(undef.Tag())
|
if info.Tag != zero.Tag() {
|
||||||
if err != nil {
|
return zero, errors.New("data doesn't match the expected type")
|
||||||
return undef, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This needs to be done before converting this node to its schema
|
// This needs to be done before converting this node to its schema
|
||||||
// representation (afterwards, the field might be renamed os it's safer
|
// representation (afterwards, the field might be renamed os it's safer
|
||||||
// to use the wire name).
|
// to use the wire name).
|
||||||
issuerNode, err := tokenPayloadNode.LookupByString("iss")
|
issuerNode, err := info.tokenPayloadNode.LookupByString("iss")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, err
|
return zero, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replaces the datamodel.Node in tokenPayloadNode with a
|
// Replaces the datamodel.Node in tokenPayloadNode with a
|
||||||
// schema.TypedNode so that we can cast it to a *token.Token after
|
// schema.TypedNode so that we can cast it to a *token.Token after
|
||||||
// unwrapping it.
|
// unwrapping it.
|
||||||
nb := undef.Prototype().Representation().NewBuilder()
|
nb := zero.Prototype().Representation().NewBuilder()
|
||||||
|
|
||||||
err = nb.AssignNode(tokenPayloadNode)
|
err = nb.AssignNode(info.tokenPayloadNode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, err
|
return zero, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenPayloadNode = nb.Build()
|
tokenPayloadNode := nb.Build()
|
||||||
|
|
||||||
tokenPayload := bindnode.Unwrap(tokenPayloadNode)
|
tokenPayload := bindnode.Unwrap(tokenPayloadNode)
|
||||||
if tokenPayload == nil {
|
if tokenPayload == nil {
|
||||||
return undef, errors.New("failed to Unwrap the TokenPayload")
|
return zero, errors.New("failed to Unwrap the TokenPayload")
|
||||||
}
|
}
|
||||||
|
|
||||||
tkn, ok := tokenPayload.(T)
|
tkn, ok := tokenPayload.(T)
|
||||||
if !ok {
|
if !ok {
|
||||||
return undef, errors.New("failed to assert the TokenPayload type as *token.Token")
|
return zero, errors.New("failed to assert the TokenPayload type as *token.Token")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the issuer's DID contains a public key with a type that
|
// Check that the issuer's DID contains a public key with a type that
|
||||||
// matches the VarsigHeader and then verify the SigPayload.
|
// matches the VarsigHeader and then verify the SigPayload.
|
||||||
issuer, err := issuerNode.AsString()
|
issuer, err := issuerNode.AsString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, err
|
return zero, err
|
||||||
}
|
}
|
||||||
|
|
||||||
issuerDID, err := did.Parse(issuer)
|
issuerDID, err := did.Parse(issuer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, err
|
return zero, err
|
||||||
}
|
}
|
||||||
|
|
||||||
issuerPubKey, err := issuerDID.PubKey()
|
issuerPubKey, err := issuerDID.PubKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, err
|
return zero, err
|
||||||
}
|
}
|
||||||
|
|
||||||
issuerVarsigHeader, err := varsig.Encode(issuerPubKey.Type())
|
issuerVarsigHeader, err := varsig.Encode(issuerPubKey.Type())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, err
|
return zero, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if string(info.VarsigHeader) != string(issuerVarsigHeader) {
|
if string(info.VarsigHeader) != string(issuerVarsigHeader) {
|
||||||
return undef, errors.New("the VarsigHeader key type doesn't match the issuer's key type")
|
return zero, errors.New("the VarsigHeader key type doesn't match the issuer's key type")
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := ipld.Encode(info.SigPayloadNode, dagcbor.Encode)
|
data, err := ipld.Encode(info.sigPayloadNode, dagcbor.Encode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, err
|
return zero, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ok, err = issuerPubKey.Verify(data, info.Signature)
|
ok, err = issuerPubKey.Verify(data, info.Signature)
|
||||||
if err != nil || !ok {
|
if err != nil || !ok {
|
||||||
return undef, errors.New("failed to verify the token's signature")
|
return zero, errors.New("failed to verify the token's signature")
|
||||||
}
|
}
|
||||||
|
|
||||||
return tkn, nil
|
return tkn, nil
|
||||||
@@ -296,42 +274,120 @@ func ToIPLD(privKey crypto.PrivKey, token Tokener) (datamodel.Node, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type Info struct {
|
// FindTag inspects the given token IPLD representation and extract the token tag.
|
||||||
Signature []byte
|
func FindTag(node datamodel.Node) (string, error) {
|
||||||
SigPayloadNode datamodel.Node
|
sigPayloadNode, err := node.LookupByIndex(1)
|
||||||
VarsigHeader []byte
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if sigPayloadNode.Kind() != datamodel.Kind_Map {
|
||||||
|
return "", fmt.Errorf("unexpected type instead of map")
|
||||||
|
}
|
||||||
|
|
||||||
|
it := sigPayloadNode.MapIterator()
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
for !it.Done() {
|
||||||
|
if i >= 2 {
|
||||||
|
return "", fmt.Errorf("expected two and only two fields in SigPayload")
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
k, _, err := it.Next()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := k.AsString()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(key, UCANTagPrefix) {
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("no token tag found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Info struct {
|
||||||
|
Tag string
|
||||||
|
Signature []byte
|
||||||
|
VarsigHeader []byte
|
||||||
|
sigPayloadNode datamodel.Node // private, we don't want to expose that
|
||||||
|
tokenPayloadNode datamodel.Node // private, we don't want to expose that
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inspect inspects the given token IPLD representation and extract some envelope facts.
|
||||||
func Inspect(node datamodel.Node) (Info, error) {
|
func Inspect(node datamodel.Node) (Info, error) {
|
||||||
var undef Info
|
var res Info
|
||||||
|
|
||||||
signatureNode, err := node.LookupByIndex(0)
|
signatureNode, err := node.LookupByIndex(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, err
|
return Info{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
signature, err := signatureNode.AsBytes()
|
res.Signature, err = signatureNode.AsBytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, err
|
return Info{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sigPayloadNode, err := node.LookupByIndex(1)
|
res.sigPayloadNode, err = node.LookupByIndex(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, err
|
return Info{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
varsigHeaderNode, err := sigPayloadNode.LookupByString(VarsigHeaderKey)
|
if res.sigPayloadNode.Kind() != datamodel.Kind_Map {
|
||||||
if err != nil {
|
return Info{}, fmt.Errorf("unexpected type instead of map")
|
||||||
return undef, err
|
|
||||||
}
|
|
||||||
varsigHeader, err := varsigHeaderNode.AsBytes()
|
|
||||||
if err != nil {
|
|
||||||
return undef, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Info{
|
it := res.sigPayloadNode.MapIterator()
|
||||||
Signature: signature,
|
foundVarsigHeader := false
|
||||||
SigPayloadNode: sigPayloadNode,
|
foundTokenPayload := false
|
||||||
VarsigHeader: varsigHeader,
|
i := 0
|
||||||
}, nil
|
|
||||||
|
for !it.Done() {
|
||||||
|
if i >= 2 {
|
||||||
|
return Info{}, fmt.Errorf("expected two and only two fields in SigPayload")
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
k, v, err := it.Next()
|
||||||
|
if err != nil {
|
||||||
|
return Info{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := k.AsString()
|
||||||
|
if err != nil {
|
||||||
|
return Info{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case key == VarsigHeaderKey:
|
||||||
|
foundVarsigHeader = true
|
||||||
|
res.VarsigHeader, err = v.AsBytes()
|
||||||
|
if err != nil {
|
||||||
|
return Info{}, err
|
||||||
|
}
|
||||||
|
case strings.HasPrefix(key, UCANTagPrefix):
|
||||||
|
foundTokenPayload = true
|
||||||
|
res.Tag = key
|
||||||
|
res.tokenPayloadNode = v
|
||||||
|
default:
|
||||||
|
return Info{}, fmt.Errorf("unexpected key type %q", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if i != 2 {
|
||||||
|
return Info{}, fmt.Errorf("expected two and only two fields in SigPayload: %d", i)
|
||||||
|
}
|
||||||
|
if !foundVarsigHeader {
|
||||||
|
return Info{}, errors.New("failed to find VarsigHeader field")
|
||||||
|
}
|
||||||
|
if !foundTokenPayload {
|
||||||
|
return Info{}, errors.New("failed to find TokenPayload field")
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,17 @@ package envelope_test
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ipld/go-ipld-prime"
|
||||||
|
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
|
||||||
"gotest.tools/v3/golden"
|
"gotest.tools/v3/golden"
|
||||||
|
|
||||||
|
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDecode(t *testing.T) {
|
func TestDecode(t *testing.T) {
|
||||||
@@ -149,3 +154,56 @@ func TestHash(t *testing.T) {
|
|||||||
require.Equal(t, hash1[:], hash2)
|
require.Equal(t, hash1[:], hash2)
|
||||||
require.Equal(t, hash1[:], hash3)
|
require.Equal(t, hash1[:], hash3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInspect(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
data := golden.Get(t, "example.dagcbor")
|
||||||
|
node, err := ipld.Decode(data, dagcbor.Decode)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expSig, err := base64.RawStdEncoding.DecodeString("fPqfwL3iFpbw9SvBiq0DIbUurv9o6c36R08tC/yslGrJcwV51ghzWahxdetpEf6T5LCszXX9I/K8khvnmAxjAg")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
info, err := envelope.Inspect(node)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, expSig, info.Signature)
|
||||||
|
assert.Equal(t, "ucan/example@v1.0.0-rc.1", info.Tag)
|
||||||
|
assert.Equal(t, []byte{0x34, 0xed, 0x1, 0x71}, info.VarsigHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzInspect(f *testing.F) {
|
||||||
|
data, err := os.ReadFile("testdata/example.dagcbor")
|
||||||
|
require.NoError(f, err)
|
||||||
|
|
||||||
|
f.Add(data)
|
||||||
|
|
||||||
|
f.Fuzz(func(t *testing.T, data []byte) {
|
||||||
|
node, err := ipld.Decode(data, dagcbor.Decode)
|
||||||
|
if err != nil {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
_, err = envelope.Inspect(node)
|
||||||
|
if err != nil {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzFindTag(f *testing.F) {
|
||||||
|
data, err := os.ReadFile("testdata/example.dagcbor")
|
||||||
|
require.NoError(f, err)
|
||||||
|
|
||||||
|
f.Add(data)
|
||||||
|
|
||||||
|
f.Fuzz(func(t *testing.T, data []byte) {
|
||||||
|
node, err := ipld.Decode(data, dagcbor.Decode)
|
||||||
|
if err != nil {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
_, err = envelope.FindTag(node)
|
||||||
|
if err != nil {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
80
tokens/read.go
Normal file
80
tokens/read.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package tokens
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/ipld/go-ipld-prime"
|
||||||
|
"github.com/ipld/go-ipld-prime/codec"
|
||||||
|
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
||||||
|
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
||||||
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
|
|
||||||
|
"github.com/ucan-wg/go-ucan/tokens/delegation"
|
||||||
|
"github.com/ucan-wg/go-ucan/tokens/internal/envelope"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Decode unmarshals the input data using the format specified by the
|
||||||
|
// provided codec.Decoder into an arbitrary UCAN token.
|
||||||
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
|
// Token is invalid.
|
||||||
|
// Supported and returned types are:
|
||||||
|
// - delegation.Token
|
||||||
|
func Decode(b []byte, decFn codec.Decoder) (any, error) {
|
||||||
|
node, err := ipld.Decode(b, decFn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return fromIPLD(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeReader is the same as Decode, but accept an io.Reader.
|
||||||
|
func DecodeReader(r io.Reader, decFn codec.Decoder) (any, error) {
|
||||||
|
node, err := ipld.DecodeStreaming(r, decFn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return fromIPLD(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromDagCbor unmarshals an arbitrary DagCbor encoded UCAN token.
|
||||||
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
|
// Token is invalid.
|
||||||
|
// Supported and returned types are:
|
||||||
|
// - delegation.Token
|
||||||
|
func FromDagCbor(b []byte) (any, error) {
|
||||||
|
return Decode(b, dagcbor.Decode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromDagCborReader is the same as FromDagCbor, but accept an io.Reader.
|
||||||
|
func FromDagCborReader(r io.Reader) (any, error) {
|
||||||
|
return DecodeReader(r, dagcbor.Decode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromDagCbor unmarshals an arbitrary DagJson encoded UCAN token.
|
||||||
|
// An error is returned if the conversion fails, or if the resulting
|
||||||
|
// Token is invalid.
|
||||||
|
// Supported and returned types are:
|
||||||
|
// - delegation.Token
|
||||||
|
func FromDagJson(b []byte) (any, error) {
|
||||||
|
return Decode(b, dagjson.Decode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromDagJsonReader is the same as FromDagJson, but accept an io.Reader.
|
||||||
|
func FromDagJsonReader(r io.Reader) (any, error) {
|
||||||
|
return DecodeReader(r, dagjson.Decode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromIPLD(node datamodel.Node) (any, error) {
|
||||||
|
tag, err := envelope.FindTag(node)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch tag {
|
||||||
|
case delegation.Tag:
|
||||||
|
return delegation.FromIPLD(node)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf(`unknown tag "%s"`, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user