1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package relay
import (
"bytes"
"context"
"errors"
"fmt"
"log/slog"
"time"
comatproto "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"
)
const (
MaxMessageBlocksBytes = 2_000_000
MaxCommitOps = 200
futureRevTolerance = 5 * time.Minute
)
var (
ErrFutureRev = errors.New("relay: commit revision in the future")
ErrRevSequence = errors.New("relay: commit revision out of order")
)
type verifier struct {
dir identity.Directory
log *slog.Logger
}
func (v *verifier) verify(ctx context.Context, evt *comatproto.SyncSubscribeRepos_Commit, prev *AccountInfo, hostname string) error {
if len(evt.Blocks) > MaxMessageBlocksBytes {
return fmt.Errorf("blocks size (%d bytes) exceeds protocol limit", len(evt.Blocks))
}
if len(evt.Ops) > MaxCommitOps {
return fmt.Errorf("too many ops in commit: %d", len(evt.Ops))
}
if len(evt.Blocks) == 0 {
return fmt.Errorf("commit message has no blocks")
}
commit, _, err := repo.LoadCommitFromCAR(ctx, bytes.NewReader(evt.Blocks))
if err != nil {
return err
}
if err := commit.VerifyStructure(); err != nil {
return err
}
rev, err := syntax.ParseTID(commit.Rev)
if err != nil {
return fmt.Errorf("commit rev syntax: %w", err)
}
if rev.Time().Compare(time.Now().Add(futureRevTolerance)) > 0 {
return fmt.Errorf("%w: %s", ErrFutureRev, rev)
}
did, err := syntax.ParseDID(evt.Repo)
if err != nil {
return err
}
ident, err := v.dir.LookupDID(ctx, did)
if err != nil {
return fmt.Errorf("resolve identity %s: %w", did, err)
}
pubkey, err := ident.PublicKey()
if err != nil {
return fmt.Errorf("commit verification: %w", err)
}
if err := commit.VerifySignature(pubkey); err != nil {
signatureErr := err
if purgeErr := v.dir.Purge(ctx, did.AtIdentifier()); purgeErr != nil {
v.log.Warn("failed to purge a DID whose commit signature did not verify",
"did", did.String(), "err", purgeErr)
return fmt.Errorf("commit verification: %w", signatureErr)
}
ident, err = v.dir.LookupDID(ctx, did)
if err != nil {
return fmt.Errorf("commit verification: %w", signatureErr)
}
pubkey, err = ident.PublicKey()
if err != nil {
return fmt.Errorf("commit verification: %w", signatureErr)
}
if err := commit.VerifySignature(pubkey); err != nil {
return fmt.Errorf("commit verification: %w", err)
}
}
if evt.Repo != commit.DID {
return fmt.Errorf("mismatched inner commit DID field: %s", commit.DID)
}
if evt.Rev != commit.Rev {
return fmt.Errorf("mismatched inner commit rev field: %s", commit.Rev)
}
if prev != nil && prev.Rev != nil {
if evt.Rev <= *prev.Rev {
return fmt.Errorf("%w: %s before or equal to %s", ErrRevSequence, evt.Rev, *prev.Rev)
}
}
if evt.TooBig {
return fmt.Errorf("deprecated tooBig commit flag set")
}
if evt.Rebase {
return fmt.Errorf("deprecated rebase commit flag set")
}
if _, err := syntax.ParseDatetime(evt.Time); err != nil {
return fmt.Errorf("commit timestamp syntax: %w", err)
}
if _, err := repo.VerifyCommitMessage(ctx, evt); err != nil {
v.log.Warn("failed to invert commit MST", "host", hostname, "did", evt.Repo, "rev", evt.Rev, "err", err)
}
return nil
}