feat(envelope): expose an Inspect function
This commit is contained in:
105
tokens/inspect.go
Normal file
105
tokens/inspect.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package tokens
|
||||||
|
|
||||||
|
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/ucan-wg/go-ucan/tokens/internal/envelope"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EnvelopeInfo describes the fields of an Envelope enclosing a UCAN token.
|
||||||
|
type EnvelopeInfo struct {
|
||||||
|
Signature []byte
|
||||||
|
Tag string
|
||||||
|
VarsigHeader []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// InspectEnvelope accepts arbitrary data and attempts to decode it as a
|
||||||
|
// UCAN token's Envelope.
|
||||||
|
func Inspect(data []byte) (EnvelopeInfo, error) {
|
||||||
|
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")
|
||||||
|
}
|
||||||
34
tokens/inspect_test.go
Normal file
34
tokens/inspect_test.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,7 +44,10 @@ import (
|
|||||||
"github.com/ucan-wg/go-ucan/tokens/internal/varsig"
|
"github.com/ucan-wg/go-ucan/tokens/internal/varsig"
|
||||||
)
|
)
|
||||||
|
|
||||||
const varsigHeaderKey = "h"
|
const (
|
||||||
|
VarsigHeaderKey = "h"
|
||||||
|
UCANTagPrefix = "ucan/"
|
||||||
|
)
|
||||||
|
|
||||||
// Tokener must be implemented by types that wish to be enclosed in a
|
// Tokener must be implemented by types that wish to be enclosed in a
|
||||||
// UCAN Envelope (presumbably one of the UCAN token types).
|
// UCAN Envelope (presumbably one of the UCAN token types).
|
||||||
@@ -140,27 +143,12 @@ func FromIPLD[T Tokener](node datamodel.Node) (T, error) {
|
|||||||
func fromIPLD[T Tokener](node datamodel.Node) (T, error) {
|
func fromIPLD[T Tokener](node datamodel.Node) (T, error) {
|
||||||
undef := *new(T)
|
undef := *new(T)
|
||||||
|
|
||||||
signatureNode, err := node.LookupByIndex(0)
|
info, err := Inspect(node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, err
|
return undef, err
|
||||||
}
|
}
|
||||||
|
|
||||||
signature, err := signatureNode.AsBytes()
|
tokenPayloadNode, err := info.SigPayloadNode.LookupByString(undef.Tag())
|
||||||
if err != nil {
|
|
||||||
return undef, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sigPayloadNode, err := node.LookupByIndex(1)
|
|
||||||
if err != nil {
|
|
||||||
return undef, err
|
|
||||||
}
|
|
||||||
|
|
||||||
varsigHeaderNode, err := sigPayloadNode.LookupByString(varsigHeaderKey)
|
|
||||||
if err != nil {
|
|
||||||
return undef, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenPayloadNode, err := sigPayloadNode.LookupByString(undef.Tag())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, err
|
return undef, err
|
||||||
}
|
}
|
||||||
@@ -217,21 +205,16 @@ func fromIPLD[T Tokener](node datamodel.Node) (T, error) {
|
|||||||
return undef, err
|
return undef, err
|
||||||
}
|
}
|
||||||
|
|
||||||
varsigHeader, err := varsigHeaderNode.AsBytes()
|
if string(info.VarsigHeader) != string(issuerVarsigHeader) {
|
||||||
if err != nil {
|
|
||||||
return undef, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(varsigHeader) != string(issuerVarsigHeader) {
|
|
||||||
return undef, errors.New("the VarsigHeader key type doesn't match the issuer's key type")
|
return undef, errors.New("the VarsigHeader key type doesn't match the issuer's key type")
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := ipld.Encode(sigPayloadNode, dagcbor.Encode)
|
data, err := ipld.Encode(info.SigPayloadNode, dagcbor.Encode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return undef, err
|
return undef, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ok, err = issuerPubKey.Verify(data, 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 undef, errors.New("failed to verify the token's signature")
|
||||||
}
|
}
|
||||||
@@ -293,7 +276,7 @@ func ToIPLD(privKey crypto.PrivKey, token Tokener) (datamodel.Node, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sigPayloadNode, err := qp.BuildMap(basicnode.Prototype.Any, 2, func(ma datamodel.MapAssembler) {
|
sigPayloadNode, err := qp.BuildMap(basicnode.Prototype.Any, 2, func(ma datamodel.MapAssembler) {
|
||||||
qp.MapEntry(ma, varsigHeaderKey, qp.Bytes(varsigHeader))
|
qp.MapEntry(ma, VarsigHeaderKey, qp.Bytes(varsigHeader))
|
||||||
qp.MapEntry(ma, token.Tag(), qp.Node(tokenPayloadNode))
|
qp.MapEntry(ma, token.Tag(), qp.Node(tokenPayloadNode))
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -312,3 +295,43 @@ func ToIPLD(privKey crypto.PrivKey, token Tokener) (datamodel.Node, error) {
|
|||||||
qp.ListEntry(la, qp.Node(sigPayloadNode))
|
qp.ListEntry(la, qp.Node(sigPayloadNode))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Info struct {
|
||||||
|
Signature []byte
|
||||||
|
SigPayloadNode datamodel.Node
|
||||||
|
VarsigHeader []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func Inspect(node datamodel.Node) (Info, error) {
|
||||||
|
var undef Info
|
||||||
|
|
||||||
|
signatureNode, err := node.LookupByIndex(0)
|
||||||
|
if err != nil {
|
||||||
|
return undef, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signature, err := signatureNode.AsBytes()
|
||||||
|
if err != nil {
|
||||||
|
return undef, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sigPayloadNode, err := node.LookupByIndex(1)
|
||||||
|
if err != nil {
|
||||||
|
return undef, err
|
||||||
|
}
|
||||||
|
|
||||||
|
varsigHeaderNode, err := sigPayloadNode.LookupByString(VarsigHeaderKey)
|
||||||
|
if err != nil {
|
||||||
|
return undef, err
|
||||||
|
}
|
||||||
|
varsigHeader, err := varsigHeaderNode.AsBytes()
|
||||||
|
if err != nil {
|
||||||
|
return undef, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return Info{
|
||||||
|
Signature: signature,
|
||||||
|
SigPayloadNode: sigPayloadNode,
|
||||||
|
VarsigHeader: varsigHeader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|||||||
1
tokens/testdata/example.dagcbor
vendored
Normal file
1
tokens/testdata/example.dagcbor
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
‚X@|úŸÀ½â–ðõ+ÁŠ!µ.®ÿhéÍúGO-ü¬”jÉsyÖsY¨quëiþ“ä°¬Íuý#ò¼’ç˜c¢ahD4íqxucan/example@v1.0.0-rc.1¢cissx8did:key:z6MkpuK2Amsu1RqcLGgmHHQHhvmeXCCBVsM4XFSg2cCyg4Nhehelloeworld
|
||||||
1
tokens/testdata/example.dagjson
vendored
Normal file
1
tokens/testdata/example.dagjson
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[{"/":{"bytes":"fPqfwL3iFpbw9SvBiq0DIbUurv9o6c36R08tC/yslGrJcwV51ghzWahxdetpEf6T5LCszXX9I/K8khvnmAxjAg"}},{"h":{"/":{"bytes":"NO0BcQ"}},"ucan/example@v1.0.0-rc.1":{"hello":"world","iss":"did:key:z6MkpuK2Amsu1RqcLGgmHHQHhvmeXCCBVsM4XFSg2cCyg4Nh"}}]
|
||||||
Reference in New Issue
Block a user