Skip to content

Commit 7f4ba1f

Browse files
authored
MSC3967: Do not require UIA when first uploading cross signing keys (#3471)
Playing around with Copilot, tests are generated. Requires matrix-org/gomatrixserverlib#444
1 parent 40bef6a commit 7f4ba1f

File tree

3 files changed

+393
-25
lines changed

3 files changed

+393
-25
lines changed

clientapi/routing/key_crosssigning.go

Lines changed: 76 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77
package routing
88

99
import (
10+
"context"
1011
"net/http"
12+
"time"
13+
14+
"github.com/matrix-org/gomatrixserverlib/fclient"
15+
"github.com/sirupsen/logrus"
1116

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

31+
type UploadKeysAPI interface {
32+
QueryKeys(ctx context.Context, req *api.QueryKeysRequest, res *api.QueryKeysResponse)
33+
api.UploadDeviceKeysAPI
34+
}
35+
2636
func UploadCrossSigningDeviceKeys(
27-
req *http.Request, userInteractiveAuth *auth.UserInteractive,
28-
keyserverAPI api.ClientKeyAPI, device *api.Device,
29-
accountAPI api.ClientUserAPI, cfg *config.ClientAPI,
37+
req *http.Request,
38+
keyserverAPI UploadKeysAPI, device *api.Device,
39+
accountAPI auth.GetAccountByPassword, cfg *config.ClientAPI,
3040
) util.JSONResponse {
3141
uploadReq := &crossSigningRequest{}
3242
uploadRes := &api.PerformUploadDeviceKeysResponse{}
@@ -35,32 +45,59 @@ func UploadCrossSigningDeviceKeys(
3545
if resErr != nil {
3646
return *resErr
3747
}
38-
sessionID := uploadReq.Auth.Session
39-
if sessionID == "" {
40-
sessionID = util.RandomString(sessionIDLength)
41-
}
42-
if uploadReq.Auth.Type != authtypes.LoginTypePassword {
48+
49+
// Query existing keys to determine if UIA is required
50+
keyResp := api.QueryKeysResponse{}
51+
keyserverAPI.QueryKeys(req.Context(), &api.QueryKeysRequest{
52+
UserID: device.UserID,
53+
UserToDevices: map[string][]string{device.UserID: {device.ID}},
54+
Timeout: time.Second * 10,
55+
}, &keyResp)
56+
57+
if keyResp.Error != nil {
58+
logrus.WithError(keyResp.Error).Error("Failed to query keys")
4359
return util.JSONResponse{
44-
Code: http.StatusUnauthorized,
45-
JSON: newUserInteractiveResponse(
46-
sessionID,
47-
[]authtypes.Flow{
48-
{
49-
Stages: []authtypes.LoginType{authtypes.LoginTypePassword},
50-
},
51-
},
52-
nil,
53-
),
60+
Code: http.StatusBadRequest,
61+
JSON: spec.Unknown(keyResp.Error.Error()),
5462
}
5563
}
56-
typePassword := auth.LoginTypePassword{
57-
GetAccountByPassword: accountAPI.QueryAccountByPassword,
58-
Config: cfg,
64+
65+
existingMasterKey, hasMasterKey := keyResp.MasterKeys[device.UserID]
66+
requireUIA := false
67+
if hasMasterKey {
68+
// If we have a master key, check if any of the existing keys differ. If they do,
69+
// we need to re-authenticate the user.
70+
requireUIA = keysDiffer(existingMasterKey, keyResp, uploadReq, device.UserID)
5971
}
60-
if _, authErr := typePassword.Login(req.Context(), &uploadReq.Auth.PasswordRequest); authErr != nil {
61-
return *authErr
72+
73+
if requireUIA {
74+
sessionID := uploadReq.Auth.Session
75+
if sessionID == "" {
76+
sessionID = util.RandomString(sessionIDLength)
77+
}
78+
if uploadReq.Auth.Type != authtypes.LoginTypePassword {
79+
return util.JSONResponse{
80+
Code: http.StatusUnauthorized,
81+
JSON: newUserInteractiveResponse(
82+
sessionID,
83+
[]authtypes.Flow{
84+
{
85+
Stages: []authtypes.LoginType{authtypes.LoginTypePassword},
86+
},
87+
},
88+
nil,
89+
),
90+
}
91+
}
92+
typePassword := auth.LoginTypePassword{
93+
GetAccountByPassword: accountAPI,
94+
Config: cfg,
95+
}
96+
if _, authErr := typePassword.Login(req.Context(), &uploadReq.Auth.PasswordRequest); authErr != nil {
97+
return *authErr
98+
}
99+
sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypePassword)
62100
}
63-
sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypePassword)
64101

65102
uploadReq.UserID = device.UserID
66103
keyserverAPI.PerformUploadDeviceKeys(req.Context(), &uploadReq.PerformUploadDeviceKeysRequest, uploadRes)
@@ -96,6 +133,21 @@ func UploadCrossSigningDeviceKeys(
96133
}
97134
}
98135

136+
func keysDiffer(existingMasterKey fclient.CrossSigningKey, keyResp api.QueryKeysResponse, uploadReq *crossSigningRequest, userID string) bool {
137+
masterKeyEqual := existingMasterKey.Equal(&uploadReq.MasterKey)
138+
if !masterKeyEqual {
139+
return true
140+
}
141+
existingSelfSigningKey := keyResp.SelfSigningKeys[userID]
142+
selfSigningEqual := existingSelfSigningKey.Equal(&uploadReq.SelfSigningKey)
143+
if !selfSigningEqual {
144+
return true
145+
}
146+
existingUserSigningKey := keyResp.UserSigningKeys[userID]
147+
userSigningEqual := existingUserSigningKey.Equal(&uploadReq.UserSigningKey)
148+
return !userSigningEqual
149+
}
150+
99151
func UploadCrossSigningDeviceSignatures(req *http.Request, keyserverAPI api.ClientKeyAPI, device *api.Device) util.JSONResponse {
100152
uploadReq := &api.PerformUploadDeviceSignaturesRequest{}
101153
uploadRes := &api.PerformUploadDeviceSignaturesResponse{}

0 commit comments

Comments
 (0)