Skip to content
Merged
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
16 changes: 8 additions & 8 deletions didresolver/cacheresolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,36 +271,36 @@ func TestCachedResolver_WithMapResolver(t *testing.T) {
// Test alice
aliceDID, err := did.Parse("did:web:alice.example.com")
require.NoError(t, err)
aliceKey, err := verifier.Parse("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK")
require.NoError(t, err)

// MapResolver now wraps verifiers as the requested DID — see
// ucantone/ucan/token/token.go for why. The cached verifier's DID()
// should match the input DID, not the underlying did:key.

// First call - should hit MapResolver
result1, err1 := cachedResolver.Resolve(t.Context(), aliceDID)
require.Nil(t, err1)
require.Equal(t, aliceKey, result1)
require.Equal(t, aliceDID, result1.DID())

// Second call - should use cache (we can't directly verify this without instrumentation)
result2, err2 := cachedResolver.Resolve(t.Context(), aliceDID)
require.Nil(t, err2)
require.Equal(t, aliceKey, result2)
require.Equal(t, aliceDID, result2.DID())

// Test bob while alice is still cached
bobDID, err := did.Parse("did:web:bob.example.com")
require.NoError(t, err)
bobKey, err := verifier.Parse("did:key:z6Mkfriq1MqLBoPWecGoDLjguo1sB9brj6wT3qZ5BxkKpuP6")
require.NoError(t, err)

result3, err3 := cachedResolver.Resolve(t.Context(), bobDID)
require.Nil(t, err3)
require.Equal(t, bobKey, result3)
require.Equal(t, bobDID, result3.DID())

// Wait for cache to expire
time.Sleep(250 * time.Millisecond)

// Alice's entry should have expired, this should hit MapResolver again
result4, err4 := cachedResolver.Resolve(t.Context(), aliceDID)
require.Nil(t, err4)
require.Equal(t, aliceKey, result4)
require.Equal(t, aliceDID, result4.DID())

// Test non-existent DID
unknownDID, err := did.Parse("did:web:unknown.example.com")
Expand Down
12 changes: 11 additions & 1 deletion didresolver/httpresolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/fil-forge/ucantone/did"
"github.com/fil-forge/ucantone/principal/ed25519/verifier"
pverifier "github.com/fil-forge/ucantone/principal/verifier"
"github.com/fil-forge/ucantone/ucan"
verrs "github.com/fil-forge/ucantone/validator/errors"
"github.com/gobwas/glob"
Expand Down Expand Up @@ -223,7 +224,16 @@ func (r *HTTPResolver) Resolve(ctx context.Context, input did.DID) (ucan.Verifie
return nil, verrs.NewDIDKeyResolutionError(input, fmt.Errorf("parsing multibase key: %w", err))
}

return didKey, nil
// token.VerifySignature compares the token's Issuer DID against the
// verifier's DID — if the issuer is did:web:foo and we return an unwrapped
// did:key verifier, that equality check fails and the signature is
// rejected before the bytes are even examined. Wrap so the verifier
// announces the originally-requested DID.
wrapped, err := pverifier.Wrap(didKey, input)
if err != nil {
return nil, verrs.NewDIDKeyResolutionError(input, fmt.Errorf("wrapping verifier as %s: %w", input, err))
}
return wrapped, nil
}

