From 6fb25481ce989c4d79743ec8c36542333e5441b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Mon, 4 Aug 2025 16:45:49 +0200 Subject: [PATCH] delegation: add interop testing --- token/delegation/interop_test.go | 108 ++++++++++++++++++ .../testdata/interop_delegation.json | 32 ++++++ 2 files changed, 140 insertions(+) create mode 100644 token/delegation/interop_test.go create mode 100644 token/delegation/testdata/interop_delegation.json diff --git a/token/delegation/interop_test.go b/token/delegation/interop_test.go new file mode 100644 index 0000000..c5127fd --- /dev/null +++ b/token/delegation/interop_test.go @@ -0,0 +1,108 @@ +package delegation + +import ( + _ "embed" + "encoding/base64" + "encoding/json" + "fmt" + "testing" + + "github.com/MetaMask/go-did-it/crypto" + "github.com/MetaMask/go-did-it/crypto/ed25519" + "github.com/multiformats/go-varint" + "github.com/stretchr/testify/require" +) + +// This comes from https://github.com/ucan-wg/spec/blob/main/fixtures/1.0.0/delegation.json +// +//go:embed testdata/interop_delegation.json +var interopDelegation []byte + +type interop struct { + Version string `json:"version"` + Comments string `json:"comments"` + Principals map[string]string `json:"principals"` + Valid []validTestCase `json:"valid"` +} + +type validTestCase struct { + Name string `json:"name"` + Token string `json:"token"` + CID string `json:"cid"` + Envelope envelopeData `json:"envelope"` +} + +type envelopeData struct { + Payload payloadData `json:"payload"` + Signature string `json:"signature"` + Algorithm string `json:"alg"` + Encoding string `json:"enc"` + Spec string `json:"spec"` + Version string `json:"version"` +} + +type payloadData struct { + Issuer string `json:"iss"` + Audience string `json:"aud"` + Subject string `json:"sub"` + Command string `json:"cmd"` + Policies json.RawMessage `json:"pol"` + ExpiresAt int64 `json:"exp"` + Nonce string `json:"nonce"` +} + +func TestInterop(t *testing.T) { + var testData interop + err := json.Unmarshal(interopDelegation, &testData) + require.NoError(t, err) + + require.Equal(t, "1.0.0-rc.1", testData.Version) + + // alice, err := decodeKey(testData.Principals["alice"]) + // require.NoError(t, err) + // bob, err := decodeKey(testData.Principals["bob"]) + // require.NoError(t, err) + // carol, err := decodeKey(testData.Principals["carol"]) + // require.NoError(t, err) + + t.Run("valid", func(t *testing.T) { + for _, tc := range testData.Valid { + t.Run(tc.Name, func(t *testing.T) { + dlgBytes, err := base64.StdEncoding.DecodeString(tc.Token) + require.NoError(t, err) + + dlg, c, err := FromSealed(dlgBytes) + require.NoError(t, err) + require.Equal(t, tc.CID, c.String()) + + require.Equal(t, tc.Envelope.Payload.Issuer, dlg.Issuer().String()) + require.Equal(t, tc.Envelope.Payload.Audience, dlg.Audience().String()) + require.Equal(t, tc.Envelope.Payload.Subject, dlg.Subject().String()) + require.Equal(t, tc.Envelope.Payload.Command, dlg.Command().String()) + require.Equal(t, tc.Envelope.Payload.Command, dlg.Command().String()) + require.JSONEq(t, string(tc.Envelope.Payload.Policies), dlg.Policy().String()) + require.Equal(t, tc.Envelope.Payload.ExpiresAt, dlg.expiration.Unix()) + nonceBytes, err := base64.StdEncoding.DecodeString(tc.Envelope.Payload.Nonce) + require.NoError(t, err) + require.Equal(t, nonceBytes, dlg.Nonce()) + }) + } + }) +} + +func decodeKey(key string) (crypto.PrivateKeySigningBytes, error) { + bytes, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return nil, err + } + + code, read, err := varint.FromUvarint(bytes) + if err != nil { + return nil, err + } + if code != 0x1300 { + return nil, fmt.Errorf("invalid varint code: %d", code) + } + + return ed25519.PrivateKeyFromSeed(bytes[read:]) +} diff --git a/token/delegation/testdata/interop_delegation.json b/token/delegation/testdata/interop_delegation.json new file mode 100644 index 0000000..ab2be42 --- /dev/null +++ b/token/delegation/testdata/interop_delegation.json @@ -0,0 +1,32 @@ +{ + "version": "1.0.0-rc.1", + "comments": "Principals private keys encoded as base64pad(varint(0x1300) + privateKey) and all other binary fields are encoded as base64pad.", + "principals": { + "carol": "gCZC43QGw7ZvYQuKTtBwBy+tdjYrKf0hXU3dd+J0HON5dw==", + "bob": "gCZfj9+RzU2U518TMBNK/fjdGQz34sB4iKE6z+9lQDpCIQ==", + "alice": "gCa9UfZv+yI5/rvUIt21DaGI7EZJlzFO1uDc5AyJ30c6/w==" + }, + "valid": [ + { + "name": "basic delegation bob > carol", + "token": "glhAd7jvZs44lTWmjSG/PWBRXvAdJA6Pq0fj86WQOVBYSw3fLrpjF7OMvjUlTynZZblPHzFsiBeBlUqtbCAHvhppCaJhaEg0Ae0B7QETcXN1Y2FuL2RsZ0AxLjAuMC1yYy4xp2NhdWR4OGRpZDprZXk6ejZNa21KY2VWb1FTSHM0NWNSZUVYb0x0V20xd29zQ0c4Ukx4Zkt3aHhvcXpvVGtDY2NtZGgvYWNjb3VudGNleHAaaIIMsWNpc3N4OGRpZDprZXk6ejZNa21UOWo2ZlZacXpYVjh1MndWVlN1NDlnWVNSWUdTUW5kdVdYRjZmb0FKcnF6Y3BvbIBjc3VieDhkaWQ6a2V5Ono2TWttVDlqNmZWWnF6WFY4dTJ3VlZTdTQ5Z1lTUllHU1FuZHVXWEY2Zm9BSnJxemVub25jZUwnbSv2keQn/Kg2KsM=", + "cid": "bafyreifqsojs54lpxxyx5xfqxiwkc4paglcyqd7vjzrcyapxi557extz6m", + "envelope": { + "payload": { + "iss": "did:key:z6MkmT9j6fVZqzXV8u2wVVSu49gYSRYGSQnduWXF6foAJrqz", + "aud": "did:key:z6MkmJceVoQSHs45cReEXoLtWm1wosCG8RLxfKwhxoqzoTkC", + "sub": "did:key:z6MkmT9j6fVZqzXV8u2wVVSu49gYSRYGSQnduWXF6foAJrqz", + "cmd": "/account", + "pol": [], + "exp": 1753353393, + "nonce": "J20r9pHkJ/yoNirD" + }, + "signature": "d7jvZs44lTWmjSG/PWBRXvAdJA6Pq0fj86WQOVBYSw3fLrpjF7OMvjUlTynZZblPHzFsiBeBlUqtbCAHvhppCQ==", + "alg": "Ed25519", + "enc": "DAG-CBOR", + "spec": "dlg", + "version": "1.0.0-rc.1" + } + } + ] +}