From a14b29b52617c06a548145a18b4d7cee6e529b79 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 22 Sep 2020 14:40:54 +0100 Subject: [PATCH] Initial notary support (#1436) * Initial work on notary support * Somewhat working (but not properly filtered) notary support, other tweaks * Update gomatrixserverlib --- federationapi/routing/keys.go | 62 +++++++++++++++++++++++++ federationapi/routing/routing.go | 22 +++++++++ federationsender/api/api.go | 2 + federationsender/internal/api.go | 24 ++++++++++ federationsender/inthttp/client.go | 72 +++++++++++++++++++++++++++--- federationsender/inthttp/server.go | 44 ++++++++++++++++++ go.mod | 2 +- go.sum | 4 +- serverkeyapi/internal/api.go | 2 +- serverkeyapi/serverkeyapi.go | 6 +-- sytest-whitelist | 4 +- 11 files changed, 229 insertions(+), 15 deletions(-) diff --git a/federationapi/routing/keys.go b/federationapi/routing/keys.go index f1ed4176..785be090 100644 --- a/federationapi/routing/keys.go +++ b/federationapi/routing/keys.go @@ -19,11 +19,14 @@ import ( "net/http" "time" + "github.com/matrix-org/dendrite/clientapi/httputil" "github.com/matrix-org/dendrite/clientapi/jsonerror" + federationSenderAPI "github.com/matrix-org/dendrite/federationsender/api" "github.com/matrix-org/dendrite/internal/config" "github.com/matrix-org/dendrite/keyserver/api" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" + "github.com/sirupsen/logrus" "golang.org/x/crypto/ed25519" ) @@ -160,3 +163,62 @@ func localKeys(cfg *config.FederationAPI, validUntil time.Time) (*gomatrixserver return &keys, nil } + +func NotaryKeys( + httpReq *http.Request, cfg *config.FederationAPI, + fsAPI federationSenderAPI.FederationSenderInternalAPI, + req *gomatrixserverlib.PublicKeyNotaryLookupRequest, +) util.JSONResponse { + if req == nil { + req = &gomatrixserverlib.PublicKeyNotaryLookupRequest{} + if reqErr := httputil.UnmarshalJSONRequest(httpReq, &req); reqErr != nil { + return *reqErr + } + } + + var response struct { + ServerKeys []json.RawMessage `json:"server_keys"` + } + response.ServerKeys = []json.RawMessage{} + + for serverName := range req.ServerKeys { + var keys *gomatrixserverlib.ServerKeys + if serverName == cfg.Matrix.ServerName { + if k, err := localKeys(cfg, time.Now().Add(cfg.Matrix.KeyValidityPeriod)); err == nil { + keys = k + } else { + return util.ErrorResponse(err) + } + } else { + if k, err := fsAPI.GetServerKeys(httpReq.Context(), serverName); err == nil { + keys = &k + } else { + return util.ErrorResponse(err) + } + } + if keys == nil { + continue + } + + j, err := json.Marshal(keys) + if err != nil { + logrus.WithError(err).Errorf("Failed to marshal %q response", serverName) + return jsonerror.InternalServerError() + } + + js, err := gomatrixserverlib.SignJSON( + string(cfg.Matrix.ServerName), cfg.Matrix.KeyID, cfg.Matrix.PrivateKey, j, + ) + if err != nil { + logrus.WithError(err).Errorf("Failed to sign %q response", serverName) + return jsonerror.InternalServerError() + } + + response.ServerKeys = append(response.ServerKeys, js) + } + + return util.JSONResponse{ + Code: http.StatusOK, + JSON: response, + } +} diff --git a/federationapi/routing/routing.go b/federationapi/routing/routing.go index 71a09d42..06ed57af 100644 --- a/federationapi/routing/routing.go +++ b/federationapi/routing/routing.go @@ -61,6 +61,26 @@ func Setup( return LocalKeys(cfg) }) + notaryKeys := httputil.MakeExternalAPI("notarykeys", func(req *http.Request) util.JSONResponse { + vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) + if err != nil { + return util.ErrorResponse(err) + } + var pkReq *gomatrixserverlib.PublicKeyNotaryLookupRequest + serverName := gomatrixserverlib.ServerName(vars["serverName"]) + keyID := gomatrixserverlib.KeyID(vars["keyID"]) + if serverName != "" && keyID != "" { + pkReq = &gomatrixserverlib.PublicKeyNotaryLookupRequest{ + ServerKeys: map[gomatrixserverlib.ServerName]map[gomatrixserverlib.KeyID]gomatrixserverlib.PublicKeyNotaryQueryCriteria{ + serverName: { + keyID: gomatrixserverlib.PublicKeyNotaryQueryCriteria{}, + }, + }, + } + } + return NotaryKeys(req, cfg, fsAPI, pkReq) + }) + // Ignore the {keyID} argument as we only have a single server key so we always // return that key. // Even if we had more than one server key, we would probably still ignore the @@ -68,6 +88,8 @@ func Setup( v2keysmux.Handle("/server/{keyID}", localKeys).Methods(http.MethodGet) v2keysmux.Handle("/server/", localKeys).Methods(http.MethodGet) v2keysmux.Handle("/server", localKeys).Methods(http.MethodGet) + v2keysmux.Handle("/query", notaryKeys).Methods(http.MethodPost) + v2keysmux.Handle("/query/{serverName}/{keyID}", notaryKeys).Methods(http.MethodGet) v1fedmux.Handle("/send/{txnID}", httputil.MakeFedAPI( "federation_send", cfg.Matrix.ServerName, keys, wakeup, diff --git a/federationsender/api/api.go b/federationsender/api/api.go index adc3b34c..5ae419be 100644 --- a/federationsender/api/api.go +++ b/federationsender/api/api.go @@ -20,6 +20,8 @@ type FederationClient interface { ClaimKeys(ctx context.Context, s gomatrixserverlib.ServerName, oneTimeKeys map[string]map[string]string) (res gomatrixserverlib.RespClaimKeys, err error) QueryKeys(ctx context.Context, s gomatrixserverlib.ServerName, keys map[string][]string) (res gomatrixserverlib.RespQueryKeys, err error) GetEvent(ctx context.Context, s gomatrixserverlib.ServerName, eventID string) (res gomatrixserverlib.Transaction, err error) + GetServerKeys(ctx context.Context, matrixServer gomatrixserverlib.ServerName) (gomatrixserverlib.ServerKeys, error) + LookupServerKeys(ctx context.Context, s gomatrixserverlib.ServerName, keyRequests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp) ([]gomatrixserverlib.ServerKeys, error) } // FederationClientError is returned from FederationClient methods in the event of a problem. diff --git a/federationsender/internal/api.go b/federationsender/internal/api.go index 49c53755..f9d35357 100644 --- a/federationsender/internal/api.go +++ b/federationsender/internal/api.go @@ -189,3 +189,27 @@ func (a *FederationSenderInternalAPI) GetEvent( } return ires.(gomatrixserverlib.Transaction), nil } + +func (a *FederationSenderInternalAPI) GetServerKeys( + ctx context.Context, s gomatrixserverlib.ServerName, +) (gomatrixserverlib.ServerKeys, error) { + ires, err := a.doRequest(s, func() (interface{}, error) { + return a.federation.GetServerKeys(ctx, s) + }) + if err != nil { + return gomatrixserverlib.ServerKeys{}, err + } + return ires.(gomatrixserverlib.ServerKeys), nil +} + +func (a *FederationSenderInternalAPI) LookupServerKeys( + ctx context.Context, s gomatrixserverlib.ServerName, keyRequests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, +) ([]gomatrixserverlib.ServerKeys, error) { + ires, err := a.doRequest(s, func() (interface{}, error) { + return a.federation.LookupServerKeys(ctx, s, keyRequests) + }) + if err != nil { + return []gomatrixserverlib.ServerKeys{}, err + } + return ires.([]gomatrixserverlib.ServerKeys), nil +} diff --git a/federationsender/inthttp/client.go b/federationsender/inthttp/client.go index 5bfe6089..e0783ee1 100644 --- a/federationsender/inthttp/client.go +++ b/federationsender/inthttp/client.go @@ -23,13 +23,15 @@ const ( FederationSenderPerformServersAlivePath = "/federationsender/performServersAlive" FederationSenderPerformBroadcastEDUPath = "/federationsender/performBroadcastEDU" - FederationSenderGetUserDevicesPath = "/federationsender/client/getUserDevices" - FederationSenderClaimKeysPath = "/federationsender/client/claimKeys" - FederationSenderQueryKeysPath = "/federationsender/client/queryKeys" - FederationSenderBackfillPath = "/federationsender/client/backfill" - FederationSenderLookupStatePath = "/federationsender/client/lookupState" - FederationSenderLookupStateIDsPath = "/federationsender/client/lookupStateIDs" - FederationSenderGetEventPath = "/federationsender/client/getEvent" + FederationSenderGetUserDevicesPath = "/federationsender/client/getUserDevices" + FederationSenderClaimKeysPath = "/federationsender/client/claimKeys" + FederationSenderQueryKeysPath = "/federationsender/client/queryKeys" + FederationSenderBackfillPath = "/federationsender/client/backfill" + FederationSenderLookupStatePath = "/federationsender/client/lookupState" + FederationSenderLookupStateIDsPath = "/federationsender/client/lookupStateIDs" + FederationSenderGetEventPath = "/federationsender/client/getEvent" + FederationSenderGetServerKeysPath = "/federationsender/client/getServerKeys" + FederationSenderLookupServerKeysPath = "/federationsender/client/lookupServerKeys" ) // NewFederationSenderClient creates a FederationSenderInternalAPI implemented by talking to a HTTP POST API. @@ -358,3 +360,59 @@ func (h *httpFederationSenderInternalAPI) GetEvent( } return *response.Res, nil } + +type getServerKeys struct { + S gomatrixserverlib.ServerName + ServerKeys gomatrixserverlib.ServerKeys + Err *api.FederationClientError +} + +func (h *httpFederationSenderInternalAPI) GetServerKeys( + ctx context.Context, s gomatrixserverlib.ServerName, +) (gomatrixserverlib.ServerKeys, error) { + span, ctx := opentracing.StartSpanFromContext(ctx, "GetServerKeys") + defer span.Finish() + + request := getServerKeys{ + S: s, + } + var response getServerKeys + apiURL := h.federationSenderURL + FederationSenderGetServerKeysPath + err := httputil.PostJSON(ctx, span, h.httpClient, apiURL, &request, &response) + if err != nil { + return gomatrixserverlib.ServerKeys{}, err + } + if response.Err != nil { + return gomatrixserverlib.ServerKeys{}, response.Err + } + return response.ServerKeys, nil +} + +type lookupServerKeys struct { + S gomatrixserverlib.ServerName + KeyRequests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp + ServerKeys []gomatrixserverlib.ServerKeys + Err *api.FederationClientError +} + +func (h *httpFederationSenderInternalAPI) LookupServerKeys( + ctx context.Context, s gomatrixserverlib.ServerName, keyRequests map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.Timestamp, +) ([]gomatrixserverlib.ServerKeys, error) { + span, ctx := opentracing.StartSpanFromContext(ctx, "LookupServerKeys") + defer span.Finish() + + request := lookupServerKeys{ + S: s, + KeyRequests: keyRequests, + } + var response lookupServerKeys + apiURL := h.federationSenderURL + FederationSenderLookupServerKeysPath + err := httputil.PostJSON(ctx, span, h.httpClient, apiURL, &request, &response) + if err != nil { + return []gomatrixserverlib.ServerKeys{}, err + } + if response.Err != nil { + return []gomatrixserverlib.ServerKeys{}, response.Err + } + return response.ServerKeys, nil +} diff --git a/federationsender/inthttp/server.go b/federationsender/inthttp/server.go index dfbff1c0..53e1183e 100644 --- a/federationsender/inthttp/server.go +++ b/federationsender/inthttp/server.go @@ -263,4 +263,48 @@ func AddRoutes(intAPI api.FederationSenderInternalAPI, internalAPIMux *mux.Route return util.JSONResponse{Code: http.StatusOK, JSON: request} }), ) + internalAPIMux.Handle( + FederationSenderGetServerKeysPath, + httputil.MakeInternalAPI("GetServerKeys", func(req *http.Request) util.JSONResponse { + var request getServerKeys + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.MessageResponse(http.StatusBadRequest, err.Error()) + } + res, err := intAPI.GetServerKeys(req.Context(), request.S) + if err != nil { + ferr, ok := err.(*api.FederationClientError) + if ok { + request.Err = ferr + } else { + request.Err = &api.FederationClientError{ + Err: err.Error(), + } + } + } + request.ServerKeys = res + return util.JSONResponse{Code: http.StatusOK, JSON: request} + }), + ) + internalAPIMux.Handle( + FederationSenderLookupServerKeysPath, + httputil.MakeInternalAPI("LookupServerKeys", func(req *http.Request) util.JSONResponse { + var request lookupServerKeys + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.MessageResponse(http.StatusBadRequest, err.Error()) + } + res, err := intAPI.LookupServerKeys(req.Context(), request.S, request.KeyRequests) + if err != nil { + ferr, ok := err.(*api.FederationClientError) + if ok { + request.Err = ferr + } else { + request.Err = &api.FederationClientError{ + Err: err.Error(), + } + } + } + request.ServerKeys = res + return util.JSONResponse{Code: http.StatusOK, JSON: request} + }), + ) } diff --git a/go.mod b/go.mod index 6b1c03b5..6d367bda 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/matrix-org/go-http-js-libp2p v0.0.0-20200518170932-783164aeeda4 github.com/matrix-org/go-sqlite3-js v0.0.0-20200522092705-bc8506ccbcf3 github.com/matrix-org/gomatrix v0.0.0-20200827122206-7dd5e2a05bcd - github.com/matrix-org/gomatrixserverlib v0.0.0-20200907151926-38f437f2b2a6 + github.com/matrix-org/gomatrixserverlib v0.0.0-20200922131600-dce167edcce4 github.com/matrix-org/naffka v0.0.0-20200901083833-bcdd62999a91 github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4 github.com/mattn/go-sqlite3 v1.14.2 diff --git a/go.sum b/go.sum index 5c4f27a5..990fa21a 100644 --- a/go.sum +++ b/go.sum @@ -569,8 +569,8 @@ github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26 h1:Hr3zjRsq2bh github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26/go.mod h1:3fxX6gUjWyI/2Bt7J1OLhpCzOfO/bB3AiX0cJtEKud0= github.com/matrix-org/gomatrix v0.0.0-20200827122206-7dd5e2a05bcd h1:xVrqJK3xHREMNjwjljkAUaadalWc0rRbmVuQatzmgwg= github.com/matrix-org/gomatrix v0.0.0-20200827122206-7dd5e2a05bcd/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s= -github.com/matrix-org/gomatrixserverlib v0.0.0-20200907151926-38f437f2b2a6 h1:43gla6bLt4opWY1mQkAasF/LUCipZl7x2d44TY0wf40= -github.com/matrix-org/gomatrixserverlib v0.0.0-20200907151926-38f437f2b2a6/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= +github.com/matrix-org/gomatrixserverlib v0.0.0-20200922131600-dce167edcce4 h1:jBUEVUTgXc5a9luTRvb9vOkuLB+F528CE3Z05nUzGeM= +github.com/matrix-org/gomatrixserverlib v0.0.0-20200922131600-dce167edcce4/go.mod h1:JsAzE1Ll3+gDWS9JSUHPJiiyAksvOOnGWF2nXdg4ZzU= github.com/matrix-org/naffka v0.0.0-20200901083833-bcdd62999a91 h1:HJ6U3S3ljJqNffYMcIeAncp5qT/i+ZMiJ2JC2F0aXP4= github.com/matrix-org/naffka v0.0.0-20200901083833-bcdd62999a91/go.mod h1:sjyPyRxKM5uw1nD2cJ6O2OxI6GOqyVBfNXqKjBZTBZE= github.com/matrix-org/util v0.0.0-20190711121626-527ce5ddefc7 h1:ntrLa/8xVzeSs8vHFHK25k0C+NV74sYMJnNSg5NoSRo= diff --git a/serverkeyapi/internal/api.go b/serverkeyapi/internal/api.go index 02028c60..bc02ac2d 100644 --- a/serverkeyapi/internal/api.go +++ b/serverkeyapi/internal/api.go @@ -20,7 +20,7 @@ type ServerKeyAPI struct { ServerKeyValidity time.Duration OurKeyRing gomatrixserverlib.KeyRing - FedClient *gomatrixserverlib.FederationClient + FedClient gomatrixserverlib.KeyClient } func (s *ServerKeyAPI) KeyRing() *gomatrixserverlib.KeyRing { diff --git a/serverkeyapi/serverkeyapi.go b/serverkeyapi/serverkeyapi.go index fbaaefad..783402b2 100644 --- a/serverkeyapi/serverkeyapi.go +++ b/serverkeyapi/serverkeyapi.go @@ -26,7 +26,7 @@ func AddInternalRoutes(router *mux.Router, intAPI api.ServerKeyInternalAPI, cach // can call functions directly on the returned API or via an HTTP interface using AddInternalRoutes. func NewInternalAPI( cfg *config.ServerKeyAPI, - fedClient *gomatrixserverlib.FederationClient, + fedClient gomatrixserverlib.KeyClient, caches *caching.Caches, ) api.ServerKeyInternalAPI { innerDB, err := storage.NewDatabase( @@ -53,7 +53,7 @@ func NewInternalAPI( OurKeyRing: gomatrixserverlib.KeyRing{ KeyFetchers: []gomatrixserverlib.KeyFetcher{ &gomatrixserverlib.DirectKeyFetcher{ - Client: fedClient.Client, + Client: fedClient, }, }, KeyDatabase: serverKeyDB, @@ -65,7 +65,7 @@ func NewInternalAPI( perspective := &gomatrixserverlib.PerspectiveKeyFetcher{ PerspectiveServerName: ps.ServerName, PerspectiveServerKeys: map[gomatrixserverlib.KeyID]ed25519.PublicKey{}, - Client: fedClient.Client, + Client: fedClient, } for _, key := range ps.Keys { diff --git a/sytest-whitelist b/sytest-whitelist index 553df1f1..84706b6c 100644 --- a/sytest-whitelist +++ b/sytest-whitelist @@ -474,4 +474,6 @@ Peeked rooms only turn up in the sync for the device who peeked them Room state at a rejected message event is the same as its predecessor Room state at a rejected state event is the same as its predecessor Inbound federation correctly soft fails events -Inbound federation accepts a second soft-failed event \ No newline at end of file +Inbound federation accepts a second soft-failed event +Federation key API can act as a notary server via a POST request +Federation key API can act as a notary server via a GET request