Skip to content

Commit 1ab1a31

Browse files
alanshawfrrist
authored andcommitted
feat: add attestation verifier
1 parent 7ca249a commit 1ab1a31

3 files changed

Lines changed: 67 additions & 15 deletions

File tree

go.sum

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -553,14 +553,10 @@ github.com/fil-forge/go-ipni-tools v0.0.0-20260519194815-545b9421aec0 h1:HAfXUPv
553553
github.com/fil-forge/go-ipni-tools v0.0.0-20260519194815-545b9421aec0/go.mod h1:3NRV/7wc4/0uzzrGdI7NoN/yeF1UvqKRwMyjBqGc5s0=
554554
github.com/fil-forge/go-ucanto v0.0.0-20260507172450-5cb5d073f8ab h1:2J2cDThqTKP6/0k3SfdlSxfyPa3aLqjTYnmvbEcryfg=
555555
github.com/fil-forge/go-ucanto v0.0.0-20260507172450-5cb5d073f8ab/go.mod h1:lZF3UXZ2hGLKYmXdquG50JqI9pRlUrV6lubGtgOYfwc=
556-
github.com/fil-forge/libforge v0.0.0-20260527084616-1c83212df033 h1:Orw344bJoiOnYYXVkt6sH573hYBl/hpyuNri6CeArNY=
557-
github.com/fil-forge/libforge v0.0.0-20260527084616-1c83212df033/go.mod h1:1ytnrneNEeJcskEbsRDtNZY/Jvgo2Yw5szIUI/9EWPk=
558556
github.com/fil-forge/libforge v0.0.0-20260527182359-ebb22552c348 h1:roYe4llNv0fpQnvXMontrdt/hy9kH5K/cnNsXmhqId4=
559557
github.com/fil-forge/libforge v0.0.0-20260527182359-ebb22552c348/go.mod h1:1ytnrneNEeJcskEbsRDtNZY/Jvgo2Yw5szIUI/9EWPk=
560558
github.com/fil-forge/piri-signing-service v0.0.0-20260527011208-918512802357 h1:sTK8Yc/kds7MkG0cpK5DGDtDCtU1ZwgQWc4OzRf3ZP4=
561559
github.com/fil-forge/piri-signing-service v0.0.0-20260527011208-918512802357/go.mod h1:fgg5hE/BlnFlR5qXsT0POv6GWVu7cj526s7v2+mdJXo=
562-
github.com/fil-forge/ucantone v0.0.0-20260522152152-eda937bc2684 h1:kWJLKVltJXPXO7tKS1z0GhzA+c59gwhediGyByEXE0o=
563-
github.com/fil-forge/ucantone v0.0.0-20260522152152-eda937bc2684/go.mod h1:XAVqsZwYoZ9vncjZoRUAJ+mL/ApLMFn9HHX7ipohVdY=
564560
github.com/fil-forge/ucantone v0.0.0-20260527115858-517b03bc3c72 h1:FFK4CC7IfLfwa5ZQExdkZayPc6Tg0JgVK4jhn2hd2cY=
565561
github.com/fil-forge/ucantone v0.0.0-20260527115858-517b03bc3c72/go.mod h1:xQ1oQ2UgA8xFpNxpyj2v+jiW2KVDAi90xjy1OjUkNXI=
566562
github.com/filecoin-project/filecoin-ffi v1.34.0 h1:OvcsvsFUCwzLOGT949dsJEqSLyGx4d8TPPRrmrzlQbk=

pkg/service/publisher/publisher_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ func TestPublisherService(t *testing.T) {
141141
DID: testutil.Bob.DID(),
142142
Client: httpClient,
143143
}),
144-
WithIndexingServiceProof(proof),
144+
WithIndexingServiceProof([]ucan.Delegation{proof}),
145145
)
146146
require.NoError(t, err)
147147

pkg/ucanhandlers/ucanfx/fx.go

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
package ucanfx
22

33
import (
4+
"bytes"
45
"context"
56
"fmt"
67
"time"
78

9+
"github.com/fil-forge/libforge/commands/ucan/attest"
810
"github.com/fil-forge/libforge/didresolver"
911
"github.com/fil-forge/ucantone/did"
1012
"github.com/fil-forge/ucantone/principal/verifier"
1113
"github.com/fil-forge/ucantone/ucan"
14+
ucantoken "github.com/fil-forge/ucantone/ucan/token"
1215
"github.com/fil-forge/ucantone/validator"
1316
"go.uber.org/fx"
1417

@@ -47,14 +50,11 @@ var Module = fx.Module("ucan",
4750
)
4851
if cfg.InsecureDIDResolution {
4952
httpResolver, err = didresolver.NewHTTPResolver(didresolver.InsecureResolution())
50-
if err != nil {
51-
return nil, fmt.Errorf("could not create http resolver: %w", err)
52-
}
5353
} else {
5454
httpResolver, err = didresolver.NewHTTPResolver()
55-
if err != nil {
56-
return nil, fmt.Errorf("could not create http resolver: %w", err)
57-
}
55+
}
56+
if err != nil {
57+
return nil, fmt.Errorf("could not create http resolver: %w", err)
5858
}
5959

6060
cachedRes, err := didresolver.NewCachedResolver(httpResolver.Resolve, 24*time.Hour)
@@ -73,6 +73,11 @@ var Module = fx.Module("ucan",
7373
}, nil
7474
},
7575

