dendrite/clientapi/routing/key_crosssigning.go
Roman Isaev 9d9841d02e
mas: added "admin's replacement without uia" endpoint
i.e. /_synapse/admin/v1/users/{userID}/_allow_cross_signing_replacement_without_uia
2024-12-30 13:34:21 +00:00

200 lines
5.5 KiB
Go

// Copyright 2024 New Vector Ltd.
// Copyright 2021 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
package routing
import (
"net/http"
"strings"
"time"
"github.com/element-hq/dendrite/clientapi/auth"
"github.com/element-hq/dendrite/clientapi/auth/authtypes"
"github.com/element-hq/dendrite/clientapi/httputil"
"github.com/element-hq/dendrite/setup/config"
"github.com/element-hq/dendrite/userapi/api"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/util"
)
type crossSigningRequest struct {
api.PerformUploadDeviceKeysRequest
Auth newPasswordAuth `json:"auth"`
}
func UploadCrossSigningDeviceKeys(
req *http.Request, userInteractiveAuth *auth.UserInteractive,
keyserverAPI api.ClientKeyAPI, device *api.Device,
accountAPI api.ClientUserAPI, cfg *config.ClientAPI,
) util.JSONResponse {
uploadReq := &crossSigningRequest{}
uploadRes := &api.PerformUploadDeviceKeysResponse{}
resErr := httputil.UnmarshalJSONRequest(req, &uploadReq)
if resErr != nil {
return *resErr
}
sessionID := uploadReq.Auth.Session
if sessionID == "" {
sessionID = util.RandomString(sessionIDLength)
}
isCrossSigningSetup := false
masterKeyUpdatableWithoutUIA := false
{
var keysResp api.QueryMasterKeysResponse
keyserverAPI.QueryMasterKeys(req.Context(), &api.QueryMasterKeysRequest{UserID: device.UserID}, &keysResp)
if err := keysResp.Error; err != nil {
return convertKeyError(err)
}
if k := keysResp.Key; k != nil {
isCrossSigningSetup = true
if k.UpdatableWithoutUIABeforeMs != nil {
masterKeyUpdatableWithoutUIA = time.Now().UnixMilli() < *k.UpdatableWithoutUIABeforeMs
}
}
}
if isCrossSigningSetup {
// With MSC3861, UIA is not possible. Instead, the auth service has to explicitly mark the master key as replaceable.
if cfg.MSCs.MSC3861Enabled() {
if !masterKeyUpdatableWithoutUIA {
url := ""
if m := cfg.MSCs.MSC3861; m.AccountManagementURL != "" {
url = strings.Join([]string{m.AccountManagementURL, "?action=", authtypes.LoginTypeCrossSigningReset}, "")
} else {
url = m.Issuer
}
return util.JSONResponse{
Code: http.StatusUnauthorized,
JSON: newUserInteractiveResponse(
"dummy",
[]authtypes.Flow{
{
Stages: []authtypes.LoginType{authtypes.LoginTypeCrossSigningReset},
},
},
map[string]interface{}{
authtypes.LoginTypeCrossSigningReset: map[string]string{
"url": url,
},
},
strings.Join([]string{
"To reset your end-to-end encryption cross-signing, identity, you first need to approve it at",
url,
"and then try again.",
}, " "),
),
}
}
// XXX: is it necessary?
sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypeCrossSigningReset)
} else {
if uploadReq.Auth.Type != authtypes.LoginTypePassword {
return util.JSONResponse{
Code: http.StatusUnauthorized,
JSON: newUserInteractiveResponse(
sessionID,
[]authtypes.Flow{
{
Stages: []authtypes.LoginType{authtypes.LoginTypePassword},
},
},
nil,
"",
),
}
}
typePassword := auth.LoginTypePassword{
GetAccountByPassword: accountAPI.QueryAccountByPassword,
Config: cfg,
}
if _, authErr := typePassword.Login(req.Context(), &uploadReq.Auth.PasswordRequest); authErr != nil {
return *authErr
}
sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypePassword)
}
}
uploadReq.UserID = device.UserID
keyserverAPI.PerformUploadDeviceKeys(req.Context(), &uploadReq.PerformUploadDeviceKeysRequest, uploadRes)
if err := uploadRes.Error; err != nil {
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()),
}
}
}
return util.JSONResponse{
Code: http.StatusOK,
JSON: struct{}{},
}
}
func UploadCrossSigningDeviceSignatures(req *http.Request, keyserverAPI api.ClientKeyAPI, device *api.Device) util.JSONResponse {
uploadReq := &api.PerformUploadDeviceSignaturesRequest{}
uploadRes := &api.PerformUploadDeviceSignaturesResponse{}
if err := httputil.UnmarshalJSONRequest(req, &uploadReq.Signatures); err != nil {
return *err
}
uploadReq.UserID = device.UserID
keyserverAPI.PerformUploadDeviceSignatures(req.Context(), uploadReq, uploadRes)
if err := uploadRes.Error; err != nil {
return convertKeyError(err)
}
return util.JSONResponse{
Code: http.StatusOK,
JSON: struct{}{},
}
}
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()),
}
}
}