From 9d9841d02e1feabe339a47a79a5116023f0b1ce1 Mon Sep 17 00:00:00 2001 From: Roman Isaev Date: Mon, 30 Dec 2024 02:11:30 +0000 Subject: [PATCH] mas: added "admin's replacement without uia" endpoint i.e. /_synapse/admin/v1/users/{userID}/_allow_cross_signing_replacement_without_uia --- clientapi/routing/admin.go | 53 +++++++++++++++++++ clientapi/routing/key_crosssigning.go | 40 +++++++------- clientapi/routing/routing.go | 5 ++ userapi/api/api.go | 14 +++++ userapi/internal/cross_signing.go | 10 ++++ userapi/storage/interface.go | 2 + .../postgres/cross_signing_keys_table.go | 29 ++++++++-- userapi/storage/shared/storage.go | 14 ++++- .../sqlite3/cross_signing_keys_table.go | 29 ++++++++-- userapi/storage/tables/interface.go | 1 + 10 files changed, 168 insertions(+), 29 deletions(-) diff --git a/clientapi/routing/admin.go b/clientapi/routing/admin.go index 0b07724a..ce5476ef 100644 --- a/clientapi/routing/admin.go +++ b/clientapi/routing/admin.go @@ -2,6 +2,7 @@ package routing import ( "context" + "database/sql" "encoding/json" "errors" "fmt" @@ -30,6 +31,8 @@ import ( userapi "github.com/element-hq/dendrite/userapi/api" ) +const replacementPeriod = 10 * time.Minute + var validRegistrationTokenRegex = regexp.MustCompile("^[[:ascii:][:digit:]_]*$") func AdminCreateNewRegistrationToken(req *http.Request, cfg *config.ClientAPI, userAPI userapi.ClientUserAPI) util.JSONResponse { @@ -607,6 +610,56 @@ func AdminHandleUserDeviceByUserID( } +func AdminAllowCrossSigningReplacementWithoutUIA( + req *http.Request, + userAPI userapi.ClientUserAPI, +) util.JSONResponse { + vars, err := httputil.URLDecodeMapValues(mux.Vars(req)) + if err != nil { + return util.MessageResponse(http.StatusBadRequest, err.Error()) + } + userIDstr, ok := vars["userID"] + userID, err := spec.NewUserID(userIDstr, false) + if !ok || err != nil { + return util.JSONResponse{ + Code: http.StatusNotFound, + JSON: spec.MissingParam("User not found."), + } + } + + switch req.Method { + case http.MethodPost: + rq := userapi.PerformAllowingMasterCrossSigningKeyReplacementWithoutUIARequest{ + UserID: userID.String(), + Duration: replacementPeriod, + } + var rs userapi.PerformAllowingMasterCrossSigningKeyReplacementWithoutUIAResponse + err = userAPI.PerformAllowingMasterCrossSigningKeyReplacementWithoutUIA(req.Context(), &rq, &rs) + if err == sql.ErrNoRows { + return util.JSONResponse{ + Code: http.StatusNotFound, + JSON: spec.MissingParam("User has no master cross-signing key"), + } + } else if err != nil { + util.GetLogger(req.Context()).WithError(err).Error("userAPI.PerformAllowingMasterCrossSigningKeyReplacementWithoutUIA") + return util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: spec.Unknown(err.Error()), + } + } + return util.JSONResponse{ + Code: http.StatusOK, + JSON: map[string]int64{"updatable_without_uia_before_ms": rs.Timestamp}, + } + default: + return util.JSONResponse{ + Code: http.StatusMethodNotAllowed, + JSON: spec.Unknown("Method not allowed."), + } + } + +} + type adminExternalID struct { AuthProvider string `json:"auth_provider"` ExternalID string `json:"external_id"` diff --git a/clientapi/routing/key_crosssigning.go b/clientapi/routing/key_crosssigning.go index 7bcd7093..78b66400 100644 --- a/clientapi/routing/key_crosssigning.go +++ b/clientapi/routing/key_crosssigning.go @@ -176,25 +176,25 @@ func UploadCrossSigningDeviceSignatures(req *http.Request, keyserverAPI api.Clie func convertKeyError(err *api.KeyError) util.JSONResponse { switch { - case err.IsInvalidSignature: - return util.JSONResponse{ - Code: http.StatusBadRequest, - JSON: spec.InvalidSignature(err.Error()), - } - case err.IsMissingParam: - return util.JSONResponse{ - Code: http.StatusBadRequest, - JSON: spec.MissingParam(err.Error()), - } - case err.IsInvalidParam: - return util.JSONResponse{ - Code: http.StatusBadRequest, - JSON: spec.InvalidParam(err.Error()), - } - default: - return util.JSONResponse{ - Code: http.StatusBadRequest, - JSON: spec.Unknown(err.Error()), - } + case err.IsInvalidSignature: + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: spec.InvalidSignature(err.Error()), } + case err.IsMissingParam: + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: spec.MissingParam(err.Error()), + } + case err.IsInvalidParam: + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: spec.InvalidParam(err.Error()), + } + default: + return util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: spec.Unknown(err.Error()), + } + } } diff --git a/clientapi/routing/routing.go b/clientapi/routing/routing.go index ed93d079..945d0e48 100644 --- a/clientapi/routing/routing.go +++ b/clientapi/routing/routing.go @@ -352,6 +352,11 @@ func Setup( httputil.MakeServiceAdminAPI("admin_user_devices", m.AdminToken, func(r *http.Request) util.JSONResponse { return AdminHandleUserDeviceByUserID(r, userAPI) })).Methods(http.MethodPost, http.MethodGet) + + synapseAdminRouter.Handle("/admin/v1/users/{userID}/_allow_cross_signing_replacement_without_uia", + httputil.MakeServiceAdminAPI("admin_allow_cross_signing_replacement_without_uia", m.AdminToken, func(r *http.Request) util.JSONResponse { + return AdminAllowCrossSigningReplacementWithoutUIA(r, userAPI) + })).Methods(http.MethodPost) } if mscCfg.Enabled("msc2753") { diff --git a/userapi/api/api.go b/userapi/api/api.go index 6899e5e2..bcd5c9c0 100644 --- a/userapi/api/api.go +++ b/userapi/api/api.go @@ -680,6 +680,11 @@ type ClientKeyAPI interface { QueryKeys(ctx context.Context, req *QueryKeysRequest, res *QueryKeysResponse) QueryMasterKeys(ctx context.Context, req *QueryMasterKeysRequest, res *QueryMasterKeysResponse) PerformUploadKeys(ctx context.Context, req *PerformUploadKeysRequest, res *PerformUploadKeysResponse) error + PerformAllowingMasterCrossSigningKeyReplacementWithoutUIA( + ctx context.Context, + req *PerformAllowingMasterCrossSigningKeyReplacementWithoutUIARequest, + res *PerformAllowingMasterCrossSigningKeyReplacementWithoutUIAResponse, + ) error PerformUploadDeviceSignatures(ctx context.Context, req *PerformUploadDeviceSignaturesRequest, res *PerformUploadDeviceSignaturesResponse) // PerformClaimKeys claims one-time keys for use in pre-key messages @@ -908,6 +913,15 @@ type PerformUploadDeviceKeysResponse struct { Error *KeyError } +type PerformAllowingMasterCrossSigningKeyReplacementWithoutUIARequest struct { + UserID string + Duration time.Duration +} + +type PerformAllowingMasterCrossSigningKeyReplacementWithoutUIAResponse struct { + Timestamp int64 +} + type PerformUploadDeviceSignaturesRequest struct { Signatures map[string]map[gomatrixserverlib.KeyID]fclient.CrossSigningForKeyOrDevice // The user that uploaded the sig, should be populated by the clientapi. diff --git a/userapi/internal/cross_signing.go b/userapi/internal/cross_signing.go index dfd426c3..a93fba15 100644 --- a/userapi/internal/cross_signing.go +++ b/userapi/internal/cross_signing.go @@ -96,6 +96,16 @@ func sanityCheckKey(key fclient.CrossSigningKey, userID string, purpose fclient. return nil } +func (a *UserInternalAPI) PerformAllowingMasterCrossSigningKeyReplacementWithoutUIA( + ctx context.Context, + req *api.PerformAllowingMasterCrossSigningKeyReplacementWithoutUIARequest, + res *api.PerformAllowingMasterCrossSigningKeyReplacementWithoutUIAResponse, +) error { + var err error + res.Timestamp, err = a.KeyDatabase.UpdateMasterCrossSigningKeyAllowReplacementWithoutUIA(ctx, req.UserID, req.Duration) + return err +} + // nolint:gocyclo func (a *UserInternalAPI) PerformUploadDeviceKeys(ctx context.Context, req *api.PerformUploadDeviceKeysRequest, res *api.PerformUploadDeviceKeysResponse) { // Find the keys to store. diff --git a/userapi/storage/interface.go b/userapi/storage/interface.go index 6dbf97c5..11b36095 100644 --- a/userapi/storage/interface.go +++ b/userapi/storage/interface.go @@ -10,6 +10,7 @@ import ( "context" "encoding/json" "errors" + "time" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/fclient" @@ -231,6 +232,7 @@ type KeyDatabase interface { StoreCrossSigningKeysForUser(ctx context.Context, userID string, keyMap types.CrossSigningKeyMap, updatableWithoutUIABeforeMs *int64) error StoreCrossSigningSigsForTarget(ctx context.Context, originUserID string, originKeyID gomatrixserverlib.KeyID, targetUserID string, targetKeyID gomatrixserverlib.KeyID, signature spec.Base64Bytes) error + UpdateMasterCrossSigningKeyAllowReplacementWithoutUIA(ctx context.Context, userID string, duration time.Duration) (int64, error) DeleteStaleDeviceLists( ctx context.Context, diff --git a/userapi/storage/postgres/cross_signing_keys_table.go b/userapi/storage/postgres/cross_signing_keys_table.go index a8f0d8cb..7e66a011 100644 --- a/userapi/storage/postgres/cross_signing_keys_table.go +++ b/userapi/storage/postgres/cross_signing_keys_table.go @@ -10,6 +10,7 @@ import ( "context" "database/sql" "fmt" + "time" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/sqlutil" @@ -42,11 +43,17 @@ const upsertCrossSigningKeysForUserSQL = "" + " VALUES($1, $2, $3)" + " ON CONFLICT (user_id, key_type) DO UPDATE SET key_data = $3" +const updateMasterCrossSigningKeyAllowReplacementWithoutUiaSQL = "" + + "UPDATE keyserver_cross_signing_keys" + + " SET updatable_without_uia_before_ms = $3" + + " WHERE user_id = $1 AND key_type = $2" + type crossSigningKeysStatements struct { - db *sql.DB - selectCrossSigningKeysForUserStmt *sql.Stmt - selectCrossSigningKeysForUserAndKeyTypeStmt *sql.Stmt - upsertCrossSigningKeysForUserStmt *sql.Stmt + db *sql.DB + selectCrossSigningKeysForUserStmt *sql.Stmt + selectCrossSigningKeysForUserAndKeyTypeStmt *sql.Stmt + upsertCrossSigningKeysForUserStmt *sql.Stmt + updateMasterCrossSigningKeyAllowReplacementWithoutUiaStmt *sql.Stmt } func NewPostgresCrossSigningKeysTable(db *sql.DB) (tables.CrossSigningKeys, error) { @@ -61,6 +68,7 @@ func NewPostgresCrossSigningKeysTable(db *sql.DB) (tables.CrossSigningKeys, erro {&s.selectCrossSigningKeysForUserStmt, selectCrossSigningKeysForUserSQL}, {&s.selectCrossSigningKeysForUserAndKeyTypeStmt, selectCrossSigningKeysForUserAndKeyTypeSQL}, {&s.upsertCrossSigningKeysForUserStmt, upsertCrossSigningKeysForUserSQL}, + {&s.updateMasterCrossSigningKeyAllowReplacementWithoutUiaStmt, updateMasterCrossSigningKeyAllowReplacementWithoutUiaSQL}, }.Prepare(db) } @@ -138,3 +146,16 @@ func (s *crossSigningKeysStatements) UpsertCrossSigningKeysForUser( } return nil } + +func (s *crossSigningKeysStatements) UpdateMasterCrossSigningKeyAllowReplacementWithoutUIA(ctx context.Context, txn *sql.Tx, userID string, duration time.Duration) (int64, error) { + keyTypeInt, _ := types.KeyTypePurposeToInt[fclient.CrossSigningKeyPurposeMaster] + ts := time.Now().Add(duration).UnixMilli() + result, err := sqlutil.TxStmt(txn, s.updateMasterCrossSigningKeyAllowReplacementWithoutUiaStmt).ExecContext(ctx, userID, keyTypeInt, ts) + if err != nil { + return -1, err + } + if n, _ := result.RowsAffected(); n == 0 { + return -1, sql.ErrNoRows + } + return ts, nil +} diff --git a/userapi/storage/shared/storage.go b/userapi/storage/shared/storage.go index 80b225c9..2feca052 100644 --- a/userapi/storage/shared/storage.go +++ b/userapi/storage/shared/storage.go @@ -1159,7 +1159,19 @@ func (d *KeyDatabase) StoreCrossSigningKeysForUser(ctx context.Context, userID s }) } -// StoreCrossSigningSigsForTarget stores a signature for a target user ID and key/dvice. +// UpdateMasterCrossSigningKeyAllowReplacementWithoutUIA updates the 'updatable_without_uia_before_ms' attribute of the master cross-signing key. +// Normally this attribute depending on its value marks the master key as replaceable without UIA. +func (d *KeyDatabase) UpdateMasterCrossSigningKeyAllowReplacementWithoutUIA(ctx context.Context, userID string, duration time.Duration) (int64, error) { + var ts int64 + err := d.Writer.Do(d.DB, nil, func(txn *sql.Tx) error { + var err error + ts, err = d.CrossSigningKeysTable.UpdateMasterCrossSigningKeyAllowReplacementWithoutUIA(ctx, txn, userID, duration) + return err + }) + return ts, err +} + +// StoreCrossSigningSigsForTarget stores a signature for a target user ID and key/device. func (d *KeyDatabase) StoreCrossSigningSigsForTarget( ctx context.Context, originUserID string, originKeyID gomatrixserverlib.KeyID, diff --git a/userapi/storage/sqlite3/cross_signing_keys_table.go b/userapi/storage/sqlite3/cross_signing_keys_table.go index 9c4d3cb5..47a39f6b 100644 --- a/userapi/storage/sqlite3/cross_signing_keys_table.go +++ b/userapi/storage/sqlite3/cross_signing_keys_table.go @@ -10,6 +10,7 @@ import ( "context" "database/sql" "fmt" + "time" "github.com/element-hq/dendrite/internal" "github.com/element-hq/dendrite/internal/sqlutil" @@ -41,11 +42,17 @@ const upsertCrossSigningKeysForUserSQL = "" + "INSERT OR REPLACE INTO keyserver_cross_signing_keys (user_id, key_type, key_data, updatable_without_uia_before_ms)" + " VALUES($1, $2, $3, $4)" +const updateMasterCrossSigningKeyAllowReplacementWithoutUiaSQL = "" + + "UPDATE keyserver_cross_signing_keys" + + " SET updatable_without_uia_before_ms = $3" + + " WHERE user_id = $1 AND key_type = $2" + type crossSigningKeysStatements struct { - db *sql.DB - selectCrossSigningKeysForUserStmt *sql.Stmt - selectCrossSigningKeysForUserAndKeyTypeStmt *sql.Stmt - upsertCrossSigningKeysForUserStmt *sql.Stmt + db *sql.DB + selectCrossSigningKeysForUserStmt *sql.Stmt + selectCrossSigningKeysForUserAndKeyTypeStmt *sql.Stmt + upsertCrossSigningKeysForUserStmt *sql.Stmt + updateMasterCrossSigningKeyAllowReplacementWithoutUiaStmt *sql.Stmt } func NewSqliteCrossSigningKeysTable(db *sql.DB) (tables.CrossSigningKeys, error) { @@ -60,6 +67,7 @@ func NewSqliteCrossSigningKeysTable(db *sql.DB) (tables.CrossSigningKeys, error) {&s.selectCrossSigningKeysForUserStmt, selectCrossSigningKeysForUserSQL}, {&s.selectCrossSigningKeysForUserAndKeyTypeStmt, selectCrossSigningKeysForUserAndKeyTypeSQL}, {&s.upsertCrossSigningKeysForUserStmt, upsertCrossSigningKeysForUserSQL}, + {&s.updateMasterCrossSigningKeyAllowReplacementWithoutUiaStmt, updateMasterCrossSigningKeyAllowReplacementWithoutUiaSQL}, }.Prepare(db) } @@ -137,3 +145,16 @@ func (s *crossSigningKeysStatements) UpsertCrossSigningKeysForUser( } return nil } + +func (s *crossSigningKeysStatements) UpdateMasterCrossSigningKeyAllowReplacementWithoutUIA(ctx context.Context, txn *sql.Tx, userID string, duration time.Duration) (int64, error) { + keyTypeInt, _ := types.KeyTypePurposeToInt[fclient.CrossSigningKeyPurposeMaster] + ts := time.Now().Add(duration).UnixMilli() + result, err := sqlutil.TxStmt(txn, s.updateMasterCrossSigningKeyAllowReplacementWithoutUiaStmt).ExecContext(ctx, userID, keyTypeInt, ts) + if err != nil { + return -1, err + } + if n, _ := result.RowsAffected(); n == 0 { + return -1, sql.ErrNoRows + } + return ts, nil +} diff --git a/userapi/storage/tables/interface.go b/userapi/storage/tables/interface.go index cfd1e571..8e629914 100644 --- a/userapi/storage/tables/interface.go +++ b/userapi/storage/tables/interface.go @@ -200,6 +200,7 @@ type CrossSigningKeys interface { SelectCrossSigningKeysForUser(ctx context.Context, txn *sql.Tx, userID string) (r types.CrossSigningKeyMap, err error) SelectCrossSigningKeysForUserAndKeyType(ctx context.Context, txn *sql.Tx, userID string, keyType fclient.CrossSigningKeyPurpose) (r types.CrossSigningKeyMap, err error) UpsertCrossSigningKeysForUser(ctx context.Context, txn *sql.Tx, userID string, keyType fclient.CrossSigningKeyPurpose, keyData spec.Base64Bytes, updatableWithoutUIABeforeMs *int64) error + UpdateMasterCrossSigningKeyAllowReplacementWithoutUIA(ctx context.Context, txn *sql.Tx, userID string, duration time.Duration) (int64, error) } type CrossSigningSigs interface {