forked from mautrix/go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
keysharing.go
361 lines (327 loc) · 13.4 KB
/
keysharing.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
// Copyright (c) 2020 Nikos Filippakis
// Copyright (c) 2023 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package crypto
import (
"context"
"errors"
"time"
"github.com/rs/zerolog"
"github.com/Saleschat/mautrix-go/crypto/olm"
"github.com/Saleschat/mautrix-go/id"
"github.com/Saleschat/mautrix-go"
"github.com/Saleschat/mautrix-go/event"
)
type KeyShareRejection struct {
Code event.RoomKeyWithheldCode
Reason string
}
var (
// Reject a key request without responding
KeyShareRejectNoResponse = KeyShareRejection{}
KeyShareRejectBlacklisted = KeyShareRejection{event.RoomKeyWithheldBlacklisted, "You have been blacklisted by this device"}
KeyShareRejectUnverified = KeyShareRejection{event.RoomKeyWithheldUnverified, "This device does not share keys to unverified devices"}
KeyShareRejectOtherUser = KeyShareRejection{event.RoomKeyWithheldUnauthorized, "This device does not share keys to other users"}
KeyShareRejectUnavailable = KeyShareRejection{event.RoomKeyWithheldUnavailable, "Requested session ID not found on this device"}
KeyShareRejectInternalError = KeyShareRejection{event.RoomKeyWithheldUnavailable, "An internal error occurred while trying to share the requested session"}
)
// RequestRoomKey sends a key request for a room to the current user's devices. If the context is cancelled, then so is the key request.
// Returns a bool channel that will get notified either when the key is received or the request is cancelled.
//
// Deprecated: this only supports a single key request target, so the whole automatic cancelling feature isn't very useful.
func (mach *OlmMachine) RequestRoomKey(ctx context.Context, toUser id.UserID, toDevice id.DeviceID,
roomID id.RoomID, senderKey id.SenderKey, sessionID id.SessionID) (chan bool, error) {
requestID := mach.Client.TxnID()
keyResponseReceived := make(chan struct{})
mach.roomKeyRequestFilled.Store(sessionID, keyResponseReceived)
err := mach.SendRoomKeyRequest(roomID, senderKey, sessionID, requestID, map[id.UserID][]id.DeviceID{toUser: {toDevice}})
if err != nil {
return nil, err
}
resChan := make(chan bool, 1)
go func() {
select {
case <-keyResponseReceived:
// key request successful
mach.Log.Debug().Msgf("Key for session %v was received, cancelling other key requests", sessionID)
resChan <- true
case <-ctx.Done():
// if the context is done, key request was unsuccessful
mach.Log.Debug().Msgf("Context closed (%v) before forwared key for session %v received, sending key request cancellation", ctx.Err(), sessionID)
resChan <- false
}
// send a message to all devices cancelling this key request
mach.roomKeyRequestFilled.Delete(sessionID)
cancelEvtContent := &event.Content{
Parsed: event.RoomKeyRequestEventContent{
Action: event.KeyRequestActionCancel,
RequestID: requestID,
RequestingDeviceID: mach.Client.DeviceID,
},
}
toDeviceCancel := &mautrix.ReqSendToDevice{
Messages: map[id.UserID]map[id.DeviceID]*event.Content{
toUser: {
toDevice: cancelEvtContent,
},
},
}
mach.Client.SendToDevice(event.ToDeviceRoomKeyRequest, toDeviceCancel)
}()
return resChan, nil
}
// SendRoomKeyRequest sends a key request for the given key (identified by the room ID, sender key and session ID) to the given users.
//
// The request ID parameter is optional. If it's empty, a random ID will be generated.
//
// This function does not wait for the keys to arrive. You can use WaitForSession to wait for the session to
// arrive (in any way, not just as a reply to this request). There's also RequestRoomKey which waits for a response
// to the specific key request, but currently it only supports a single target device and is therefore deprecated.
// A future function may properly support multiple targets and automatically canceling the other requests when receiving
// the first response.
func (mach *OlmMachine) SendRoomKeyRequest(roomID id.RoomID, senderKey id.SenderKey, sessionID id.SessionID, requestID string, users map[id.UserID][]id.DeviceID) error {
if len(requestID) == 0 {
requestID = mach.Client.TxnID()
}
requestEvent := &event.Content{
Parsed: &event.RoomKeyRequestEventContent{
Action: event.KeyRequestActionRequest,
Body: event.RequestedKeyInfo{
Algorithm: id.AlgorithmMegolmV1,
RoomID: roomID,
SenderKey: senderKey,
SessionID: sessionID,
},
RequestID: requestID,
RequestingDeviceID: mach.Client.DeviceID,
},
}
toDeviceReq := &mautrix.ReqSendToDevice{
Messages: make(map[id.UserID]map[id.DeviceID]*event.Content, len(users)),
}
for user, devices := range users {
toDeviceReq.Messages[user] = make(map[id.DeviceID]*event.Content, len(devices))
for _, device := range devices {
toDeviceReq.Messages[user][device] = requestEvent
}
}
_, err := mach.Client.SendToDevice(event.ToDeviceRoomKeyRequest, toDeviceReq)
return err
}
func (mach *OlmMachine) importForwardedRoomKey(ctx context.Context, evt *DecryptedOlmEvent, content *event.ForwardedRoomKeyEventContent) bool {
log := zerolog.Ctx(ctx).With().
Str("session_id", content.SessionID.String()).
Str("room_id", content.RoomID.String()).
Logger()
if content.Algorithm != id.AlgorithmMegolmV1 || evt.Keys.Ed25519 == "" {
log.Debug().
Str("algorithm", string(content.Algorithm)).
Msg("Ignoring weird forwarded room key")
return false
}
igsInternal, err := olm.InboundGroupSessionImport([]byte(content.SessionKey))
if err != nil {
log.Error().Err(err).Msg("Failed to import inbound group session")
return false
} else if igsInternal.ID() != content.SessionID {
log.Warn().
Str("actual_session_id", igsInternal.ID().String()).
Msg("Mismatched session ID while creating inbound group session from forward")
return false
}
config := mach.StateStore.GetEncryptionEvent(content.RoomID)
var maxAge time.Duration
var maxMessages int
if config != nil {
maxAge = time.Duration(config.RotationPeriodMillis) * time.Millisecond
maxMessages = config.RotationPeriodMessages
}
if content.MaxAge != 0 {
maxAge = time.Duration(content.MaxAge) * time.Millisecond
}
if content.MaxMessages != 0 {
maxMessages = content.MaxMessages
}
igs := &InboundGroupSession{
Internal: *igsInternal,
SigningKey: evt.Keys.Ed25519,
SenderKey: content.SenderKey,
RoomID: content.RoomID,
ForwardingChains: append(content.ForwardingKeyChain, evt.SenderKey.String()),
id: content.SessionID,
ReceivedAt: time.Now().UTC(),
MaxAge: maxAge.Milliseconds(),
MaxMessages: maxMessages,
IsScheduled: content.IsScheduled,
}
err = mach.CryptoStore.PutGroupSession(content.RoomID, content.SenderKey, content.SessionID, igs)
if err != nil {
log.Error().Err(err).Msg("Failed to store new inbound group session")
return false
}
mach.markSessionReceived(content.SessionID)
log.Debug().Msg("Received forwarded inbound group session")
return true
}
func (mach *OlmMachine) rejectKeyRequest(rejection KeyShareRejection, device *id.Device, request event.RequestedKeyInfo) {
if rejection.Code == "" {
// If the rejection code is empty, it means don't share keys, but also don't tell the requester.
return
}
content := event.RoomKeyWithheldEventContent{
RoomID: request.RoomID,
Algorithm: request.Algorithm,
SessionID: request.SessionID,
SenderKey: request.SenderKey,
Code: rejection.Code,
Reason: rejection.Reason,
}
err := mach.sendToOneDevice(device.UserID, device.DeviceID, event.ToDeviceRoomKeyWithheld, &content)
if err != nil {
mach.Log.Warn().Err(err).
Str("code", string(rejection.Code)).
Str("user_id", device.UserID.String()).
Str("device_id", device.DeviceID.String()).
Msg("Failed to send key share rejection")
}
err = mach.sendToOneDevice(device.UserID, device.DeviceID, event.ToDeviceOrgMatrixRoomKeyWithheld, &content)
if err != nil {
mach.Log.Warn().Err(err).
Str("code", string(rejection.Code)).
Str("user_id", device.UserID.String()).
Str("device_id", device.DeviceID.String()).
Msg("Failed to send key share rejection (legacy event type)")
}
}
func (mach *OlmMachine) defaultAllowKeyShare(ctx context.Context, device *id.Device, _ event.RequestedKeyInfo) *KeyShareRejection {
log := mach.machOrContextLog(ctx)
if mach.Client.UserID != device.UserID {
log.Debug().Msg("Rejecting key request from a different user")
return &KeyShareRejectOtherUser
} else if mach.Client.DeviceID == device.DeviceID {
log.Debug().Msg("Ignoring key request from ourselves")
return &KeyShareRejectNoResponse
} else if device.Trust == id.TrustStateBlacklisted {
log.Debug().Msg("Rejecting key request from blacklisted device")
return &KeyShareRejectBlacklisted
} else if trustState := mach.ResolveTrust(device); trustState >= mach.ShareKeysMinTrust {
log.Debug().
Str("min_trust", mach.SendKeysMinTrust.String()).
Str("device_trust", trustState.String()).
Msg("Accepting key request from trusted device")
return nil
} else {
log.Debug().
Str("min_trust", mach.SendKeysMinTrust.String()).
Str("device_trust", trustState.String()).
Msg("Rejecting key request from untrusted device")
return &KeyShareRejectUnverified
}
}
func (mach *OlmMachine) handleRoomKeyRequest(ctx context.Context, sender id.UserID, content *event.RoomKeyRequestEventContent) {
log := zerolog.Ctx(ctx).With().
Str("request_id", content.RequestID).
Str("device_id", content.RequestingDeviceID.String()).
Str("room_id", content.Body.RoomID.String()).
Str("session_id", content.Body.SessionID.String()).
Logger()
ctx = log.WithContext(ctx)
if content.Action != event.KeyRequestActionRequest {
return
} else if content.RequestingDeviceID == mach.Client.DeviceID && sender == mach.Client.UserID {
log.Debug().Msg("Ignoring key request from ourselves")
return
}
log.Debug().Msg("Received key request")
device, err := mach.GetOrFetchDevice(ctx, sender, content.RequestingDeviceID)
if err != nil {
log.Error().Err(err).Msg("Failed to fetch device that requested keys")
return
}
rejection := mach.AllowKeyShare(ctx, device, content.Body)
if rejection != nil {
mach.rejectKeyRequest(*rejection, device, content.Body)
return
}
igs, err := mach.CryptoStore.GetGroupSession(content.Body.RoomID, content.Body.SenderKey, content.Body.SessionID)
if err != nil {
if errors.Is(err, ErrGroupSessionWithheld) {
log.Debug().Err(err).Msg("Requested group session not available")
mach.rejectKeyRequest(KeyShareRejectUnavailable, device, content.Body)
} else {
log.Error().Err(err).Msg("Failed to get group session to forward")
mach.rejectKeyRequest(KeyShareRejectInternalError, device, content.Body)
}
return
} else if igs == nil {
log.Error().Msg("Didn't find group session to forward")
mach.rejectKeyRequest(KeyShareRejectUnavailable, device, content.Body)
return
}
if internalID := igs.ID(); internalID != content.Body.SessionID {
// Should this be an error?
log = log.With().Str("unexpected_session_id", internalID.String()).Logger()
}
firstKnownIndex := igs.Internal.FirstKnownIndex()
log = log.With().Uint32("first_known_index", firstKnownIndex).Logger()
exportedKey, err := igs.Internal.Export(firstKnownIndex)
if err != nil {
log.Error().Err(err).Msg("Failed to export group session to forward")
mach.rejectKeyRequest(KeyShareRejectInternalError, device, content.Body)
return
}
forwardedRoomKey := event.Content{
Parsed: &event.ForwardedRoomKeyEventContent{
RoomKeyEventContent: event.RoomKeyEventContent{
Algorithm: id.AlgorithmMegolmV1,
RoomID: igs.RoomID,
SessionID: igs.ID(),
SessionKey: string(exportedKey),
},
SenderKey: content.Body.SenderKey,
ForwardingKeyChain: igs.ForwardingChains,
SenderClaimedKey: igs.SigningKey,
},
}
if err = mach.SendEncryptedToDevice(ctx, device, event.ToDeviceForwardedRoomKey, forwardedRoomKey); err != nil {
log.Error().Err(err).Msg("Failed to encrypt and send group session")
} else {
log.Debug().Msg("Successfully sent forwarded group session")
}
}
func (mach *OlmMachine) handleBeeperRoomKeyAck(ctx context.Context, sender id.UserID, content *event.BeeperRoomKeyAckEventContent) {
log := mach.machOrContextLog(ctx).With().
Str("room_id", content.RoomID.String()).
Str("session_id", content.SessionID.String()).
Int("first_message_index", content.FirstMessageIndex).
Logger()
sess, err := mach.CryptoStore.GetGroupSession(content.RoomID, "", content.SessionID)
if err != nil {
if errors.Is(err, ErrGroupSessionWithheld) {
log.Debug().Err(err).Msg("Acked group session was already redacted")
} else {
log.Err(err).Msg("Failed to get group session to check if it should be redacted")
}
return
} else if sess == nil {
log.Warn().Msg("Got key backup ack for unknown session")
return
}
log = log.With().
Str("sender_key", sess.SenderKey.String()).
Str("own_identity", mach.OwnIdentity().IdentityKey.String()).
Logger()
isInbound := sess.SenderKey == mach.OwnIdentity().IdentityKey
if isInbound && mach.DeleteOutboundKeysOnAck && content.FirstMessageIndex == 0 {
log.Debug().Msg("Redacting inbound copy of outbound group session after ack")
err = mach.CryptoStore.RedactGroupSession(content.RoomID, sess.SenderKey, content.SessionID, "outbound session acked")
if err != nil {
log.Err(err).Msg("Failed to redact group session")
}
} else {
log.Debug().Bool("inbound", isInbound).Msg("Received room key ack")
}
}