porting all github actions from bluesky-social/indigo to tangled CI

handle repository signatures, was more of an adventure than i initially thought

+2 -3
api/plc.go
··· 13 13 "net/url" 14 14 "strings" 15 15 16 - key "github.com/bluesky-social/indigo/key" 17 16 did "github.com/whyrusleeping/go-did" 18 17 otel "go.opentelemetry.io/otel" 19 18 ) ··· 63 62 Sig string `json:"sig" cborgen:"sig,omitempty"` 64 63 } 65 64 66 - func (s *PLCServer) CreateDID(ctx context.Context, sigkey *key.Key, recovery string, handle string, service string) (string, error) { 65 + func (s *PLCServer) CreateDID(ctx context.Context, sigkey *did.PrivKey, recovery string, handle string, service string) (string, error) { 67 66 if s.C == nil { 68 67 s.C = http.DefaultClient 69 68 } 70 69 71 70 op := CreateOp{ 72 71 Type: "create", 73 - SigningKey: sigkey.DID(), 72 + SigningKey: sigkey.Public().DID(), 74 73 RecoveryKey: recovery, 75 74 Handle: handle, 76 75 Service: service,
+4 -4
api/plc_test.go
··· 8 8 "fmt" 9 9 "testing" 10 10 11 - key "github.com/bluesky-social/indigo/key" 12 11 "github.com/lestrrat-go/jwx/jwk" 12 + did "github.com/whyrusleeping/go-did" 13 13 ) 14 14 15 15 type testVector struct { ··· 88 88 t.Fatal(err) 89 89 } 90 90 91 - mk := key.Key{ 91 + mk := did.PrivKey{ 92 92 Raw: &spk, 93 - Type: "P-256", 93 + Type: did.KeyTypeP256, 94 94 } 95 95 96 - if mk.DID() != tv.Did { 96 + if mk.Public().DID() != tv.Did { 97 97 t.Fatal("keys generated different DIDs") 98 98 } 99 99
+3 -2
bgs/bgs.go
··· 175 175 176 176 u = new(User) 177 177 u.ID = subj.Uid 178 + u.Did = evt.Repo 178 179 } 179 180 180 181 // TODO: if the user is already in the 'slow' path, we shouldnt even bother trying to fast path this event 181 182 182 - if err := bgs.repoman.HandleExternalUserEvent(ctx, host.ID, u.ID, evt.RepoAppend.Prev, evt.RepoAppend.Ops, evt.RepoAppend.Car); err != nil { 183 + if err := bgs.repoman.HandleExternalUserEvent(ctx, host.ID, u.ID, u.Did, evt.RepoAppend.Prev, evt.RepoAppend.Ops, evt.RepoAppend.Car); err != nil { 183 184 if !errors.Is(err, carstore.ErrRepoBaseMismatch) { 184 - return err 185 + return fmt.Errorf("handle user event failed: %w", err) 185 186 } 186 187 187 188 ai, err := bgs.Index.LookupUser(ctx, u.ID)
+12 -6
carstore/repo_test.go
··· 12 12 13 13 "github.com/bluesky-social/indigo/api" 14 14 "github.com/bluesky-social/indigo/repo" 15 + "github.com/bluesky-social/indigo/util" 15 16 sqlbs "github.com/ipfs/go-bs-sqlite3" 16 17 "github.com/ipfs/go-cid" 17 18 flatfs "github.com/ipfs/go-ds-flatfs" ··· 110 111 t.Fatal(err) 111 112 } 112 113 113 - nroot, err := rr.Commit(ctx) 114 + kmgr := &util.FakeKeyManager{} 115 + nroot, err := rr.Commit(ctx, kmgr.SignForUser) 114 116 if err != nil { 115 117 t.Fatal(err) 116 118 } ··· 132 134 } 133 135 134 136 func setupRepo(ctx context.Context, bs blockstore.Blockstore) (cid.Cid, error) { 135 - nr := repo.NewRepo(ctx, bs) 137 + nr := repo.NewRepo(ctx, "did:foo", bs) 136 138 137 139 if _, _, err := nr.CreateRecord(ctx, "app.bsky.feed.post", &api.PostRecord{ 138 140 Text: fmt.Sprintf("hey look its a tweet %s", time.Now()), ··· 140 142 return cid.Undef, err 141 143 } 142 144 143 - ncid, err := nr.Commit(ctx) 145 + kmgr := &util.FakeKeyManager{} 146 + ncid, err := nr.Commit(ctx, kmgr.SignForUser) 144 147 if err != nil { 145 148 return cid.Undef, err 146 149 } ··· 190 193 b.Fatal(err) 191 194 } 192 195 193 - nroot, err := rr.Commit(ctx) 196 + kmgr := &util.FakeKeyManager{} 197 + nroot, err := rr.Commit(ctx, kmgr.SignForUser) 194 198 if err != nil { 195 199 b.Fatal(err) 196 200 } ··· 232 236 b.Fatal(err) 233 237 } 234 238 235 - nroot, err := rr.Commit(ctx) 239 + kmgr := &util.FakeKeyManager{} 240 + nroot, err := rr.Commit(ctx, kmgr.SignForUser) 236 241 if err != nil { 237 242 b.Fatal(err) 238 243 } ··· 269 274 b.Fatal(err) 270 275 } 271 276 272 - nroot, err := rr.Commit(ctx) 277 + kmgr := &util.FakeKeyManager{} 278 + nroot, err := rr.Commit(ctx, kmgr.SignForUser) 273 279 if err != nil { 274 280 b.Fatal(err) 275 281 }
+4 -3
cmd/bigsky/main.go
··· 114 114 return err 115 115 } 116 116 117 - repoman := repomgr.NewRepoManager(db, cstore) 117 + didr := &api.PLCServer{Host: cctx.String("plc")} 118 + kmgr := indexer.NewKeyManager(didr, nil) 119 + 120 + repoman := repomgr.NewRepoManager(db, cstore, kmgr) 118 121 119 122 evtman := events.NewEventManager() 120 123 ··· 123 126 // not necessary to generate notifications, should probably make the 124 127 // indexer just take optional callbacks for notification stuff 125 128 notifman := notifs.NewNotificationManager(db, repoman.GetRecord) 126 - 127 - didr := &api.PLCServer{Host: cctx.String("plc")} 128 129 129 130 ix, err := indexer.NewIndexer(db, notifman, evtman, didr, repoman, true) 130 131 if err != nil {
+14 -5
cmd/gosky/main.go
··· 15 15 atproto "github.com/bluesky-social/indigo/api/atproto" 16 16 bsky "github.com/bluesky-social/indigo/api/bsky" 17 17 cliutil "github.com/bluesky-social/indigo/cmd/gosky/util" 18 - "github.com/bluesky-social/indigo/key" 19 18 "github.com/bluesky-social/indigo/repo" 20 19 "github.com/ipfs/go-cid" 21 20 "github.com/lestrrat-go/jwx/jwa" ··· 24 23 rejson "github.com/polydawn/refmt/json" 25 24 "github.com/polydawn/refmt/shared" 26 25 cli "github.com/urfave/cli/v2" 26 + "github.com/whyrusleeping/go-did" 27 27 ) 28 28 29 29 func main() { ··· 202 202 return err 203 203 } 204 204 205 - fmt.Println("KEYDID: ", sigkey.DID()) 205 + fmt.Println("KEYDID: ", sigkey.Public().DID()) 206 206 207 207 ndid, err := s.CreateDID(context.TODO(), sigkey, recoverydid, handle, service) 208 208 if err != nil { ··· 214 214 }, 215 215 } 216 216 217 - func loadKey(kfile string) (*key.Key, error) { 217 + func loadKey(kfile string) (*did.PrivKey, error) { 218 218 kb, err := os.ReadFile(kfile) 219 219 if err != nil { 220 220 return nil, err ··· 234 234 return nil, fmt.Errorf("need a curve set") 235 235 } 236 236 237 - return &key.Key{ 237 + var out string 238 + kts := string(curve.(jwa.EllipticCurveAlgorithm)) 239 + switch kts { 240 + case "P-256": 241 + out = did.KeyTypeP256 242 + default: 243 + return nil, fmt.Errorf("unrecognized key type: %s", kts) 244 + } 245 + 246 + return &did.PrivKey{ 238 247 Raw: &spk, 239 - Type: string(curve.(jwa.EllipticCurveAlgorithm)), 248 + Type: out, 240 249 }, nil 241 250 } 242 251
+1 -1
cmd/stress/main.go
··· 135 135 136 136 ctx := context.Background() 137 137 138 - r := repo.NewRepo(ctx, membs) 138 + r := repo.NewRepo(ctx, "did:plc:foobar", membs) 139 139 140 140 root, err := testing.GenerateFakeRepo(r, l) 141 141 if err != nil {
+2 -1
go.mod
··· 18 18 github.com/ipfs/go-log/v2 v2.5.1 19 19 github.com/ipld/go-car v0.5.0 20 20 github.com/ipld/go-car/v2 v2.5.1 21 + github.com/ipsn/go-secp256k1 v0.0.0-20180726113642-9d62b9f0bc52 21 22 github.com/labstack/echo/v4 v4.10.0 22 23 github.com/lestrrat-go/jwx v1.2.25 23 24 github.com/lestrrat-go/jwx/v2 v2.0.8 ··· 29 30 github.com/stretchr/testify v1.8.1 30 31 github.com/urfave/cli/v2 v2.23.7 31 32 github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa 32 - github.com/whyrusleeping/go-did v0.0.0-20221105001742-8d9e0ffb0d59 33 + github.com/whyrusleeping/go-did v0.0.0-20230210051655-85c9ba6709ab 33 34 go.opentelemetry.io/otel v1.11.2 34 35 go.opentelemetry.io/otel/exporters/jaeger v1.11.2 35 36 go.opentelemetry.io/otel/sdk v1.11.2
+6 -8
go.sum
··· 101 101 github.com/ipfs/go-car v0.0.4/go.mod h1:eZX0EppfsvSQN8IsJnx57bheogWMgQjJVWU/fDA7ySQ= 102 102 github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= 103 103 github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= 104 - github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= 105 104 github.com/ipfs/go-cid v0.0.4/go.mod h1:4LLaPOQwmk5z9LBgQnpkivrx8BJjUyGwTXCd5Xfj6+M= 106 105 github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= 107 106 github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= ··· 144 143 github.com/ipfs/go-ipfs-util v0.0.2 h1:59Sswnk1MFaiq+VcaknX7aYEyGyGDAA73ilhEK2POp8= 145 144 github.com/ipfs/go-ipfs-util v0.0.2/go.mod h1:CbPtkWJzjLdEcezDns2XYaehFVNXG9zrdrtMecczcsQ= 146 145 github.com/ipfs/go-ipld-cbor v0.0.2/go.mod h1:wTBtrQZA3SoFKMVkp6cn6HMRteIB1VsmHA0AQFOn7Nc= 147 - github.com/ipfs/go-ipld-cbor v0.0.6 h1:pYuWHyvSpIsOOLw4Jy7NbBkCyzLDcl64Bf/LZW7eBQ0= 148 - github.com/ipfs/go-ipld-cbor v0.0.6/go.mod h1:ssdxxaLJPXH7OjF5V4NSjBbcfh+evoR4ukuru0oPXMA= 149 146 github.com/ipfs/go-ipld-cbor v0.0.7-0.20230126201833-a73d038d90bc h1:eUEo764smNy0EVRuMTSmirmuh552Mf2aBjfpDcLnDa8= 150 147 github.com/ipfs/go-ipld-cbor v0.0.7-0.20230126201833-a73d038d90bc/go.mod h1:X7SgEIwC4COC5OWfcepZBWafO5kA1Rmt9ZsLLbhihQk= 151 148 github.com/ipfs/go-ipld-format v0.0.1/go.mod h1:kyJtbkDALmFHv3QR6et67i35QzO3S0dCDnkOJhcZkms= ··· 185 182 github.com/ipld/go-ipld-prime v0.19.0 h1:5axC7rJmPc17Emw6TelxGwnzALk0PdupZ2oj2roDj04= 186 183 github.com/ipld/go-ipld-prime v0.19.0/go.mod h1:Q9j3BaVXwaA3o5JUDNvptDDr/x8+F7FG6XJ8WI3ILg4= 187 184 github.com/ipld/go-ipld-prime/storage/bsadapter v0.0.0-20211210234204-ce2a1c70cd73 h1:TsyATB2ZRRQGTwafJdgEUQkmjOExRV0DNokcihZxbnQ= 185 + github.com/ipsn/go-secp256k1 v0.0.0-20180726113642-9d62b9f0bc52 h1:QG4CGBqCeuBo6aZlGAamSkxWdgWfZGeE49eUOWJPA4c= 186 + github.com/ipsn/go-secp256k1 v0.0.0-20180726113642-9d62b9f0bc52/go.mod h1:fdg+/X9Gg4AsAIzWpEHwnqd+QY3b7lajxyjE1m4hkq4= 188 187 github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 189 188 github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 190 189 github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= ··· 442 441 github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= 443 442 github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= 444 443 github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 h1:5HZfQkwe0mIfyDmc1Em5GqlNRzcdtlv4HTNmdpt7XH0= 445 - github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI= 446 - github.com/whyrusleeping/cbor-gen v0.0.0-20230109192608-0173f1e641ac h1:vSeRURgERu0v7h+bKvlP0wuT+inofyu61R15qka/Xh0= 447 - github.com/whyrusleeping/cbor-gen v0.0.0-20230109192608-0173f1e641ac/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= 448 444 github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa h1:EyA027ZAkuaCLoxVX4r1TZMPy1d31fM6hbfQ4OU4I5o= 449 445 github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa/go.mod h1:fgkXqYy7bV2cFeIEOkVTZS/WjXARfBqSH6Q2qHL33hQ= 450 446 github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E= 451 - github.com/whyrusleeping/go-did v0.0.0-20221105001742-8d9e0ffb0d59 h1:dRYr/sfpZjX8evmbFrOG7ldkzdk5TLMGRVM40k1AZPQ= 452 - github.com/whyrusleeping/go-did v0.0.0-20221105001742-8d9e0ffb0d59/go.mod h1:mX/AQ/SS9KrCwO8V+IWyIozytxw5gw75cMHymoJvMGo= 447 + github.com/whyrusleeping/go-did v0.0.0-20230209234736-e14671c25e01 h1:YPGD8CeME7k4QgW0/lb1bR8nYSQDQbxoTPqb4zKke78= 448 + github.com/whyrusleeping/go-did v0.0.0-20230209234736-e14671c25e01/go.mod h1:mX/AQ/SS9KrCwO8V+IWyIozytxw5gw75cMHymoJvMGo= 449 + github.com/whyrusleeping/go-did v0.0.0-20230210051655-85c9ba6709ab h1:u1P8OjfkqR7LGZV91TMS76NEecUPa/boX29FBMNq+nw= 450 + github.com/whyrusleeping/go-did v0.0.0-20230210051655-85c9ba6709ab/go.mod h1:qPtRyexGM5XMHFIfjH+EiA/A/1n2JakWEdMPC53pJAE= 453 451 github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= 454 452 github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= 455 453 github.com/whyrusleeping/go-notifier v0.0.0-20170827234753-097c5d47330f/go.mod h1:cZNvX9cFybI01GriPRMXDtczuvUhgbcYr9iCGaNlRv8=
+62
indexer/keymgr.go
··· 1 + package indexer 2 + 3 + import ( 4 + "context" 5 + "crypto" 6 + "fmt" 7 + "time" 8 + 9 + "github.com/bluesky-social/indigo/plc" 10 + did "github.com/whyrusleeping/go-did" 11 + ) 12 + 13 + type KeyManager struct { 14 + didr plc.PLCClient 15 + 16 + signingKey *did.PrivKey 17 + } 18 + 19 + func NewKeyManager(didr plc.PLCClient, k *did.PrivKey) *KeyManager { 20 + return &KeyManager{ 21 + didr: didr, 22 + signingKey: k, 23 + } 24 + } 25 + 26 + type cachedKey struct { 27 + cachedAt time.Time 28 + pub crypto.PublicKey 29 + } 30 + 31 + func (km *KeyManager) VerifyUserSignature(ctx context.Context, did string, sig []byte, msg []byte) error { 32 + k, err := km.getKey(ctx, did) 33 + if err != nil { 34 + return err 35 + } 36 + 37 + return k.Verify(msg, sig) 38 + } 39 + 40 + func (km *KeyManager) getKey(ctx context.Context, did string) (*did.PubKey, error) { 41 + // TODO: caching should be done at the DID document level, that way we can 42 + // have a thing that subscribes to plc updates for cache busting 43 + doc, err := km.didr.GetDocument(ctx, did) 44 + if err != nil { 45 + return nil, err 46 + } 47 + 48 + pubk, err := doc.GetPublicKey("#signingKey") 49 + if err != nil { 50 + return nil, err 51 + } 52 + 53 + return pubk, nil 54 + } 55 + 56 + func (km *KeyManager) SignForUser(ctx context.Context, did string, msg []byte) ([]byte, error) { 57 + if km.signingKey == nil { 58 + return nil, fmt.Errorf("key manager does not have a signing key, cannot sign") 59 + } 60 + 61 + return km.signingKey.Sign(msg) 62 + }
+1 -1
indexer/posts_test.go
··· 56 56 t.Fatal(err) 57 57 } 58 58 59 - repoman := repomgr.NewRepoManager(maindb, cs) 59 + repoman := repomgr.NewRepoManager(maindb, cs, &util.FakeKeyManager{}) 60 60 notifman := notifs.NewNotificationManager(maindb, repoman.GetRecord) 61 61 evtman := events.NewEventManager() 62 62
-87
key/key.go
··· 1 - package key 2 - 3 - import ( 4 - "crypto/ecdsa" 5 - "crypto/ed25519" 6 - "crypto/elliptic" 7 - "crypto/rand" 8 - "crypto/sha256" 9 - "crypto/x509" 10 - "fmt" 11 - 12 - "github.com/multiformats/go-multibase" 13 - "github.com/multiformats/go-varint" 14 - ) 15 - 16 - const ( 17 - MCed25519 = 0xED 18 - MCP256 = 0x1200 19 - ) 20 - 21 - type Key struct { 22 - Raw interface{} 23 - Type string 24 - } 25 - 26 - func (k *Key) Sign(b []byte) ([]byte, error) { 27 - switch k.Type { 28 - case "ed25519": 29 - return ed25519.Sign(k.Raw.(ed25519.PrivateKey), b), nil 30 - case "P-256": 31 - h := sha256.Sum256(b) 32 - //return ecdsa.SignASN1(rand.Reader, k.Raw.(*ecdsa.PrivateKey), h[:]) 33 - r, s, err := ecdsa.Sign(rand.Reader, k.Raw.(*ecdsa.PrivateKey), h[:]) 34 - if err != nil { 35 - return nil, err 36 - } 37 - 38 - return append(r.Bytes(), s.Bytes()...), nil 39 - default: 40 - return nil, fmt.Errorf("unsupported key type: %s", k.Type) 41 - } 42 - } 43 - 44 - func (k *Key) DID() string { 45 - var buf []byte 46 - switch k.Type { 47 - case "ed25519": 48 - kb := k.Raw.(ed25519.PrivateKey) 49 - buf := make([]byte, 8+len(kb)) 50 - n := varint.PutUvarint(buf, MCed25519) 51 - copy(buf[n:], kb) 52 - buf = buf[:n+len(kb)] 53 - case "P-256": 54 - sk := k.Raw.(*ecdsa.PrivateKey) 55 - enc := elliptic.MarshalCompressed(elliptic.P256(), sk.X, sk.Y) 56 - 57 - buf = make([]byte, 8+len(enc)) 58 - n := varint.PutUvarint(buf, MCP256) 59 - copy(buf[n:], enc) 60 - buf = buf[:n+len(enc)] 61 - default: 62 - return "<invalid key type>" 63 - } 64 - 65 - kstr, err := multibase.Encode(multibase.Base58BTC, buf) 66 - if err != nil { 67 - panic(err) 68 - } 69 - 70 - return "did:key:" + kstr 71 - } 72 - 73 - func (k *Key) RawBytes() ([]byte, error) { 74 - switch k.Type { 75 - case "ed25519": 76 - return k.Raw.([]byte), nil 77 - case "P-256": 78 - b, err := x509.MarshalECPrivateKey(k.Raw.(*ecdsa.PrivateKey)) 79 - if err != nil { 80 - return nil, err 81 - } 82 - 83 - return b, nil 84 - default: 85 - return nil, fmt.Errorf("unsupported key type: %q", k.Type) 86 - } 87 - }
+1 -1
pds/auth.go
··· 55 55 56 56 // setting this is a little weird, 57 57 // since the token isn't signed by this key, we dont have a way to validate... 58 - accessTok.Set("pds", s.signingKey.DID()) 58 + accessTok.Set("pds", s.signingKey.Public().DID()) 59 59 60 60 rval := make([]byte, 10) 61 61 rand.Read(rval)
+4 -4
pds/fedmgr.go
··· 10 10 "time" 11 11 12 12 "github.com/bluesky-social/indigo/events" 13 - "github.com/bluesky-social/indigo/key" 14 13 15 14 "github.com/gorilla/websocket" 16 15 cbg "github.com/whyrusleeping/cbor-gen" 16 + "github.com/whyrusleeping/go-did" 17 17 "gorm.io/gorm" 18 18 ) 19 19 ··· 27 27 cb IndexCallback 28 28 29 29 db *gorm.DB 30 - signingKey *key.Key 30 + signingKey *did.PrivKey 31 31 } 32 32 33 - func NewSlurper(cb IndexCallback, db *gorm.DB, signingKey *key.Key) Slurper { 33 + func NewSlurper(cb IndexCallback, db *gorm.DB, signingKey *did.PrivKey) Slurper { 34 34 return Slurper{ 35 35 cb: cb, 36 36 db: db, ··· 55 55 var backoff int 56 56 for { 57 57 h := http.Header{ 58 - "DID": []string{s.signingKey.DID()}, 58 + "DID": []string{s.signingKey.Public().DID()}, 59 59 } 60 60 61 61 con, res, err := d.Dial("ws://"+host.Host+"/events", h)
+2 -2
pds/handlers.go
··· 394 394 } 395 395 396 396 if recoveryKey == "" { 397 - recoveryKey = s.signingKey.DID() 397 + recoveryKey = s.signingKey.Public().DID() 398 398 } 399 399 400 400 d, err := s.plc.CreateDID(ctx, s.signingKey, recoveryKey, input.Handle, s.serviceUrl) ··· 461 461 462 462 func (s *Server) handleComAtprotoHandleResolve(ctx context.Context, handle string) (*comatprototypes.HandleResolve_Output, error) { 463 463 if handle == "" { 464 - return &comatprototypes.HandleResolve_Output{Did: s.signingKey.DID()}, nil 464 + return &comatprototypes.HandleResolve_Output{Did: s.signingKey.Public().DID()}, nil 465 465 } 466 466 u, err := s.lookupUserByHandle(ctx, handle) 467 467 if err != nil {
+18 -7
pds/server.go
··· 17 17 "github.com/bluesky-social/indigo/carstore" 18 18 "github.com/bluesky-social/indigo/events" 19 19 "github.com/bluesky-social/indigo/indexer" 20 - "github.com/bluesky-social/indigo/key" 21 20 "github.com/bluesky-social/indigo/lex/util" 22 21 "github.com/bluesky-social/indigo/models" 23 22 "github.com/bluesky-social/indigo/notifs" ··· 33 32 "github.com/lestrrat-go/jwx/jwa" 34 33 jwk "github.com/lestrrat-go/jwx/jwk" 35 34 jwt "github.com/lestrrat-go/jwx/jwt" 35 + "github.com/whyrusleeping/go-did" 36 36 "gorm.io/gorm" 37 37 ) 38 38 ··· 47 47 indexer *indexer.Indexer 48 48 events *events.EventManager 49 49 slurper *Slurper 50 - signingKey *key.Key 50 + signingKey *did.PrivKey 51 51 echo *echo.Echo 52 52 jwtSigningKey []byte 53 53 enforcePeering bool ··· 72 72 73 73 evtman := events.NewEventManager() 74 74 75 - repoman := repomgr.NewRepoManager(db, cs) 75 + kmgr := indexer.NewKeyManager(didr, serkey) 76 + 77 + repoman := repomgr.NewRepoManager(db, cs, kmgr) 76 78 notifman := notifs.NewNotificationManager(db, repoman.GetRecord) 77 79 78 80 ix, err := indexer.NewIndexer(db, notifman, evtman, didr, repoman, false) ··· 145 147 u.ID = subj.Uid 146 148 } 147 149 148 - return s.repoman.HandleExternalUserEvent(ctx, host.ID, u.ID, evt.RepoAppend.Prev, evt.RepoAppend.Ops, evt.RepoAppend.Car) 150 + return s.repoman.HandleExternalUserEvent(ctx, host.ID, u.ID, u.Did, evt.RepoAppend.Prev, evt.RepoAppend.Ops, evt.RepoAppend.Car) 149 151 default: 150 152 return fmt.Errorf("invalid fed event") 151 153 } ··· 292 294 return util.CborDecodeValue(blk.RawData()) 293 295 } 294 296 295 - func loadKey(kfile string) (*key.Key, error) { 297 + func loadKey(kfile string) (*did.PrivKey, error) { 296 298 kb, err := os.ReadFile(kfile) 297 299 if err != nil { 298 300 return nil, err ··· 312 314 return nil, fmt.Errorf("need a curve set") 313 315 } 314 316 315 - return &key.Key{ 317 + var out string 318 + kts := string(curve.(jwa.EllipticCurveAlgorithm)) 319 + switch kts { 320 + case "P-256": 321 + out = did.KeyTypeP256 322 + default: 323 + return nil, fmt.Errorf("unrecognized key type: %s", kts) 324 + } 325 + 326 + return &did.PrivKey{ 316 327 Raw: &spk, 317 - Type: string(curve.(jwa.EllipticCurveAlgorithm)), 328 + Type: out, 318 329 }, nil 319 330 } 320 331
+19 -9
plc/fakedid.go
··· 5 5 "crypto/rand" 6 6 "encoding/hex" 7 7 8 - "github.com/bluesky-social/indigo/key" 9 8 "github.com/whyrusleeping/go-did" 10 9 "gorm.io/gorm" 11 10 ) 12 11 13 12 type FakeDidMapping struct { 14 13 gorm.Model 15 - Handle string 16 - Did string `gorm:"index"` 17 - Service string 14 + Handle string 15 + Did string `gorm:"index"` 16 + Service string 17 + KeyType string 18 + PubKeyMbase string 18 19 } 19 20 20 21 type FakeDid struct { ··· 46 47 47 48 //Authentication []interface{} `json:"authentication"` 48 49 49 - //VerificationMethod []VerificationMethod `json:"verificationMethod"` 50 + VerificationMethod: []did.VerificationMethod{ 51 + did.VerificationMethod{ 52 + ID: "#signingKey", 53 + Type: rec.KeyType, 54 + PublicKeyMultibase: &rec.PubKeyMbase, 55 + Controller: rec.Did, 56 + }, 57 + }, 50 58 51 59 Service: []did.Service{ 52 60 did.Service{ ··· 58 66 }, nil 59 67 } 60 68 61 - func (fd *FakeDid) CreateDID(ctx context.Context, sigkey *key.Key, recovery string, handle string, service string) (string, error) { 69 + func (fd *FakeDid) CreateDID(ctx context.Context, sigkey *did.PrivKey, recovery string, handle string, service string) (string, error) { 62 70 buf := make([]byte, 8) 63 71 rand.Read(buf) 64 72 d := "did:plc:" + hex.EncodeToString(buf) 65 73 66 74 if err := fd.db.Create(&FakeDidMapping{ 67 - Handle: handle, 68 - Did: d, 69 - Service: service, 75 + Handle: handle, 76 + Did: d, 77 + Service: service, 78 + PubKeyMbase: sigkey.Public().MultibaseString(), 79 + KeyType: sigkey.KeyType(), 70 80 }).Error; err != nil { 71 81 return "", err 72 82 }
+1 -2
plc/plc.go
··· 3 3 import ( 4 4 "context" 5 5 6 - "github.com/bluesky-social/indigo/key" 7 6 "github.com/whyrusleeping/go-did" 8 7 ) 9 8 10 9 type PLCClient interface { 11 10 GetDocument(ctx context.Context, didstr string) (*did.Document, error) 12 - CreateDID(ctx context.Context, sigkey *key.Key, recovery string, handle string, service string) (string, error) 11 + CreateDID(ctx context.Context, sigkey *did.PrivKey, recovery string, handle string, service string) (string, error) 13 12 }
+50 -3
repo/repo.go
··· 85 85 return OpenRepo(ctx, bs, root) 86 86 } 87 87 88 - func NewRepo(ctx context.Context, bs blockstore.Blockstore) *Repo { 88 + func NewRepo(ctx context.Context, did string, bs blockstore.Blockstore) *Repo { 89 89 cst := util.CborStore(bs) 90 90 91 91 t := mst.NewMST(cst, 32, cid.Undef, []mst.NodeEntry{}, 0) 92 92 93 + meta := Meta{ 94 + Datastore: "TODO", 95 + Did: did, 96 + Version: 1, 97 + } 98 + 93 99 return &Repo{ 100 + meta: meta, 94 101 cst: cst, 95 102 bs: bs, 96 103 mst: t, ··· 106 113 return nil, fmt.Errorf("loading root from blockstore: %w", err) 107 114 } 108 115 116 + var rt Root 117 + if err := cst.Get(ctx, sr.Root, &rt); err != nil { 118 + return nil, fmt.Errorf("loading root: %w", err) 119 + } 120 + 121 + var meta Meta 122 + if err := cst.Get(ctx, rt.Meta, &meta); err != nil { 123 + return nil, fmt.Errorf("loading meta: %w", err) 124 + } 125 + 109 126 return &Repo{ 110 127 sr: sr, 111 128 bs: bs, 112 129 cst: cst, 113 130 repoCid: root, 131 + meta: meta, 114 132 }, nil 115 133 } 116 134 ··· 118 136 MarshalCBOR(w io.Writer) error 119 137 } 120 138 139 + func (r *Repo) MetaCid(ctx context.Context) (cid.Cid, error) { 140 + var root Root 141 + if err := r.cst.Get(ctx, r.sr.Root, &root); err != nil { 142 + return cid.Undef, err 143 + } 144 + 145 + return root.Meta, nil 146 + } 147 + 148 + func (r *Repo) RepoDid() string { 149 + if r.meta.Did == "" { 150 + panic("repo has unset did") 151 + } 152 + 153 + return r.meta.Did 154 + } 155 + 121 156 func (r *Repo) PrevCommit(ctx context.Context) (*cid.Cid, error) { 122 - 123 157 var c Root 124 158 if err := r.cst.Get(ctx, r.sr.Root, &c); err != nil { 125 159 return nil, fmt.Errorf("loading previous commit: %w", err) ··· 130 164 131 165 func (r *Repo) CommitRoot() cid.Cid { 132 166 return r.sr.Root 167 + } 168 + 169 + func (r *Repo) SignedCommit() SignedCommit { 170 + return r.sr 133 171 } 134 172 135 173 func (r *Repo) Blockstore() blockstore.Blockstore { ··· 205 243 return nil 206 244 } 207 245 208 - func (r *Repo) Commit(ctx context.Context) (cid.Cid, error) { 246 + func (r *Repo) Commit(ctx context.Context, signer func(context.Context, string, []byte) ([]byte, error)) (cid.Cid, error) { 209 247 ctx, span := otel.Tracer("repo").Start(ctx, "Commit") 210 248 defer span.End() 211 249 ··· 235 273 if err != nil { 236 274 return cid.Undef, err 237 275 } 276 + fmt.Println("PUT NEW META: ", mcid) 238 277 nroot.Meta = mcid 239 278 } 240 279 ··· 243 282 return cid.Undef, err 244 283 } 245 284 285 + did := r.RepoDid() 286 + 287 + sig, err := signer(ctx, did, ncomcid.Bytes()) 288 + if err != nil { 289 + return cid.Undef, fmt.Errorf("failed to sign root: %w", err) 290 + } 291 + 246 292 nsroot := SignedCommit{ 247 293 Root: ncomcid, 294 + Sig: sig, 248 295 } 249 296 250 297 nsrootcid, err := r.cst.Put(ctx, &nsroot)
+2 -1
repomgr/ingest_test.go
··· 8 8 "testing" 9 9 10 10 "github.com/bluesky-social/indigo/carstore" 11 + "github.com/bluesky-social/indigo/util" 11 12 "github.com/ipfs/go-cid" 12 13 "gorm.io/driver/sqlite" 13 14 "gorm.io/gorm" ··· 53 54 t.Fatal(err) 54 55 } 55 56 56 - repoman := NewRepoManager(maindb, cs) 57 + repoman := NewRepoManager(maindb, cs, &util.FakeKeyManager{}) 57 58 58 59 fi, err := os.Open("testrepo.car") 59 60 if err != nil {
+57 -11
repomgr/repomgr.go
··· 30 30 31 31 var log = logging.Logger("repomgr") 32 32 33 - func NewRepoManager(db *gorm.DB, cs *carstore.CarStore) *RepoManager { 33 + func NewRepoManager(db *gorm.DB, cs *carstore.CarStore, kmgr KeyManager) *RepoManager { 34 34 db.AutoMigrate(RepoHead{}) 35 35 36 36 return &RepoManager{ 37 37 db: db, 38 38 cs: cs, 39 39 userLocks: make(map[uint]*userLock), 40 + kmgr: kmgr, 40 41 } 41 42 } 42 43 44 + type KeyManager interface { 45 + VerifyUserSignature(context.Context, string, []byte, []byte) error 46 + SignForUser(context.Context, string, []byte) ([]byte, error) 47 + } 48 + 43 49 func (rm *RepoManager) SetEventHandler(cb func(context.Context, *RepoEvent)) { 44 50 rm.events = cb 45 51 } 46 52 47 53 type RepoManager struct { 48 - cs *carstore.CarStore 49 - db *gorm.DB 54 + cs *carstore.CarStore 55 + db *gorm.DB 56 + kmgr KeyManager 50 57 51 58 lklk sync.Mutex 52 59 userLocks map[uint]*userLock ··· 193 200 return "", cid.Undef, err 194 201 } 195 202 196 - nroot, err := r.Commit(ctx) 203 + nroot, err := r.Commit(ctx, rm.kmgr.SignForUser) 197 204 if err != nil { 198 205 return "", cid.Undef, err 199 206 } ··· 255 262 return cid.Undef, err 256 263 } 257 264 258 - nroot, err := r.Commit(ctx) 265 + nroot, err := r.Commit(ctx, rm.kmgr.SignForUser) 259 266 if err != nil { 260 267 return cid.Undef, err 261 268 } ··· 316 323 return err 317 324 } 318 325 319 - nroot, err := r.Commit(ctx) 326 + nroot, err := r.Commit(ctx, rm.kmgr.SignForUser) 320 327 if err != nil { 321 328 return err 322 329 } ··· 366 373 return err 367 374 } 368 375 369 - r := repo.NewRepo(ctx, ds) 376 + r := repo.NewRepo(ctx, did, ds) 370 377 371 378 profile := &bsky.ActorProfile{ 372 379 DisplayName: displayname, ··· 391 398 392 399 // TODO: set declaration? 393 400 394 - root, err := r.Commit(ctx) 401 + root, err := r.Commit(ctx, rm.kmgr.SignForUser) 395 402 if err != nil { 396 - return err 403 + return fmt.Errorf("committing repo for actor init: %w", err) 397 404 } 398 405 399 406 rslice, err := ds.CloseWithRoot(ctx, root) ··· 497 504 return ap, nil 498 505 } 499 506 500 - func (rm *RepoManager) HandleExternalUserEvent(ctx context.Context, pdsid uint, uid uint, prev *cid.Cid, ops []*events.RepoOp, carslice []byte) error { 507 + func (rm *RepoManager) HandleExternalUserEvent(ctx context.Context, pdsid uint, uid uint, did string, prev *cid.Cid, ops []*events.RepoOp, carslice []byte) error { 501 508 ctx, span := otel.Tracer("repoman").Start(ctx, "HandleExternalUserEvent") 502 509 defer span.End() 503 510 ··· 516 523 return fmt.Errorf("opening external user repo: %w", err) 517 524 } 518 525 526 + repoDid := r.RepoDid() 527 + 528 + if did != repoDid { 529 + return fmt.Errorf("DID in repo did not match (%q != %q)", did, repoDid) 530 + } 531 + 532 + scom := r.SignedCommit() 533 + 534 + if err := rm.kmgr.VerifyUserSignature(ctx, repoDid, scom.Sig, scom.Root.Bytes()); err != nil { 535 + return fmt.Errorf("signature check failed: %w", err) 536 + } 537 + 519 538 log.Infow("external event", "uid", uid, "ops", ops) 520 539 521 540 var evtops []RepoOp ··· 675 694 } 676 695 } 677 696 678 - nroot, err := r.Commit(ctx) 697 + nroot, err := r.Commit(ctx, rm.kmgr.SignForUser) 679 698 if err != nil { 680 699 return err 681 700 } ··· 812 831 } 813 832 814 833 membs := blockstore.NewBlockstore(datastore.NewMapDatastore()) 834 + 835 + // mild hack: without access to the 'meta' object, we cant properly verify each new repo slice has the right DID in the case of a gap fill procedure 836 + if until.Defined() { 837 + robs, err := rm.cs.ReadOnlySession(user) 838 + if err != nil { 839 + return err 840 + } 841 + 842 + old, err := repo.OpenRepo(ctx, robs, until) 843 + if err != nil { 844 + return err 845 + } 846 + 847 + mcid, err := old.MetaCid(ctx) 848 + if err != nil { 849 + return err 850 + } 851 + 852 + blk, err := robs.Get(ctx, mcid) 853 + if err != nil { 854 + return err 855 + } 856 + 857 + if err := membs.Put(ctx, blk); err != nil { 858 + return err 859 + } 860 + } 815 861 816 862 for { 817 863 blk, err := carr.Next()
+6 -2
testing/utils.go
··· 399 399 return nil, err 400 400 } 401 401 402 - repoman := repomgr.NewRepoManager(maindb, cs) 402 + kmgr := indexer.NewKeyManager(didr, nil) 403 + 404 + repoman := repomgr.NewRepoManager(maindb, cs, kmgr) 403 405 404 406 notifman := notifs.NewNotificationManager(maindb, repoman.GetRecord) 405 407 ··· 761 763 } 762 764 } 763 765 764 - nroot, err := r.Commit(ctx) 766 + kmgr := &bsutil.FakeKeyManager{} 767 + 768 + nroot, err := r.Commit(ctx, kmgr.SignForUser) 765 769 if err != nil { 766 770 return cid.Undef, err 767 771 }
+14
util/fakekey.go
··· 1 + package util 2 + 3 + import "context" 4 + 5 + type FakeKeyManager struct { 6 + } 7 + 8 + func (km *FakeKeyManager) VerifyUserSignature(context.Context, string, []byte, []byte) error { 9 + return nil 10 + } 11 + 12 + func (km *FakeKeyManager) SignForUser(ctx context.Context, did string, msg []byte) ([]byte, error) { 13 + return []byte("signature"), nil 14 + }