Merge pull request #93 from ucan-wg/proof-rephrase

invocation: rephrase slightly the proof rules to be less confusing down the line
This commit is contained in:
Michael Muré
2024-12-10 13:27:12 +01:00
committed by GitHub
20 changed files with 134 additions and 64 deletions

View File

@@ -102,6 +102,9 @@ func (d DID) PubKey() (crypto.PubKey, error) {
// String formats the decentralized identity document (DID) as a string. // String formats the decentralized identity document (DID) as a string.
func (d DID) String() string { func (d DID) String() string {
if d == Undef {
return "(undefined)"
}
key, _ := mbase.Encode(mbase.Base58BTC, []byte(d.bytes)) key, _ := mbase.Encode(mbase.Base58BTC, []byte(d.bytes))
return "did:key:" + key return "did:key:" + key
} }

View File

@@ -160,13 +160,12 @@ func randToken() (*delegation.Token, cid.Cid, []byte) {
opts := []delegation.Option{ opts := []delegation.Option{
delegation.WithExpiration(time.Now().Add(time.Hour)), delegation.WithExpiration(time.Now().Add(time.Hour)),
delegation.WithSubject(iss),
} }
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
opts = append(opts, delegation.WithMeta(randomString(8), randomString(10))) opts = append(opts, delegation.WithMeta(randomString(8), randomString(10)))
} }
t, err := delegation.New(iss, aud, cmd, pol, opts...) t, err := delegation.Root(iss, aud, cmd, pol, opts...)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -10,8 +10,10 @@ package delegation
// TODO: change the "delegation" link above when the specification is merged // TODO: change the "delegation" link above when the specification is merged
import ( import (
"encoding/base64"
"errors" "errors"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/ucan-wg/go-ucan/did" "github.com/ucan-wg/go-ucan/did"
@@ -44,16 +46,15 @@ type Token struct {
expiration *time.Time expiration *time.Time
} }
// New creates a validated Token from the provided parameters and options. // New creates a validated delegation Token from the provided parameters and options.
// This is typically used to delegate a given power to another agent.
// //
// When creating a delegated token, the Issuer's (iss) DID is assembled // You can read it as "(issuer) allows (audience) to perform (cmd+pol) on (subject)".
// using the public key associated with the private key sent as the first func New(iss did.DID, aud did.DID, cmd command.Command, pol policy.Policy, sub did.DID, opts ...Option) (*Token, error) {
// parameter.
func New(iss, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Option) (*Token, error) {
tkn := &Token{ tkn := &Token{
issuer: iss, issuer: iss,
audience: aud, audience: aud,
subject: did.Undef, subject: sub,
command: cmd, command: cmd,
policy: pol, policy: pol,
meta: meta.NewMeta(), meta: meta.NewMeta(),
@@ -81,16 +82,27 @@ func New(iss, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Optio
return tkn, nil return tkn, nil
} }
// Root creates a validated UCAN delegation Token from the provided // Root creates a validated UCAN delegation Token from the provided parameters and options.
// parameters and options. // This is typically used to create and give a power to an agent.
// //
// When creating a root token, both the Issuer's (iss) and Subject's // You can read it as "(issuer) allows (audience) to perform (cmd+pol) on itself".
// (sub) DIDs are assembled from the public key associated with the func Root(iss did.DID, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Option) (*Token, error) {
// private key passed as the first argument. return New(iss, aud, cmd, pol, iss, opts...)
func Root(iss, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Option) (*Token, error) { }
opts = append(opts, WithSubject(iss))
return New(iss, aud, cmd, pol, opts...) // Powerline creates a validated UCAN delegation Token from the provided parameters and options.
//
// Powerline is a pattern for automatically delegating all future delegations to another agent regardless of Subject.
// This is a very powerful pattern, use it only if you understand it.
// Powerline delegations MUST NOT be used as the root delegation to a resource
//
// A very common use case for Powerline is providing a stable DID across multiple agents (e.g. representing a user with
// multiple devices). This enables the automatic sharing of authority across their devices without needing to share keys
// or set up a threshold scheme. It is also flexible, since a Powerline delegation MAY be revoked.
//
// You can read it as "(issuer) allows (audience) to perform (cmd+pol) on anything".
func Powerline(iss did.DID, aud did.DID, cmd command.Command, pol policy.Policy, opts ...Option) (*Token, error) {
return New(iss, aud, cmd, pol, did.Undef, opts...)
} }
// Issuer returns the did.DID representing the Token's issuer. // Issuer returns the did.DID representing the Token's issuer.
@@ -160,6 +172,32 @@ func (t *Token) IsValidAt(ti time.Time) bool {
return true return true
} }
func (t *Token) String() string {
var res strings.Builder
var kind string
switch {
case t.issuer == t.subject:
kind = " (root delegation)"
case t.subject == did.Undef:
kind = " (powerline delegation)"
default:
kind = " (normal delegation)"
}
res.WriteString(fmt.Sprintf("Issuer: %s\n", t.Issuer()))
res.WriteString(fmt.Sprintf("Audience: %s\n", t.Audience()))
res.WriteString(fmt.Sprintf("Subject: %s%s\n", t.Subject(), kind))
res.WriteString(fmt.Sprintf("Command: %s\n", t.Command()))
res.WriteString(fmt.Sprintf("Policy: %s\n", t.Policy()))
res.WriteString(fmt.Sprintf("Nonce: %s\n", base64.StdEncoding.EncodeToString(t.Nonce())))
res.WriteString(fmt.Sprintf("Meta: %s\n", t.Meta()))
res.WriteString(fmt.Sprintf("NotBefore: %v\n", t.NotBefore()))
res.WriteString(fmt.Sprintf("Expiration: %v", t.Expiration()))
return res.String()
}
func (t *Token) validate() error { func (t *Token) validate() error {
var errs error var errs error

View File

@@ -56,9 +56,6 @@ const (
] ]
` `
newCID = "zdpuAwa4qv3ncMDPeDoqVxjZy3JoyWsbqUzm94rdA1AvRFkkw"
rootCID = "zdpuAkgGmUp5JrXvehGuuw9JA8DLQKDaxtK3R8brDQQVC2i5X"
aesKey = "xQklMmNTnVrmaPBq/0pwV5fEwuv/iClF5HWak9MsgI8=" aesKey = "xQklMmNTnVrmaPBq/0pwV5fEwuv/iClF5HWak9MsgI8="
) )
@@ -75,9 +72,8 @@ func TestConstructors(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
t.Run("New", func(t *testing.T) { t.Run("New", func(t *testing.T) {
tkn, err := delegation.New(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol, tkn, err := delegation.New(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol, didtest.PersonaCarol.DID(),
delegation.WithNonce([]byte(nonce)), delegation.WithNonce([]byte(nonce)),
delegation.WithSubject(didtest.PersonaAlice.DID()),
delegation.WithExpiration(exp), delegation.WithExpiration(exp),
delegation.WithMeta("foo", "fooo"), delegation.WithMeta("foo", "fooo"),
delegation.WithMeta("bar", "barr"), delegation.WithMeta("bar", "barr"),
@@ -106,6 +102,23 @@ func TestConstructors(t *testing.T) {
golden.Assert(t, string(data), "root.dagjson") golden.Assert(t, string(data), "root.dagjson")
}) })
t.Run("Powerline", func(t *testing.T) {
t.Parallel()
tkn, err := delegation.Powerline(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol,
delegation.WithNonce([]byte(nonce)),
delegation.WithExpiration(exp),
delegation.WithMeta("foo", "fooo"),
delegation.WithMeta("bar", "barr"),
)
require.NoError(t, err)
data, err := tkn.ToDagJson(didtest.PersonaAlice.PrivKey())
require.NoError(t, err)
golden.Assert(t, string(data), "powerline.dagjson")
})
} }
func TestEncryptedMeta(t *testing.T) { func TestEncryptedMeta(t *testing.T) {
@@ -153,7 +166,7 @@ func TestEncryptedMeta(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
t.Parallel() t.Parallel()
tkn, err := delegation.New(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol, tkn, err := delegation.Root(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol,
delegation.WithEncryptedMetaString(tt.key, tt.value, encryptionKey), delegation.WithEncryptedMetaString(tt.key, tt.value, encryptionKey),
) )
require.NoError(t, err) require.NoError(t, err)
@@ -191,7 +204,7 @@ func TestEncryptedMeta(t *testing.T) {
} }
// Create token with multiple encrypted values // Create token with multiple encrypted values
tkn, err := delegation.New(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol, opts...) tkn, err := delegation.Root(didtest.PersonaAlice.DID(), didtest.PersonaBob.DID(), cmd, pol, opts...)
require.NoError(t, err) require.NoError(t, err)
data, err := tkn.ToDagCbor(didtest.PersonaAlice.PrivKey()) data, err := tkn.ToDagCbor(didtest.PersonaAlice.PrivKey())

View File

@@ -30,10 +30,11 @@ const (
var constantNonce = []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b} var constantNonce = []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b}
type newDelegationParams struct { type newDelegationParams struct {
privKey crypto.PrivKey privKey crypto.PrivKey // iss
aud did.DID aud did.DID
cmd command.Command cmd command.Command
pol policy.Policy pol policy.Policy
sub did.DID
opts []delegation.Option opts []delegation.Option
} }
@@ -89,8 +90,8 @@ func (g *generator) chainPersonas(personas []didtest.Persona, acc acc, vari vari
aud: personas[1].DID(), aud: personas[1].DID(),
cmd: delegationtest.NominalCommand, cmd: delegationtest.NominalCommand,
pol: policytest.EmptyPolicy, pol: policytest.EmptyPolicy,
sub: didtest.PersonaAlice.DID(),
opts: []delegation.Option{ opts: []delegation.Option{
delegation.WithSubject(didtest.PersonaAlice.DID()),
delegation.WithNonce(constantNonce), delegation.WithNonce(constantNonce),
}, },
} }
@@ -117,7 +118,7 @@ func (g *generator) chainPersonas(personas []didtest.Persona, acc acc, vari vari
p.cmd = delegationtest.AttenuatedCommand p.cmd = delegationtest.AttenuatedCommand
}}, }},
{name: "InvalidSubject", variant: func(p *newDelegationParams) { {name: "InvalidSubject", variant: func(p *newDelegationParams) {
p.opts = append(p.opts, delegation.WithSubject(didtest.PersonaBob.DID())) p.sub = didtest.PersonaBob.DID()
}}, }},
{name: "InvalidExpired", variant: func(p *newDelegationParams) { {name: "InvalidExpired", variant: func(p *newDelegationParams) {
// Note: this makes the generator not deterministic // Note: this makes the generator not deterministic
@@ -161,7 +162,7 @@ func (g *generator) createDelegation(params newDelegationParams, name string, va
return cid.Undef, err return cid.Undef, err
} }
tkn, err := delegation.New(issDID, params.aud, params.cmd, params.pol, params.opts...) tkn, err := delegation.New(issDID, params.aud, params.cmd, params.pol, params.sub, params.opts...)
if err != nil { if err != nil {
return cid.Undef, err return cid.Undef, err
} }

View File

@@ -107,21 +107,21 @@ var (
) )
var ( var (
TokenCarolDan_InvalidExpiredCID = cid.MustParse("bafyreigvdcarp6426e5aenal3q3c4uyhncwfbmt3fsmb4qzspkpjy57gti") TokenCarolDan_InvalidExpiredCID = cid.MustParse("bafyreifyzm5jkx2sfu5awyndg3dn5zlg7sq5hssgfatafk62kiiilapnqe")
TokenCarolDan_InvalidExpiredSealed = mustGetBundle(TokenCarolDan_InvalidExpiredCID).Sealed TokenCarolDan_InvalidExpiredSealed = mustGetBundle(TokenCarolDan_InvalidExpiredCID).Sealed
TokenCarolDan_InvalidExpiredBundle = mustGetBundle(TokenCarolDan_InvalidExpiredCID) TokenCarolDan_InvalidExpiredBundle = mustGetBundle(TokenCarolDan_InvalidExpiredCID)
TokenCarolDan_InvalidExpired = mustGetBundle(TokenCarolDan_InvalidExpiredCID).Decoded TokenCarolDan_InvalidExpired = mustGetBundle(TokenCarolDan_InvalidExpiredCID).Decoded
) )
var ( var (
TokenDanErin_InvalidExpiredCID = cid.MustParse("bafyreifa265mung4nfpjjuiir5nytzgbrhfl66ye2dqmbq4p6dm7bzp4he") TokenDanErin_InvalidExpiredCID = cid.MustParse("bafyreihhnisabmkofuk3qaw37leijxqjaz5or6v2cufjxwzdkvuvv2dzbq")
TokenDanErin_InvalidExpiredSealed = mustGetBundle(TokenDanErin_InvalidExpiredCID).Sealed TokenDanErin_InvalidExpiredSealed = mustGetBundle(TokenDanErin_InvalidExpiredCID).Sealed
TokenDanErin_InvalidExpiredBundle = mustGetBundle(TokenDanErin_InvalidExpiredCID) TokenDanErin_InvalidExpiredBundle = mustGetBundle(TokenDanErin_InvalidExpiredCID)
TokenDanErin_InvalidExpired = mustGetBundle(TokenDanErin_InvalidExpiredCID).Decoded TokenDanErin_InvalidExpired = mustGetBundle(TokenDanErin_InvalidExpiredCID).Decoded
) )
var ( var (
TokenErinFrank_InvalidExpiredCID = cid.MustParse("bafyreicde2per7ynjruvstuae67dxzdg2iepid3nonrmgr6tvodzpbqomu") TokenErinFrank_InvalidExpiredCID = cid.MustParse("bafyreigeokaziviwm5kzmkpwesj3gta5k7zrd62x4a746fnrnkhvatwbna")
TokenErinFrank_InvalidExpiredSealed = mustGetBundle(TokenErinFrank_InvalidExpiredCID).Sealed TokenErinFrank_InvalidExpiredSealed = mustGetBundle(TokenErinFrank_InvalidExpiredCID).Sealed
TokenErinFrank_InvalidExpiredBundle = mustGetBundle(TokenErinFrank_InvalidExpiredCID) TokenErinFrank_InvalidExpiredBundle = mustGetBundle(TokenErinFrank_InvalidExpiredCID)
TokenErinFrank_InvalidExpired = mustGetBundle(TokenErinFrank_InvalidExpiredCID).Decoded TokenErinFrank_InvalidExpired = mustGetBundle(TokenErinFrank_InvalidExpiredCID).Decoded

View File

@@ -41,8 +41,7 @@ func ExampleNew() {
)), )),
) )
tkn, err := delegation.New(didtest.PersonaBob.DID(), didtest.PersonaCarol.DID(), cmd, pol, tkn, err := delegation.New(didtest.PersonaBob.DID(), didtest.PersonaCarol.DID(), cmd, pol, didtest.PersonaAlice.DID(),
delegation.WithSubject(didtest.PersonaAlice.DID()),
delegation.WithExpirationIn(time.Hour), delegation.WithExpirationIn(time.Hour),
delegation.WithNotBeforeIn(time.Minute), delegation.WithNotBeforeIn(time.Minute),
delegation.WithMeta("foo", "bar"), delegation.WithMeta("foo", "bar"),

View File

@@ -3,8 +3,6 @@ package delegation
import ( import (
"fmt" "fmt"
"time" "time"
"github.com/ucan-wg/go-ucan/did"
) )
// Option is a type that allows optional fields to be set during the // Option is a type that allows optional fields to be set during the
@@ -85,20 +83,6 @@ func WithNotBeforeIn(nbf time.Duration) Option {
} }
} }
// WithSubject sets the Tokens's optional "subject" field to the value of
// provided did.DID.
//
// This Option should only be used with the New constructor - since
// Subject is a required parameter when creating a Token via the Root
// constructor, any value provided via this Option will be silently
// overwritten.
func WithSubject(sub did.DID) Option {
return func(t *Token) error {
t.subject = sub
return nil
}
}
// WithNonce sets the Token's nonce with the given value. // WithNonce sets the Token's nonce with the given value.
// If this option is not used, a random 12-byte nonce is generated for this required field. // If this option is not used, a random 12-byte nonce is generated for this required field.
func WithNonce(nonce []byte) Option { func WithNonce(nonce []byte) Option {

View File

@@ -33,9 +33,12 @@ func TestSchemaRoundTrip(t *testing.T) {
p1, err := delegation.FromDagJson(delegationJson) p1, err := delegation.FromDagJson(delegationJson)
require.NoError(t, err) require.NoError(t, err)
_, newCID, err := p1.ToSealed(privKey)
require.NoError(t, err)
cborBytes, id, err := p1.ToSealed(privKey) cborBytes, id, err := p1.ToSealed(privKey)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, newCID, envelope.CIDToBase58BTC(id)) assert.Equal(t, envelope.CIDToBase58BTC(newCID), envelope.CIDToBase58BTC(id))
p2, c2, err := delegation.FromSealed(cborBytes) p2, c2, err := delegation.FromSealed(cborBytes)
require.NoError(t, err) require.NoError(t, err)
@@ -58,10 +61,13 @@ func TestSchemaRoundTrip(t *testing.T) {
p1, err := delegation.FromDagJsonReader(buf) p1, err := delegation.FromDagJsonReader(buf)
require.NoError(t, err) require.NoError(t, err)
_, newCID, err := p1.ToSealed(privKey)
require.NoError(t, err)
cborBytes := &bytes.Buffer{} cborBytes := &bytes.Buffer{}
id, err := p1.ToSealedWriter(cborBytes, privKey) id, err := p1.ToSealedWriter(cborBytes, privKey)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, newCID, envelope.CIDToBase58BTC(id)) assert.Equal(t, envelope.CIDToBase58BTC(newCID), envelope.CIDToBase58BTC(id))
// buf = bytes.NewBuffer(cborBytes.Bytes()) // buf = bytes.NewBuffer(cborBytes.Bytes())
p2, c2, err := delegation.FromSealedReader(cborBytes) p2, c2, err := delegation.FromSealedReader(cborBytes)

View File

@@ -1 +1 @@
[{"/":{"bytes":"BBabgnWqd+cjwG1td0w9BudNocmUwoR89RMZTqZHk3osCXEI/bOkko0zTvlusaE4EMBBeSzZDKzjvunLBfdiBg"}},{"h":{"/":{"bytes":"NO0BcQ"}},"ucan/dlg@1.0.0-rc.1":{"aud":"did:key:z6MkvJPmEZZYbgiw1ouT1oouTsTFBHJSts9ophVsNgcRmYxU","cmd":"/foo/bar","exp":7258118400,"iss":"did:key:z6Mkuukk2skDXLQn7NK3Eh9jMndYfvDBxxktgpidJAqb7M3p","meta":{"bar":"barr","foo":"fooo"},"nonce":{"/":{"bytes":"NnJvRGhHaTBraU5yaVFBejdKM2QrYk9lb0kvdGo4RU5pa21RTmJ0am5EMA"}},"pol":[["==",".status","draft"],["all",".reviewer",["like",".email","*@example.com"]],["any",".tags",["or",[["==",".","news"],["==",".","press"]]]]],"sub":"did:key:z6Mkuukk2skDXLQn7NK3Eh9jMndYfvDBxxktgpidJAqb7M3p"}}] [{"/":{"bytes":"YJsl8EMLnXSFE/nKKjMxz9bHHo+Y7QeLEzukEzW1TB+m53TTiY1aOt+qUO8JaTcOKsOHt/a4Vn+YiOd5CkLdAQ"}},{"h":{"/":{"bytes":"NO0BcQ"}},"ucan/dlg@1.0.0-rc.1":{"aud":"did:key:z6MkvJPmEZZYbgiw1ouT1oouTsTFBHJSts9ophVsNgcRmYxU","cmd":"/foo/bar","exp":7258118400,"iss":"did:key:z6Mkuukk2skDXLQn7NK3Eh9jMndYfvDBxxktgpidJAqb7M3p","meta":{"bar":"barr","foo":"fooo"},"nonce":{"/":{"bytes":"NnJvRGhHaTBraU5yaVFBejdKM2QrYk9lb0kvdGo4RU5pa21RTmJ0am5EMA"}},"pol":[["==",".status","draft"],["all",".reviewer",["like",".email","*@example.com"]],["any",".tags",["or",[["==",".","news"],["==",".","press"]]]]],"sub":"did:key:z6Mkgupchh5HwuHahS7YsyE8bLua1Mr8p2iKNRhyvSvRAs9n"}}]

View File

@@ -0,0 +1 @@
[{"/":{"bytes":"i3YkPDvNSU4V8XYEluZhLH0b+NDcW/6+PtPSUHC17cmXXqgelG0K4EzWQQkS9UsYCHfkZSCn9NjGSXYMMFhaAQ"}},{"h":{"/":{"bytes":"NO0BcQ"}},"ucan/dlg@1.0.0-rc.1":{"aud":"did:key:z6MkvJPmEZZYbgiw1ouT1oouTsTFBHJSts9ophVsNgcRmYxU","cmd":"/foo/bar","exp":7258118400,"iss":"did:key:z6Mkuukk2skDXLQn7NK3Eh9jMndYfvDBxxktgpidJAqb7M3p","meta":{"bar":"barr","foo":"fooo"},"nonce":{"/":{"bytes":"NnJvRGhHaTBraU5yaVFBejdKM2QrYk9lb0kvdGo4RU5pa21RTmJ0am5EMA"}},"pol":[["==",".status","draft"],["all",".reviewer",["like",".email","*@example.com"]],["any",".tags",["or",[["==",".","news"],["==",".","press"]]]]]}}]

View File

@@ -27,7 +27,7 @@ func ExampleNew() {
return return
} }
inv, err := invocation.New(iss, sub, cmd, prf, inv, err := invocation.New(iss, cmd, sub, prf,
invocation.WithArgument("uri", args["uri"]), invocation.WithArgument("uri", args["uri"]),
invocation.WithArgument("headers", args["headers"]), invocation.WithArgument("headers", args["headers"]),
invocation.WithArgument("payload", args["payload"]), invocation.WithArgument("payload", args["payload"]),

View File

@@ -8,8 +8,10 @@
package invocation package invocation
import ( import (
"encoding/base64"
"errors" "errors"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/ipfs/go-cid" "github.com/ipfs/go-cid"
@@ -67,7 +69,9 @@ type Token struct {
// //
// With the exception of the WithMeta option, all others will overwrite // With the exception of the WithMeta option, all others will overwrite
// the previous contents of their target field. // the previous contents of their target field.
func New(iss, sub did.DID, cmd command.Command, prf []cid.Cid, opts ...Option) (*Token, error) { //
// You can read it as "(Issuer - I) executes (command) on (subject)".
func New(iss did.DID, cmd command.Command, sub did.DID, prf []cid.Cid, opts ...Option) (*Token, error) {
iat := time.Now() iat := time.Now()
tkn := Token{ tkn := Token{
@@ -212,6 +216,24 @@ func (t *Token) IsValidAt(ti time.Time) bool {
return true return true
} }
func (t *Token) String() string {
var res strings.Builder
res.WriteString(fmt.Sprintf("Issuer: %s\n", t.Issuer()))
res.WriteString(fmt.Sprintf("Audience: %s\n", t.Audience()))
res.WriteString(fmt.Sprintf("Subject: %v\n", t.Subject()))
res.WriteString(fmt.Sprintf("Command: %s\n", t.Command()))
res.WriteString(fmt.Sprintf("Args: %s\n", t.Arguments()))
res.WriteString(fmt.Sprintf("Proof: %v\n", t.Proof()))
res.WriteString(fmt.Sprintf("Nonce: %s\n", base64.StdEncoding.EncodeToString(t.Nonce())))
res.WriteString(fmt.Sprintf("Meta: %s\n", t.Meta()))
res.WriteString(fmt.Sprintf("Expiration: %v\n", t.Expiration()))
res.WriteString(fmt.Sprintf("Invoked At: %v\n", t.InvokedAt()))
res.WriteString(fmt.Sprintf("Cause: %v", t.Cause()))
return res.String()
}
func (t *Token) validate() error { func (t *Token) validate() error {
var errs error var errs error

View File

@@ -129,7 +129,7 @@ func TestToken_ExecutionAllowed(t *testing.T) {
testFails(t, invocation.ErrWrongSub, didtest.PersonaFrank, delegationtest.ExpandedCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank_InvalidSubject) testFails(t, invocation.ErrWrongSub, didtest.PersonaFrank, delegationtest.ExpandedCommand, emptyArguments, delegationtest.ProofAliceBobCarolDanErinFrank_InvalidSubject)
}) })
t.Run("passes - arguments satify example policy", func(t *testing.T) { t.Run("passes - arguments satisfy example policy", func(t *testing.T) {
t.Parallel() t.Parallel()
testFails(t, invocation.ErrPolicyNotSatisfied, didtest.PersonaFrank, delegationtest.NominalCommand, policytest.SpecInvalidArguments, delegationtest.ProofAliceBobCarolDanErinFrank_ValidExamplePolicy) testFails(t, invocation.ErrPolicyNotSatisfied, didtest.PersonaFrank, delegationtest.NominalCommand, policytest.SpecInvalidArguments, delegationtest.ProofAliceBobCarolDanErinFrank_ValidExamplePolicy)
@@ -142,7 +142,7 @@ func test(t *testing.T, persona didtest.Persona, cmd command.Command, args *args
opts = append(opts, invocation.WithArguments(args)) opts = append(opts, invocation.WithArguments(args))
tkn, err := invocation.New(persona.DID(), didtest.PersonaAlice.DID(), cmd, prf, opts...) tkn, err := invocation.New(persona.DID(), cmd, didtest.PersonaAlice.DID(), prf, opts...)
require.NoError(t, err) require.NoError(t, err)
return tkn.ExecutionAllowed(delegationtest.GetDelegationLoader()) return tkn.ExecutionAllowed(delegationtest.GetDelegationLoader())

View File

@@ -16,7 +16,7 @@ func TestSealUnsealRoundtrip(t *testing.T) {
privKey, iss, sub, cmd, args, prf, meta, err := setupExampleNew() privKey, iss, sub, cmd, args, prf, meta, err := setupExampleNew()
require.NoError(t, err) require.NoError(t, err)
tkn1, err := invocation.New(iss, sub, cmd, prf, tkn1, err := invocation.New(iss, cmd, sub, prf,
invocation.WithArgument("uri", args["uri"]), invocation.WithArgument("uri", args["uri"]),
invocation.WithArgument("headers", args["headers"]), invocation.WithArgument("headers", args["headers"]),
invocation.WithArgument("payload", args["payload"]), invocation.WithArgument("payload", args["payload"]),

View File

@@ -38,11 +38,15 @@ func WithArguments(args *args.Args) Option {
// WithAudience sets the Token's audience to the provided did.DID. // WithAudience sets the Token's audience to the provided did.DID.
// //
// This can be used if the resource on which the token operates on is different
// from the subject. In that situation, the subject is akin to the "service" and
// the audience is akin to the resource.
//
// If the provided did.DID is the same as the Token's subject, the // If the provided did.DID is the same as the Token's subject, the
// audience is not set. // audience is not set.
func WithAudience(aud did.DID) Option { func WithAudience(aud did.DID) Option {
return func(t *Token) error { return func(t *Token) error {
if t.subject.String() != aud.String() { if t.subject != aud {
t.audience = aud t.audience = aud
} }

View File

@@ -40,7 +40,7 @@ import (
// c. The first proof must be issued to the Invoker (audience DID). // c. The first proof must be issued to the Invoker (audience DID).
// d. The Issuer of each delegation must be the Audience in the next one. // d. The Issuer of each delegation must be the Audience in the next one.
// e. The last token must be a root delegation. // e. The last token must be a root delegation.
// f. The Subject of each delegation must equal the invocation's Audience field. // f. The Subject of each delegation must equal the invocation's Subject (or Audience if defined)
// g. The command of each delegation must "allow" the one before it. // g. The command of each delegation must "allow" the one before it.
// //
// 5. If steps 1-4 pass: // 5. If steps 1-4 pass:
@@ -58,18 +58,18 @@ func (t *Token) verifyProofs(delegations []*delegation.Token) error {
cmd := t.command cmd := t.command
iss := t.issuer iss := t.issuer
aud := t.audience sub := t.subject
if !aud.Defined() { if t.audience.Defined() {
aud = t.subject sub = t.audience
} }
// control from the invocation to the root // control from the invocation to the root
for i, dlgCid := range t.proof { for i, dlgCid := range t.proof {
dlg := delegations[i] dlg := delegations[i]
// The Subject of each delegation must equal the invocation's Audience field. - 4f // The Subject of each delegation must equal the invocation's Subject (or Audience if defined). - 4f
if dlg.Subject() != aud { if dlg.Subject() != sub {
return fmt.Errorf("%w: delegation %s, expected %s, got %s", ErrWrongSub, dlgCid, aud, dlg.Subject()) return fmt.Errorf("%w: delegation %s, expected %s, got %s", ErrWrongSub, dlgCid, sub, dlg.Subject())
} }
// The first proof must be issued to the Invoker (audience DID). - 4c // The first proof must be issued to the Invoker (audience DID). - 4c