/
reciprocate.go
274 lines (238 loc) · 9.13 KB
/
reciprocate.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
// Copyright (c) 2024 Sumner Evans
//
// 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 verificationhelper
import (
"bytes"
"context"
"fmt"
"golang.org/x/exp/slices"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
// HandleScannedQRData verifies the keys from a scanned QR code and if
// successful, sends the m.key.verification.start event and
// m.key.verification.done event.
func (vh *VerificationHelper) HandleScannedQRData(ctx context.Context, data []byte) error {
qrCode, err := NewQRCodeFromBytes(data)
if err != nil {
return err
}
log := vh.getLog(ctx).With().
Str("verification_action", "handle scanned QR data").
Stringer("transaction_id", qrCode.TransactionID).
Int("mode", int(qrCode.Mode)).
Logger()
vh.activeTransactionsLock.Lock()
defer vh.activeTransactionsLock.Unlock()
txn, ok := vh.activeTransactions[qrCode.TransactionID]
if !ok {
log.Warn().Msg("Ignoring QR code scan for an unknown transaction")
return nil
} else if txn.VerificationState != verificationStateReady {
log.Warn().Msg("Ignoring QR code scan for a transaction that is not in the ready state")
return nil
}
txn.VerificationState = verificationStateTheirQRScanned
// Verify the keys
log.Info().Msg("Verifying keys from QR code")
switch qrCode.Mode {
case QRCodeModeCrossSigning:
panic("unimplemented")
// TODO verify and sign their master key
case QRCodeModeSelfVerifyingMasterKeyTrusted:
// The QR was created by a device that trusts the master key, which
// means that we don't trust the key. Key1 is the master key public
// key, and Key2 is what the other device thinks our device key is.
if vh.client.UserID != txn.TheirUser {
return fmt.Errorf("mode %d is only allowed when the other user is the same as the current user", qrCode.Mode)
}
// Verify the master key is correct
crossSigningPubkeys := vh.mach.GetOwnCrossSigningPublicKeys(ctx)
if bytes.Equal(crossSigningPubkeys.MasterKey.Bytes(), qrCode.Key1[:]) {
log.Info().Msg("Verified that the other device has the same master key")
} else {
return fmt.Errorf("the master key does not match")
}
// Verify that the device key that the other device things we have is
// correct.
myKeys := vh.mach.OwnIdentity()
if bytes.Equal(myKeys.SigningKey.Bytes(), qrCode.Key2[:]) {
log.Info().Msg("Verified that the other device has the correct key for this device")
} else {
return fmt.Errorf("the other device has the wrong key for this device")
}
case QRCodeModeSelfVerifyingMasterKeyUntrusted:
// The QR was created by a device that does not trust the master key,
// which means that we do trust the master key. Key1 is the other
// device's device key, and Key2 is what the other device thinks the
// master key is.
if vh.client.UserID != txn.TheirUser {
return fmt.Errorf("mode %d is only allowed when the other user is the same as the current user", qrCode.Mode)
}
// Get their device
theirDevice, err := vh.mach.GetOrFetchDevice(ctx, txn.TheirUser, txn.TheirDevice)
if err != nil {
return err
}
// Verify that the other device's key is what we expect.
if bytes.Equal(theirDevice.SigningKey.Bytes(), qrCode.Key1[:]) {
log.Info().Msg("Verified that the other device key is what we expected")
} else {
return fmt.Errorf("the other device's key is not what we expected")
}
// Verify that what they think the master key is is correct.
if bytes.Equal(vh.mach.GetOwnCrossSigningPublicKeys(ctx).MasterKey.Bytes(), qrCode.Key2[:]) {
log.Info().Msg("Verified that the other device has the correct master key")
} else {
return fmt.Errorf("the master key does not match")
}
// Trust their device
theirDevice.Trust = id.TrustStateVerified
err = vh.mach.CryptoStore.PutDevice(ctx, txn.TheirUser, theirDevice)
if err != nil {
return fmt.Errorf("failed to update device trust state after verifying: %w", err)
}
// Cross-sign their device with the self-signing key
err = vh.mach.SignOwnDevice(ctx, theirDevice)
if err != nil {
return fmt.Errorf("failed to sign their device: %w", err)
}
default:
return fmt.Errorf("unknown QR code mode %d", qrCode.Mode)
}
// Send a m.key.verification.start event with the secret
txn.StartedByUs = true
txn.StartEventContent = &event.VerificationStartEventContent{
FromDevice: vh.client.DeviceID,
Method: event.VerificationMethodReciprocate,
Secret: qrCode.SharedSecret,
}
err = vh.sendVerificationEvent(ctx, txn, event.InRoomVerificationStart, txn.StartEventContent)
if err != nil {
return err
}
// Immediately send the m.key.verification.done event, as our side of the
// transaction is done.
err = vh.sendVerificationEvent(ctx, txn, event.InRoomVerificationDone, &event.VerificationDoneEventContent{})
if err != nil {
return err
}
txn.SentOurDone = true
if txn.ReceivedTheirDone {
txn.VerificationState = verificationStateDone
vh.verificationDone(ctx, txn.TransactionID)
}
return nil
}
// ConfirmQRCodeScanned confirms that our QR code has been scanned and sends the
// m.key.verification.done event to the other device.
func (vh *VerificationHelper) ConfirmQRCodeScanned(ctx context.Context, txnID id.VerificationTransactionID) error {
log := vh.getLog(ctx).With().
Str("verification_action", "confirm QR code scanned").
Stringer("transaction_id", txnID).
Logger()
vh.activeTransactionsLock.Lock()
defer vh.activeTransactionsLock.Unlock()
txn, ok := vh.activeTransactions[txnID]
if !ok {
log.Warn().Msg("Ignoring QR code scan confirmation for an unknown transaction")
return nil
} else if txn.VerificationState != verificationStateOurQRScanned {
log.Warn().Msg("Ignoring QR code scan confirmation for a transaction that is not in the started state")
return nil
}
log.Info().Msg("Confirming QR code scanned")
if txn.TheirUser == vh.client.UserID {
// Self-signing situation. Trust their device.
// Get their device
theirDevice, err := vh.mach.GetOrFetchDevice(ctx, txn.TheirUser, txn.TheirDevice)
if err != nil {
return err
}
// Trust their device
theirDevice.Trust = id.TrustStateVerified
err = vh.mach.CryptoStore.PutDevice(ctx, txn.TheirUser, theirDevice)
if err != nil {
return fmt.Errorf("failed to update device trust state after verifying: %w", err)
}
// Cross-sign their device with the self-signing key
if vh.mach.CrossSigningKeys != nil {
err = vh.mach.SignOwnDevice(ctx, theirDevice)
if err != nil {
return fmt.Errorf("failed to sign their device: %w", err)
}
}
}
// TODO: handle QR codes that are not self-signing situations
err := vh.sendVerificationEvent(ctx, txn, event.InRoomVerificationDone, &event.VerificationDoneEventContent{})
if err != nil {
return err
}
txn.SentOurDone = true
if txn.ReceivedTheirDone {
txn.VerificationState = verificationStateDone
vh.verificationDone(ctx, txn.TransactionID)
}
return nil
}
func (vh *VerificationHelper) generateAndShowQRCode(ctx context.Context, txn *verificationTransaction) error {
log := vh.getLog(ctx).With().
Str("verification_action", "generate and show QR code").
Stringer("transaction_id", txn.TransactionID).
Logger()
if vh.showQRCode == nil {
log.Warn().Msg("Ignoring QR code generation request as showing a QR code is not enabled on this device")
return nil
}
if !slices.Contains(txn.TheirSupportedMethods, event.VerificationMethodQRCodeScan) {
log.Warn().Msg("Ignoring QR code generation request as other device cannot scan QR codes")
return nil
}
ownCrossSigningPublicKeys := vh.mach.GetOwnCrossSigningPublicKeys(ctx)
mode := QRCodeModeCrossSigning
if vh.client.UserID == txn.TheirUser {
// This is a self-signing situation.
if trusted, err := vh.mach.IsUserTrusted(ctx, vh.client.UserID); err != nil {
return err
} else if trusted {
mode = QRCodeModeSelfVerifyingMasterKeyTrusted
} else {
mode = QRCodeModeSelfVerifyingMasterKeyUntrusted
}
}
var key1, key2 []byte
switch mode {
case QRCodeModeCrossSigning:
// Key 1 is the current user's master signing key.
key1 = ownCrossSigningPublicKeys.MasterKey.Bytes()
// Key 2 is the other user's master signing key.
theirSigningKeys, err := vh.mach.GetCrossSigningPublicKeys(ctx, txn.TheirUser)
if err != nil {
return err
}
key2 = theirSigningKeys.MasterKey.Bytes()
case QRCodeModeSelfVerifyingMasterKeyTrusted:
// Key 1 is the current user's master signing key.
key1 = ownCrossSigningPublicKeys.MasterKey.Bytes()
// Key 2 is the other device's key.
theirDevice, err := vh.mach.GetOrFetchDevice(ctx, txn.TheirUser, txn.TheirDevice)
if err != nil {
return err
}
key2 = theirDevice.IdentityKey.Bytes()
case QRCodeModeSelfVerifyingMasterKeyUntrusted:
// Key 1 is the current device's key
key1 = vh.mach.OwnIdentity().IdentityKey.Bytes()
// Key 2 is the master signing key.
key2 = ownCrossSigningPublicKeys.MasterKey.Bytes()
default:
log.Fatal().Str("mode", string(mode)).Msg("Unknown QR code mode")
}
qrCode := NewQRCode(mode, txn.TransactionID, [32]byte(key1), [32]byte(key2))
txn.QRCodeSharedSecret = qrCode.SharedSecret
vh.showQRCode(ctx, txn.TransactionID, qrCode)
return nil
}