76+
// Trust attestations issued by the Forge upload service
77+
func(cfg app.UCANServiceConfig, resolvers validator.VerifierResolverMap) validator.NonStandardSignatureVerifierFunc {
78+
return newAttestationVerifier(cfg.Services.Upload.DID, resolvers)
79+
},
80+
7681
// Server-wide options. Both transports need the DID verifier
7782
// resolvers so they can validate UCANs signed by did:web identities
7883
// (e.g. did:web:indexer, did:web:upload). Without the retrieval
@@ -84,11 +89,17 @@ var Module = fx.Module("ucan",
8489
// hidden in the X-UCAN-Container header — which downstream
8590
// clients (the indexer's blobindexlookup) mis-read as
8691
// success-with-empty-body and then choke on CAR decode EOF.
87-
ucanhandlers.ProvideRPCOption(func(resolver validator.VerifierResolverMap) server.HTTPOption {
88-
return server.WithValidationOptions(validator.WithDIDVerifierResolvers(resolver))
92+
ucanhandlers.ProvideRPCOption(func(resolver validator.VerifierResolverMap, verifyNonStandardSig validator.NonStandardSignatureVerifierFunc) server.HTTPOption {
93+
return server.WithValidationOptions(
94+
validator.WithDIDVerifierResolvers(resolver),
95+
validator.WithNonStandardSignatureVerifier(verifyNonStandardSig),
96+
)
8997
}),
90-
ucanhandlers.ProvideRetrievalOption(func(resolver validator.VerifierResolverMap) server.HTTPOption {
91-
return server.WithValidationOptions(validator.WithDIDVerifierResolvers(resolver))
98+
ucanhandlers.ProvideRetrievalOption(func(resolver validator.VerifierResolverMap, verifyNonStandardSig validator.NonStandardSignatureVerifierFunc) server.HTTPOption {
99+
return server.WithValidationOptions(
100+
validator.WithDIDVerifierResolvers(resolver),
101+
validator.WithNonStandardSignatureVerifier(verifyNonStandardSig),
102+
)
92103
}),
93104
),
94105

@@ -98,3 +109,48 @@ var Module = fx.Module("ucan",
98109
content.Module,
99110
pdp.Module,
100111
)
112+
113+
// newAttestationVerifier creates a [validator.NonStandardSignatureVerifierFunc]
114+
// that validates that a delegation is attested by the given authority.
115+
func newAttestationVerifier(authority did.DID, resolvers validator.VerifierResolverMap) validator.NonStandardSignatureVerifierFunc {
116+
return func(ctx context.Context, token ucan.Token, meta ucan.Container) error {
117+
resolver, ok := resolvers[authority.Method()]
118+
if !ok {
119+
return fmt.Errorf("no resolver for DID method: %s", authority.Method())
120+
}
121+
verifier, err := resolver(ctx, authority)
122+
if err != nil {
123+
return fmt.Errorf("could not resolve DID: %w", err)
124+
}
125+
// We only support attestations as delegations - attested delegation MUST
126+
// delegate to an agent DID which is then used in the invocation.
127+
dlg, ok := token.(ucan.Delegation)
128+
if !ok {
129+
return fmt.Errorf("token is not a delegation")
130+
}
131+
for _, inv := range meta.Invocations() {
132+
if inv.Command() != attest.Proof.Command {
133+
continue
134+
}
135+
// only trust attestations authority issued
136+
if inv.Issuer() != authority || inv.Subject() == did.Undef || inv.Subject() != authority {
137+
continue
138+
}
139+
var args attest.ProofArguments
140+
if err := args.UnmarshalCBOR(bytes.NewReader(inv.ArgumentsBytes())); err != nil {
141+
continue
142+
}
143+
// make sure the attestation is for the delegation in question
144+
if args.Proof != dlg.Link() {
145+
continue
146+
}
147+
// finally, make sure the signature is valid
148+
ok, err := ucantoken.VerifySignature(inv, verifier)
149+
if !ok || err != nil {
150+
continue
151+
}
152+
return nil
153+
}
154+
return fmt.Errorf("no valid attestation found for delegation")
155+
}
156+
}

0 commit comments

Comments
 (0)