func fetchDIDDocument(ctx context.Context, endpoint url.URL) (*Document, error) {
Expand Down
29 changes: 25 additions & 4 deletions didresolver/httpresolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/fil-forge/libforge/didresolver"
"github.com/fil-forge/ucantone/did"
"github.com/fil-forge/ucantone/principal"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -279,9 +280,22 @@ func TestHTTPResolver_ResolveDIDKey(t *testing.T) {
}
} else {
require.Nil(t, unresolvedErr)
expectedDID, err := did.Parse(tc.expectedDIDKey)
// The resolver wraps the underlying did:key verifier so it
// announces the originally-requested DID — required for
// ucantone token.VerifySignature, which compares the token's
// issuer DID against the verifier's DID before checking
// signature bytes.
require.Equal(t, inputDID, result.DID())
// expectedDIDKey identifies the underlying did:key the
// resolver should have extracted from the document; reach
// through Unwrap() to assert it.
expectedDIDKey, err := did.Parse(tc.expectedDIDKey)
require.NoError(t, err)
require.Equal(t, expectedDID, result.DID())
unwrapper, ok := result.(interface {
Unwrap() principal.Verifier
})
require.True(t, ok, "resolver should return a wrapped verifier")
require.Equal(t, expectedDIDKey, unwrapper.Unwrap().DID())
}
})
}
Expand Down Expand Up @@ -505,9 +519,16 @@ func TestHTTPResolver_ResolveDIDKey_ContextFormats(t *testing.T) {
result, unresolvedErr := resolver.Resolve(t.Context(), didWeb)
require.Nil(t, unresolvedErr)

expectedDID, err := did.Parse(tc.expectedDIDKey)
// Resolver wraps the underlying did:key as the requested did:web —
// see ucantone/ucan/token/token.go for why this matters.
require.Equal(t, didWeb, result.DID())
expectedDIDKey, err := did.Parse(tc.expectedDIDKey)
require.NoError(t, err)
require.Equal(t, expectedDID, result.DID())
unwrapper, ok := result.(interface {
Unwrap() principal.Verifier
})
require.True(t, ok, "resolver should return a wrapped verifier")
require.Equal(t, expectedDIDKey, unwrapper.Unwrap().DID())
})
}
}
Expand Down
16 changes: 15 additions & 1 deletion didresolver/mapresolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/fil-forge/ucantone/did"
"github.com/fil-forge/ucantone/principal/ed25519/verifier"
pverifier "github.com/fil-forge/ucantone/principal/verifier"
"github.com/fil-forge/ucantone/ucan"
verrs "github.com/fil-forge/ucantone/validator/errors"
)
Expand Down Expand Up @@ -33,10 +34,23 @@ func NewMapResolver(smap map[string]string) (*MapResolver, error) {
return nil, err
}
// TODO: multiple verification methods when https://github.com/fil-forge/ucantone/pull/7 lands
dv, err := verifier.Parse(v)
didKey, err := verifier.Parse(v)
if err != nil {
return nil, err
}
// token.VerifySignature compares the token's Issuer DID against the
// verifier's DID. If a did:web (or any non-key DID) maps to a did:key
// verifier, the equality check fails and signature verification is
// rejected before the bytes are even examined. Wrap the verifier so
// it announces the requested DID. did:key inputs are stored unwrapped.
var dv ucan.Verifier = didKey
if dk.Method() != "key" {
wrapped, err := pverifier.Wrap(didKey, dk)
if err != nil {
return nil, fmt.Errorf("wrapping verifier as %s: %w", dk, err)
}
dv = wrapped
}
dmap[dk] = dv
}
return &MapResolver{Mapping: dmap}, nil
Expand Down
5 changes: 4 additions & 1 deletion didresolver/mapresolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ func TestPrincipalResolver(t *testing.T) {

resolved, err := ppr.Resolve(t.Context(), p0)
require.NoError(t, err)
require.Equal(t, r, resolved.DID())
// Resolver wraps the underlying did:key verifier so it announces the
// requested did:web — required for ucantone token.VerifySignature, which
// compares issuer DID against verifier DID before checking signature bytes.
require.Equal(t, p0, resolved.DID())

// cannot resolve DID not in mapping
_, err = ppr.Resolve(t.Context(), p1)
Expand Down
8 changes: 5 additions & 3 deletions didresolver/tieredresolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,15 +154,17 @@ func TestTieredResolver_ResolveDIDKey(t *testing.T) {
Tiers: []didresolver.DIDVerifierResolverFunc{mapA.Resolve, mapB.Resolve},
}

// Resolves via the first tier
// Resolves via the first tier. MapResolver wraps the did:key verifier
// as the requested did:web so token.VerifySignature's issuer-vs-verifier
// DID equality check passes — see ucantone/ucan/token/token.go.
resA, err := resolver.Resolve(t.Context(), didA)
require.NoError(t, err)
require.Equal(t, keyA, resA)
require.Equal(t, didA, resA.DID())

// Falls through to the second tier
resB, err := resolver.Resolve(t.Context(), didB)
require.NoError(t, err)
require.Equal(t, keyB, resB)
require.Equal(t, didB, resB.DID())

// Not resolvable by any tier
_, err = resolver.Resolve(t.Context(), didC)
Expand Down
Loading