1package indexer
2
3import (
4 "context"
5 "fmt"
6 "log/slog"
7
8 did "github.com/whyrusleeping/go-did"
9 "go.opentelemetry.io/otel"
10)
11
12type KeyManager struct {
13 didr DidResolver
14
15 signingKey *did.PrivKey
16
17 log *slog.Logger
18}
19
20type DidResolver interface {
21 GetDocument(ctx context.Context, didstr string) (*did.Document, error)
22}
23
24func NewKeyManager(didr DidResolver, k *did.PrivKey) *KeyManager {
25 return &KeyManager{
26 didr: didr,
27 signingKey: k,
28 log: slog.Default().With("system", "indexer"),
29 }
30}
31
32func (km *KeyManager) VerifyUserSignature(ctx context.Context, did string, sig []byte, msg []byte) error {
33 ctx, span := otel.Tracer("keymgr").Start(ctx, "verifySignature")
34 defer span.End()
35
36 k, err := km.getKey(ctx, did)
37 if err != nil {
38 return err
39 }
40
41 err = k.Verify(msg, sig)
42 if err != nil {
43 km.log.Warn("signature failed to verify", "err", err, "did", did, "pubKey", k, "sigBytes", sig, "msgBytes", msg)
44 }
45 return err
46}
47
48func (km *KeyManager) getKey(ctx context.Context, did string) (*did.PubKey, error) {
49 ctx, span := otel.Tracer("keymgr").Start(ctx, "getKey")
50 defer span.End()
51
52 // TODO: caching should be done at the DID document level, that way we can
53 // have a thing that subscribes to plc updates for cache busting
54 doc, err := km.didr.GetDocument(ctx, did)
55 if err != nil {
56 return nil, err
57 }
58
59 pubk, err := doc.GetPublicKey("#atproto")
60 if err != nil {
61 return nil, err
62 }
63
64 return pubk, nil
65}
66
67func (km *KeyManager) SignForUser(ctx context.Context, did string, msg []byte) ([]byte, error) {
68 if km.signingKey == nil {
69 return nil, fmt.Errorf("key manager does not have a signing key, cannot sign")
70 }
71
72 return km.signingKey.Sign(msg)
73}