Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MSC3967: Do not require UIA when first uploading cross signing keys #3471

Merged
merged 7 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 76 additions & 24 deletions clientapi/routing/key_crosssigning.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
package routing

import (
"context"
"net/http"
"time"

"github.com/matrix-org/gomatrixserverlib/fclient"
"github.com/sirupsen/logrus"

"github.com/element-hq/dendrite/clientapi/auth"
"github.com/element-hq/dendrite/clientapi/auth/authtypes"
Expand All @@ -23,10 +28,15 @@ type crossSigningRequest struct {
Auth newPasswordAuth `json:"auth"`
}

type UploadKeysAPI interface {
QueryKeys(ctx context.Context, req *api.QueryKeysRequest, res *api.QueryKeysResponse)
api.UploadDeviceKeysAPI
}

func UploadCrossSigningDeviceKeys(
req *http.Request, userInteractiveAuth *auth.UserInteractive,
keyserverAPI api.ClientKeyAPI, device *api.Device,
accountAPI api.ClientUserAPI, cfg *config.ClientAPI,
req *http.Request,
keyserverAPI UploadKeysAPI, device *api.Device,
accountAPI auth.GetAccountByPassword, cfg *config.ClientAPI,
) util.JSONResponse {
uploadReq := &crossSigningRequest{}
uploadRes := &api.PerformUploadDeviceKeysResponse{}
Expand All @@ -35,32 +45,59 @@ func UploadCrossSigningDeviceKeys(
if resErr != nil {
return *resErr
}
sessionID := uploadReq.Auth.Session
if sessionID == "" {
sessionID = util.RandomString(sessionIDLength)
}
if uploadReq.Auth.Type != authtypes.LoginTypePassword {

// Query existing keys to determine if UIA is required
keyResp := api.QueryKeysResponse{}
keyserverAPI.QueryKeys(req.Context(), &api.QueryKeysRequest{
UserID: device.UserID,
UserToDevices: map[string][]string{device.UserID: {device.ID}},
Timeout: time.Second * 10,
}, &keyResp)

if keyResp.Error != nil {
logrus.WithError(keyResp.Error).Error("Failed to query keys")
return util.JSONResponse{
Code: http.StatusUnauthorized,
JSON: newUserInteractiveResponse(
sessionID,
[]authtypes.Flow{
{
Stages: []authtypes.LoginType{authtypes.LoginTypePassword},
},
},
nil,
),
Code: http.StatusBadRequest,
JSON: spec.Unknown(keyResp.Error.Error()),
}
}
typePassword := auth.LoginTypePassword{
GetAccountByPassword: accountAPI.QueryAccountByPassword,
Config: cfg,

existingMasterKey, hasMasterKey := keyResp.MasterKeys[device.UserID]
requireUIA := false
if hasMasterKey {
// If we have a master key, check if any of the existing keys differ. If they do,
// we need to re-authenticate the user.
requireUIA = keysDiffer(existingMasterKey, keyResp, uploadReq, device.UserID)
}
if _, authErr := typePassword.Login(req.Context(), &uploadReq.Auth.PasswordRequest); authErr != nil {
return *authErr

if requireUIA {
sessionID := uploadReq.Auth.Session
if sessionID == "" {
sessionID = util.RandomString(sessionIDLength)
}
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,
Config: cfg,
}
if _, authErr := typePassword.Login(req.Context(), &uploadReq.Auth.PasswordRequest); authErr != nil {
return *authErr
}
sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypePassword)
}
sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypePassword)

uploadReq.UserID = device.UserID
keyserverAPI.PerformUploadDeviceKeys(req.Context(), &uploadReq.PerformUploadDeviceKeysRequest, uploadRes)
Expand Down Expand Up @@ -96,6 +133,21 @@ func UploadCrossSigningDeviceKeys(
}
}

func keysDiffer(existingMasterKey fclient.CrossSigningKey, keyResp api.QueryKeysResponse, uploadReq *crossSigningRequest, userID string) bool {
masterKeyEqual := existingMasterKey.Equal(&uploadReq.MasterKey)
if !masterKeyEqual {
return true
}
existingSelfSigningKey := keyResp.SelfSigningKeys[userID]
selfSigningEqual := existingSelfSigningKey.Equal(&uploadReq.SelfSigningKey)
if !selfSigningEqual {
return true
}
existingUserSigningKey := keyResp.UserSigningKeys[userID]
userSigningEqual := existingUserSigningKey.Equal(&uploadReq.UserSigningKey)
return !userSigningEqual
}

func UploadCrossSigningDeviceSignatures(req *http.Request, keyserverAPI api.ClientKeyAPI, device *api.Device) util.JSONResponse {
uploadReq := &api.PerformUploadDeviceSignaturesRequest{}
uploadRes := &api.PerformUploadDeviceSignaturesResponse{}
Expand Down
Loading
Loading