11package ucanfx
22
33import (
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