Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
4.9 kB · 142 lines
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143package reposync
import ( "bytes" "context" "fmt"
indigoat "github.com/bluesky-social/indigo/api/atproto" "github.com/bluesky-social/indigo/atproto/identity" "github.com/bluesky-social/indigo/atproto/repo" "github.com/bluesky-social/indigo/atproto/syntax" "github.com/bluesky-social/indigo/xrpc" "github.com/ipfs/go-cid")
// Head is a repo's current commit, after signature verification. Everything// reachable from Root is authenticated by Commit.Sig.type Head struct { // Commit is the decoded, structurally valid, correctly signed commit. Commit *repo.Commit // CID is the CID of the commit block itself. CID cid.Cid // Root is the CID of the root MST node (Commit.Data). Root cid.Cid // Rev is the commit revision (a TID). Rev string}
// LatestCommit asks a host which commit a repo is on, and nothing more: one// request, no blocks fetched, no signature checked.//// The answer is therefore the host's word rather than proof. That is enough to// tell "our index is at the same rev as the host" from "it is not", which is// all a drift check needs -- and a check that finds drift hands the repo to the// fully verified walk in [FetchVerifiedHead], so nothing gets indexed on the// strength of this call.//// At most one retry policy may be given; omitting it uses the package defaults.func LatestCommit(ctx context.Context, client *xrpc.Client, did string, retry ...RetryPolicy) (*indigoat.SyncGetLatestCommit_Output, error) { if len(retry) > 1 { return nil, fmt.Errorf("at most one retry policy, got %d", len(retry)) } var policy RetryPolicy if len(retry) == 1 { policy = retry[0] } policy = policy.forHost(client.Host)
var latest *indigoat.SyncGetLatestCommit_Output err := policy.do(ctx, "com.atproto.sync.getLatestCommit "+did, func() error { var err error latest, err = indigoat.SyncGetLatestCommit(ctx, client, did) return err }) if err != nil { return nil, fmt.Errorf("com.atproto.sync.getLatestCommit for %s: %w", did, err) } return latest, nil}
// FetchVerifiedHead resolves a repo's current commit and proves it belongs to// did.//// It asks the host for the latest commit CID, pulls that block through f (which// verifies the bytes against the CID), decodes it, and checks the commit's// structure, DID, rev and signature against the account's atproto signing key// from dir. On success, nothing the host says about the repo below Head.Root can// be forged.//// At most one retry policy may be given; it applies to the getLatestCommit call// (the block fetch carries its own). Omitting it uses the package defaults.func FetchVerifiedHead(ctx context.Context, client *xrpc.Client, f BlockFetcher, dir identity.Directory, did string, retry ...RetryPolicy) (*Head, error) { if len(retry) > 1 { return nil, fmt.Errorf("at most one retry policy, got %d", len(retry)) } var policy RetryPolicy if len(retry) == 1 { policy = retry[0] } policy = policy.forHost(client.Host)
parsedDID, err := syntax.ParseDID(did) if err != nil { return nil, fmt.Errorf("invalid did %q: %w", did, err) }
latest, err := LatestCommit(ctx, client, did, policy) if err != nil { return nil, err } commitCID, err := cid.Decode(latest.Cid) if err != nil { return nil, fmt.Errorf("undecodable commit cid %q for %s: %w", latest.Cid, did, err) }
blocks, err := f.GetBlocks(ctx, []cid.Cid{commitCID}) if err != nil { return nil, fmt.Errorf("fetching commit block for %s: %w", did, err) } raw, ok := blocks[commitCID] if !ok { return nil, fmt.Errorf("%w: commit %s for %s", ErrMissingBlock, commitCID, did) } // Don't take the fetcher's word for it: the commit CID is the anchor for the // whole walk, so re-verify it here regardless of which fetcher we were given. if err := VerifyBlock(commitCID, raw); err != nil { return nil, fmt.Errorf("commit block for %s: %w", did, err) }
var commit repo.Commit if err := commit.UnmarshalCBOR(bytes.NewReader(raw)); err != nil { return nil, fmt.Errorf("decoding commit %s for %s: %w", commitCID, did, err) } if err := commit.VerifyStructure(); err != nil { return nil, fmt.Errorf("commit %s for %s: %w", commitCID, did, err) } if commit.DID != did { return nil, fmt.Errorf("commit %s is for repo %s, not %s", commitCID, commit.DID, did) } if commit.Rev != latest.Rev { return nil, fmt.Errorf("commit %s has rev %q, host reported %q for %s", commitCID, commit.Rev, latest.Rev, did) }
ident, err := dir.LookupDID(ctx, parsedDID) if err != nil { return nil, fmt.Errorf("resolving identity for %s: %w", did, err) } pubkey, err := ident.PublicKey() if err != nil { return nil, fmt.Errorf("no atproto signing key for %s: %w", did, err) } if err := commit.VerifySignature(pubkey); err != nil { return nil, fmt.Errorf("commit %s signature for %s: %w", commitCID, did, err) }
return &Head{ Commit: &commit, CID: commitCID, Root: commit.Data, Rev: commit.Rev, }, nil}