diff --git a/pkg/principalresolver/httpresolver.go b/pkg/principalresolver/httpresolver.go index 20a71771..f5404992 100644 --- a/pkg/principalresolver/httpresolver.go +++ b/pkg/principalresolver/httpresolver.go @@ -176,8 +176,6 @@ func NewHTTPResolver(webKeys []did.DID, opts ...Option) (*HTTPResolver, error) { return resolver, nil } -// TODO(forrest): the interface this implements in go-ucanto should probably accept a context -// since means of resolution here are open ended, and may go to network or disk. func (r *HTTPResolver) ResolveDIDKey(ctx context.Context, input did.DID) (did.DID, validator.UnresolvedDID) { endpoint, ok := r.webKeys[input] if !ok { diff --git a/pkg/service/publisher/publisher.go b/pkg/service/publisher/publisher.go index 29bd2582..aea27acf 100644 --- a/pkg/service/publisher/publisher.go +++ b/pkg/service/publisher/publisher.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "slices" + "strings" "sync" "github.com/ipfs/go-cid" @@ -26,8 +27,14 @@ import ( "github.com/storacha/go-ucanto/core/receipt" "github.com/storacha/go-ucanto/core/result" "github.com/storacha/go-ucanto/core/result/ok" + "github.com/storacha/go-ucanto/did" "github.com/storacha/go-ucanto/principal" + edverifier "github.com/storacha/go-ucanto/principal/ed25519/verifier" + "github.com/storacha/go-ucanto/principal/verifier" + "github.com/storacha/go-ucanto/ucan" + "github.com/storacha/go-ucanto/validator" "github.com/storacha/piri/lib" + "github.com/storacha/piri/pkg/principalresolver" "github.com/storacha/go-libstoracha/advertisement" ) @@ -286,6 +293,67 @@ func New( }, nil } +func validateClaimCacheDelegation(ctx context.Context, id ucan.Signer, serviceID ucan.Principal, proofs []delegation.Proof, options ...principalresolver.Option) error { + var authority principal.Verifier + var principalResolver validator.PrincipalResolverFunc + if strings.HasPrefix(serviceID.DID().String(), "did:web:") { + reslv, err := principalresolver.NewHTTPResolver([]did.DID{serviceID.DID()}, options...) + if err != nil { + return fmt.Errorf("creating HTTP resolver: %w", err) + } + didkey, err := reslv.ResolveDIDKey(ctx, serviceID.DID()) + if err != nil { + return fmt.Errorf("resolving indexing service DID: %w", err) + } + v, err := edverifier.Decode(didkey.Bytes()) + if err != nil { + return fmt.Errorf("decoding indexing service DID: %w", err) + } + authority, err = verifier.Wrap(v, serviceID.DID()) + if err != nil { + return fmt.Errorf("wrapping indexing service DID: %w", err) + } + principalResolver = reslv.ResolveDIDKey + } else { + v, err := edverifier.Decode(serviceID.DID().Bytes()) + if err != nil { + return fmt.Errorf("decoding indexing service DID: %w", err) + } + authority = v + principalResolver = validator.FailDIDKeyResolution + } + + vctx := validator.NewValidationContext( + authority, + claim.Cache, + validator.IsSelfIssued, + // TODO: check revocation status when revocation API is implemented. + func(ctx context.Context, auth validator.Authorization[any]) validator.Revoked { + return nil + }, + validator.ProofUnavailable, + edverifier.Parse, + principalResolver, + ) + + placeholder, _ := cid.Parse("bafkqaaa") + inv, err := claim.Cache.Invoke( + id, + serviceID, + serviceID.DID().String(), + claim.CacheCaveats{ + Claim: cidlink.Link{Cid: placeholder}, + }, + delegation.WithProof(proofs...), + ) + if err != nil { + return fmt.Errorf("creating claim cache invocation: %w", err) + } + + _, err = validator.Access(ctx, inv, vctx) + return err +} + func providerInfo(peerID peer.ID, publicAddr multiaddr.Multiaddr, blobAddr multiaddr.Multiaddr) (peer.AddrInfo, error) { provider := peer.AddrInfo{ID: peerID} if blobAddr == nil { diff --git a/pkg/service/publisher/publisher_test.go b/pkg/service/publisher/publisher_test.go index db4aae8a..68e2cafe 100644 --- a/pkg/service/publisher/publisher_test.go +++ b/pkg/service/publisher/publisher_test.go @@ -2,8 +2,12 @@ package publisher import ( "context" + "encoding/json" "fmt" + "net/http" + "net/http/httptest" "net/url" + "strings" "testing" "github.com/ipfs/go-datastore" @@ -24,10 +28,13 @@ import ( "github.com/storacha/go-ucanto/core/result" "github.com/storacha/go-ucanto/core/result/failure" "github.com/storacha/go-ucanto/core/result/ok" + "github.com/storacha/go-ucanto/did" "github.com/storacha/go-ucanto/principal" + "github.com/storacha/go-ucanto/principal/signer" "github.com/storacha/go-ucanto/server" "github.com/storacha/go-ucanto/ucan" "github.com/storacha/piri/pkg/internal/digestutil" + "github.com/storacha/piri/pkg/principalresolver" "github.com/storacha/piri/pkg/service/publisher/advertisement" "github.com/stretchr/testify/require" ) @@ -214,3 +221,49 @@ func mockIndexingService(t *testing.T, id principal.Signer, handler server.Handl ), )(t) } + +func TestValidateClaimCacheDelegation(t *testing.T) { + var serviceID principal.Signer + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != principalresolver.WellKnownDIDPath { + w.WriteHeader(http.StatusNotFound) + return + } + + doc := principalresolver.Document{ + Context: principalresolver.FlexibleContext{"https://w3id.org/did/v1", "https://w3id.org/security/v1"}, + ID: serviceID.DID().String(), + VerificationMethod: []principalresolver.VerificationMethod{ + { + ID: serviceID.DID().String() + "#key1", + Type: "Ed25519VerificationKey2018", + Controller: serviceID.DID().String(), + PublicKeyMultibase: strings.TrimPrefix(testutil.Service.DID().String(), "did:key:"), + }, + }, + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(doc) + })) + defer server.Close() + + didWeb, err := did.Parse("did:web:" + testutil.Must(url.Parse(server.URL))(t).Host) + require.NoError(t, err) + + serviceID, err = signer.Wrap(testutil.Service, didWeb) + require.NoError(t, err) + + dlg, err := delegation.Delegate( + serviceID, + testutil.Alice, + []ucan.Capability[ucan.NoCaveats]{ + ucan.NewCapability(claim.CacheAbility, serviceID.DID().String(), ucan.NoCaveats{}), + }, + ) + require.NoError(t, err) + + proofs := []delegation.Proof{delegation.FromDelegation(dlg)} + + err = validateClaimCacheDelegation(t.Context(), testutil.Alice, serviceID, proofs, principalresolver.InsecureResolution()) + require.NoError(t, err) +}