Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions attestation/signer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package attestation_test

import (
"bytes"
"context"
"testing"

"github.com/ipfs/go-cid"
Expand All @@ -21,6 +22,58 @@ import (
"github.com/fil-forge/libforge/testutil"
)

// TestChainedAttestation verifies that attested signatures chain correctly when
// the authority is itself a did:mailto attested by a root key. In other words,
// this demonstrates that the signature is validated as an invocation using the
// same DID resolvers and verifier factories that the top `ValidateToken` got.
func TestChainedAttestation(t *testing.T) {
// root signs with real ed25519 keys (did:key)
root := testutil.RandomIssuer(t)

// service is a did:mailto DID that signs via attestation from root
serviceDID, err := did.Parse("did:mailto:service.example.com:svc")
require.NoError(t, err)
service := attestation.Attest(t.Context(), serviceDID, root)

// alice is a did:mailto DID that signs via attestation from service
aliceDID, err := did.Parse("did:mailto:example.com:alice")
require.NoError(t, err)
alice := attestation.Attest(t.Context(), aliceDID, service)

del, err := delegation.Delegate(
alice,
testutil.RandomDID(t),
alice.DID(),
command.MustParse("/example/command"),
)
require.NoError(t, err)

encoded, err := delegation.Encode(del)
require.NoError(t, err)

decoded, err := delegation.Decode(encoded)
require.NoError(t, err)

// The resolver needs to know both mailto DIDs with their distinct authorities.
resolver := did.ResolverMap{
"key": key.Resolver,
"mailto": did.ResolverFunc(func(ctx context.Context, d did.DID) (did.Document, error) {
authority := serviceDID // default: alice and others attested by service
if d == serviceDID {
authority = root.DID() // service itself is attested by root
}
return didmailto.NewResolver(authority).Resolve(ctx, d)
}),
}
factories := validator.DefaultFactories()
factories[attestation.Type] = attestation.NewVerifierFactory(resolver, factories)
err = validator.ValidateToken(t.Context(), decoded,
validator.WithDIDResolver(resolver),
validator.WithVerifierFactories(factories),
)
require.NoError(t, err)
}

func TestSigner(t *testing.T) {
authority := testutil.RandomIssuer(t)
alice, err := did.Parse("did:mailto:example.com:alice")
Expand Down
5 changes: 4 additions & 1 deletion attestation/verificationmethod.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ func NewVerifierFactory(resolver did.Resolver, verifierFactories map[string]vali
if err != nil {
return nil, fmt.Errorf("failed to derive multi-verifier: %w", err)
}
return AttestedVerifier(ctx, authorityDid, v), nil
return AttestedVerifier(ctx, authorityDid, v,
validator.WithDIDResolver(resolver),
validator.WithVerifierFactories(verifierFactories),
), nil
}
}

Expand Down
6 changes: 4 additions & 2 deletions attestation/verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type Verifier struct {
ctx context.Context
authorityID did.DID
authorityVerifier ucan.Verifier
validationOptions []validator.Option
}

var _ ucan.Verifier = Verifier{}
Expand Down Expand Up @@ -54,17 +55,18 @@ func (v Verifier) Verify(msg []byte, sig []byte) bool {
return false
}

if validator.ValidateInvocation(v.ctx, inv) != nil {
if validator.ValidateInvocation(v.ctx, inv, v.validationOptions...) != nil {
return false
}

return true
}

func AttestedVerifier(ctx context.Context, authorityID did.DID, authority ucan.Verifier) ucan.Verifier {
func AttestedVerifier(ctx context.Context, authorityID did.DID, authority ucan.Verifier, options ...validator.Option) ucan.Verifier {
return Verifier{
ctx: ctx,
authorityID: authorityID,
authorityVerifier: authority,
validationOptions: options,
}
}
Loading