verificationhelper: better errors/logs and more aggressive cancellations

Signed-off-by: Sumner Evans <sumner.evans@automattic.com>
This commit is contained in:
Sumner Evans 2024-05-21 15:59:48 -06:00
commit 3dbf8ef2f0
No known key found for this signature in database
4 changed files with 119 additions and 62 deletions

View file

@ -35,11 +35,9 @@ func (vh *VerificationHelper) HandleScannedQRData(ctx context.Context, data []by
txn, ok := vh.activeTransactions[qrCode.TransactionID] txn, ok := vh.activeTransactions[qrCode.TransactionID]
if !ok { if !ok {
log.Warn().Msg("Ignoring QR code scan for an unknown transaction") return fmt.Errorf("unknown transaction ID found in QR code")
return nil
} else if txn.VerificationState != verificationStateReady { } else if txn.VerificationState != verificationStateReady {
log.Warn().Msg("Ignoring QR code scan for a transaction that is not in the ready state") return vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUnexpectedMessage, "transaction found in the QR code is not in the ready state")
return nil
} }
txn.VerificationState = verificationStateTheirQRScanned txn.VerificationState = verificationStateTheirQRScanned
@ -57,14 +55,14 @@ func (vh *VerificationHelper) HandleScannedQRData(ctx context.Context, data []by
// key, and Key2 is what the other device thinks our device key is. // key, and Key2 is what the other device thinks our device key is.
if vh.client.UserID != txn.TheirUser { 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) return vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUnexpectedMessage, "mode %d is only allowed when the other user is the same as the current user", qrCode.Mode)
} }
// Verify the master key is correct // Verify the master key is correct
if bytes.Equal(ownCrossSigningPublicKeys.MasterKey.Bytes(), qrCode.Key1[:]) { if bytes.Equal(ownCrossSigningPublicKeys.MasterKey.Bytes(), qrCode.Key1[:]) {
log.Info().Msg("Verified that the other device has the same master key") log.Info().Msg("Verified that the other device has the same master key")
} else { } else {
return fmt.Errorf("the master key does not match") return vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeKeyMismatch, "the master key does not match")
} }
// Verify that the device key that the other device things we have is // Verify that the device key that the other device things we have is
@ -73,11 +71,11 @@ func (vh *VerificationHelper) HandleScannedQRData(ctx context.Context, data []by
if bytes.Equal(myKeys.SigningKey.Bytes(), qrCode.Key2[:]) { if bytes.Equal(myKeys.SigningKey.Bytes(), qrCode.Key2[:]) {
log.Info().Msg("Verified that the other device has the correct key for this device") log.Info().Msg("Verified that the other device has the correct key for this device")
} else { } else {
return fmt.Errorf("the other device has the wrong key for this device") return vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeKeyMismatch, "the other device has the wrong key for this device")
} }
if err := vh.mach.SignOwnMasterKey(ctx); err != nil { if err := vh.mach.SignOwnMasterKey(ctx); err != nil {
return fmt.Errorf("failed to sign own master key: %w", err) return vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeKeyMismatch, "failed to sign own master key: %w", err)
} }
case QRCodeModeSelfVerifyingMasterKeyUntrusted: case QRCodeModeSelfVerifyingMasterKeyUntrusted:
// The QR was created by a device that does not trust the master key, // The QR was created by a device that does not trust the master key,
@ -89,47 +87,47 @@ func (vh *VerificationHelper) HandleScannedQRData(ctx context.Context, data []by
if trusted, err := vh.mach.CryptoStore.IsKeySignedBy(ctx, vh.client.UserID, ownCrossSigningPublicKeys.MasterKey, vh.client.UserID, vh.mach.OwnIdentity().SigningKey); err != nil { if trusted, err := vh.mach.CryptoStore.IsKeySignedBy(ctx, vh.client.UserID, ownCrossSigningPublicKeys.MasterKey, vh.client.UserID, vh.mach.OwnIdentity().SigningKey); err != nil {
return err return err
} else if !trusted { } else if !trusted {
return fmt.Errorf("the master key is not trusted by this device") return vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeMasterKeyNotTrusted, "the master key is not trusted by this device, cannot verify device that does not trust the master key")
} }
if vh.client.UserID != txn.TheirUser { 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) return vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUnexpectedMessage, "mode %d is only allowed when the other user is the same as the current user", qrCode.Mode)
} }
// Get their device // Get their device
theirDevice, err := vh.mach.GetOrFetchDevice(ctx, txn.TheirUser, txn.TheirDevice) theirDevice, err := vh.mach.GetOrFetchDevice(ctx, txn.TheirUser, txn.TheirDevice)
if err != nil { if err != nil {
return err return vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeInternalError, "failed to get their device: %w", err)
} }
// Verify that the other device's key is what we expect. // Verify that the other device's key is what we expect.
if bytes.Equal(theirDevice.SigningKey.Bytes(), qrCode.Key1[:]) { if bytes.Equal(theirDevice.SigningKey.Bytes(), qrCode.Key1[:]) {
log.Info().Msg("Verified that the other device key is what we expected") log.Info().Msg("Verified that the other device key is what we expected")
} else { } else {
return fmt.Errorf("the other device's key is not what we expected") return vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeKeyMismatch, "the other device's key is not what we expected")
} }
// Verify that what they think the master key is is correct. // Verify that what they think the master key is is correct.
if bytes.Equal(vh.mach.GetOwnCrossSigningPublicKeys(ctx).MasterKey.Bytes(), qrCode.Key2[:]) { if bytes.Equal(vh.mach.GetOwnCrossSigningPublicKeys(ctx).MasterKey.Bytes(), qrCode.Key2[:]) {
log.Info().Msg("Verified that the other device has the correct master key") log.Info().Msg("Verified that the other device has the correct master key")
} else { } else {
return fmt.Errorf("the master key does not match") return vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeKeyMismatch, "the master key does not match")
} }
// Trust their device // Trust their device
theirDevice.Trust = id.TrustStateVerified theirDevice.Trust = id.TrustStateVerified
err = vh.mach.CryptoStore.PutDevice(ctx, txn.TheirUser, theirDevice) err = vh.mach.CryptoStore.PutDevice(ctx, txn.TheirUser, theirDevice)
if err != nil { if err != nil {
return fmt.Errorf("failed to update device trust state after verifying: %w", err) return vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeInternalError, "failed to update device trust state after verifying: %+v", err)
} }
// Cross-sign their device with the self-signing key // Cross-sign their device with the self-signing key
err = vh.mach.SignOwnDevice(ctx, theirDevice) err = vh.mach.SignOwnDevice(ctx, theirDevice)
if err != nil { if err != nil {
return fmt.Errorf("failed to sign their device: %w", err) return vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeInternalError, "failed to sign their device: %+v", err)
} }
default: default:
return fmt.Errorf("unknown QR code mode %d", qrCode.Mode) return vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUnexpectedMessage, "unknown QR code mode %d", qrCode.Mode)
} }
// Send a m.key.verification.start event with the secret // Send a m.key.verification.start event with the secret
@ -141,17 +139,20 @@ func (vh *VerificationHelper) HandleScannedQRData(ctx context.Context, data []by
} }
err = vh.sendVerificationEvent(ctx, txn, event.InRoomVerificationStart, txn.StartEventContent) err = vh.sendVerificationEvent(ctx, txn, event.InRoomVerificationStart, txn.StartEventContent)
if err != nil { if err != nil {
return err return fmt.Errorf("failed to send m.key.verification.start event: %w", err)
} }
log.Debug().Msg("Successfully sent the m.key.verification.start event")
// Immediately send the m.key.verification.done event, as our side of the // Immediately send the m.key.verification.done event, as our side of the
// transaction is done. // transaction is done.
err = vh.sendVerificationEvent(ctx, txn, event.InRoomVerificationDone, &event.VerificationDoneEventContent{}) err = vh.sendVerificationEvent(ctx, txn, event.InRoomVerificationDone, &event.VerificationDoneEventContent{})
if err != nil { if err != nil {
return err return fmt.Errorf("failed to send m.key.verification.done event: %w", err)
} }
log.Debug().Msg("Successfully sent the m.key.verification.done event")
txn.SentOurDone = true txn.SentOurDone = true
if txn.ReceivedTheirDone { if txn.ReceivedTheirDone {
log.Debug().Msg("We already received their done event. Setting verification state to done.")
txn.VerificationState = verificationStateDone txn.VerificationState = verificationStateDone
vh.verificationDone(ctx, txn.TransactionID) vh.verificationDone(ctx, txn.TransactionID)
} }
@ -225,15 +226,17 @@ func (vh *VerificationHelper) generateAndShowQRCode(ctx context.Context, txn *ve
Stringer("transaction_id", txn.TransactionID). Stringer("transaction_id", txn.TransactionID).
Logger() Logger()
if vh.showQRCode == nil { if vh.showQRCode == nil {
log.Warn().Msg("Ignoring QR code generation request as showing a QR code is not enabled on this device") log.Info().Msg("Ignoring QR code generation request as showing a QR code is not enabled on this device")
return nil return nil
} } else if !slices.Contains(txn.TheirSupportedMethods, event.VerificationMethodQRCodeScan) {
if !slices.Contains(txn.TheirSupportedMethods, event.VerificationMethodQRCodeScan) { log.Info().Msg("Ignoring QR code generation request as other device cannot scan QR codes")
log.Warn().Msg("Ignoring QR code generation request as other device cannot scan QR codes")
return nil return nil
} }
ownCrossSigningPublicKeys := vh.mach.GetOwnCrossSigningPublicKeys(ctx) ownCrossSigningPublicKeys := vh.mach.GetOwnCrossSigningPublicKeys(ctx)
if ownCrossSigningPublicKeys == nil || len(ownCrossSigningPublicKeys.MasterKey) == 0 {
return fmt.Errorf("failed to get own cross-signing master public key")
}
mode := QRCodeModeCrossSigning mode := QRCodeModeCrossSigning
if vh.client.UserID == txn.TheirUser { if vh.client.UserID == txn.TheirUser {
@ -245,6 +248,8 @@ func (vh *VerificationHelper) generateAndShowQRCode(ctx context.Context, txn *ve
} else { } else {
mode = QRCodeModeSelfVerifyingMasterKeyUntrusted mode = QRCodeModeSelfVerifyingMasterKeyUntrusted
} }
} else {
panic("unimplemented")
} }
var key1, key2 []byte var key1, key2 []byte
@ -276,7 +281,7 @@ func (vh *VerificationHelper) generateAndShowQRCode(ctx context.Context, txn *ve
// Key 2 is the master signing key. // Key 2 is the master signing key.
key2 = ownCrossSigningPublicKeys.MasterKey.Bytes() key2 = ownCrossSigningPublicKeys.MasterKey.Bytes()
default: default:
log.Fatal().Str("mode", string(mode)).Msg("Unknown QR code mode") log.Fatal().Int("mode", int(mode)).Msg("Unknown QR code mode")
} }
qrCode := NewQRCode(mode, txn.TransactionID, [32]byte(key1), [32]byte(key2)) qrCode := NewQRCode(mode, txn.TransactionID, [32]byte(key1), [32]byte(key2))

View file

@ -580,7 +580,7 @@ func (vh *VerificationHelper) onVerificationMAC(ctx context.Context, txn *verifi
slices.Sort(keyIDs) slices.Sort(keyIDs)
expectedKeyMAC, err := vh.verificationMACHKDF(txn, txn.TheirUser, txn.TheirDevice, vh.client.UserID, vh.client.DeviceID, "KEY_IDS", strings.Join(keyIDs, ",")) expectedKeyMAC, err := vh.verificationMACHKDF(txn, txn.TheirUser, txn.TheirDevice, vh.client.UserID, vh.client.DeviceID, "KEY_IDS", strings.Join(keyIDs, ","))
if err != nil { if err != nil {
vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeSASMismatch, "failed to calculate key list MAC: %v", err) vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeSASMismatch, "failed to calculate key list MAC: %w", err)
return return
} }
if !bytes.Equal(expectedKeyMAC, macEvt.Keys) { if !bytes.Equal(expectedKeyMAC, macEvt.Keys) {
@ -607,7 +607,7 @@ func (vh *VerificationHelper) onVerificationMAC(ctx context.Context, txn *verifi
if kID == txn.TheirDevice.String() { if kID == txn.TheirDevice.String() {
theirDevice, err = vh.mach.GetOrFetchDevice(ctx, txn.TheirUser, txn.TheirDevice) theirDevice, err = vh.mach.GetOrFetchDevice(ctx, txn.TheirUser, txn.TheirDevice)
if err != nil { if err != nil {
vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUser, "failed to fetch their device: %v", err) vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUser, "failed to fetch their device: %w", err)
return return
} }
key = theirDevice.SigningKey.String() key = theirDevice.SigningKey.String()
@ -626,7 +626,7 @@ func (vh *VerificationHelper) onVerificationMAC(ctx context.Context, txn *verifi
expectedMAC, err := vh.verificationMACHKDF(txn, txn.TheirUser, txn.TheirDevice, vh.client.UserID, vh.client.DeviceID, keyID.String(), key) expectedMAC, err := vh.verificationMACHKDF(txn, txn.TheirUser, txn.TheirDevice, vh.client.UserID, vh.client.DeviceID, keyID.String(), key)
if err != nil { if err != nil {
vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUser, "failed to calculate key MAC: %v", err) vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUser, "failed to calculate key MAC: %w", err)
return return
} }
if !bytes.Equal(expectedMAC, mac) { if !bytes.Equal(expectedMAC, mac) {
@ -639,7 +639,7 @@ func (vh *VerificationHelper) onVerificationMAC(ctx context.Context, txn *verifi
theirDevice.Trust = id.TrustStateVerified theirDevice.Trust = id.TrustStateVerified
err = vh.mach.CryptoStore.PutDevice(ctx, txn.TheirUser, theirDevice) err = vh.mach.CryptoStore.PutDevice(ctx, txn.TheirUser, theirDevice)
if err != nil { if err != nil {
vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUser, "failed to update device trust state after verifying: %v", err) vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUser, "failed to update device trust state after verifying: %w", err)
return return
} }
} }
@ -653,7 +653,7 @@ func (vh *VerificationHelper) onVerificationMAC(ctx context.Context, txn *verifi
txn.VerificationState = verificationStateSASMACExchanged txn.VerificationState = verificationStateSASMACExchanged
err := vh.sendVerificationEvent(ctx, txn, event.InRoomVerificationDone, &event.VerificationDoneEventContent{}) err := vh.sendVerificationEvent(ctx, txn, event.InRoomVerificationDone, &event.VerificationDoneEventContent{})
if err != nil { if err != nil {
vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUser, "failed to send verification done event: %v", err) vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUser, "failed to send verification done event: %w", err)
return return
} }
txn.SentOurDone = true txn.SentOurDone = true

View file

@ -178,31 +178,31 @@ func NewVerificationHelper(client *mautrix.Client, mach *crypto.OlmMachine, call
} }
if c, ok := callbacks.(RequiredCallbacks); !ok { if c, ok := callbacks.(RequiredCallbacks); !ok {
panic("callbacks must implement VerificationRequested") panic("callbacks must implement RequiredCallbacks")
} else { } else {
helper.verificationRequested = c.VerificationRequested helper.verificationRequested = c.VerificationRequested
helper.verificationCancelledCallback = c.VerificationCancelled helper.verificationCancelledCallback = c.VerificationCancelled
helper.verificationDone = c.VerificationDone helper.verificationDone = c.VerificationDone
} }
supportedMethods := map[event.VerificationMethod]struct{}{}
if c, ok := callbacks.(showSASCallbacks); ok { if c, ok := callbacks.(showSASCallbacks); ok {
helper.supportedMethods = append(helper.supportedMethods, event.VerificationMethodSAS) supportedMethods[event.VerificationMethodSAS] = struct{}{}
helper.showSAS = c.ShowSAS helper.showSAS = c.ShowSAS
} }
if c, ok := callbacks.(showQRCodeCallbacks); ok { if c, ok := callbacks.(showQRCodeCallbacks); ok {
helper.supportedMethods = append(helper.supportedMethods, supportedMethods[event.VerificationMethodQRCodeShow] = struct{}{}
event.VerificationMethodQRCodeShow, event.VerificationMethodReciprocate) supportedMethods[event.VerificationMethodReciprocate] = struct{}{}
helper.scanQRCode = c.ScanQRCode helper.scanQRCode = c.ScanQRCode
helper.showQRCode = c.ShowQRCode helper.showQRCode = c.ShowQRCode
helper.qrCodeScaned = c.QRCodeScanned helper.qrCodeScaned = c.QRCodeScanned
} }
if supportsScan { if supportsScan {
helper.supportedMethods = append(helper.supportedMethods, supportedMethods[event.VerificationMethodQRCodeScan] = struct{}{}
event.VerificationMethodQRCodeScan, event.VerificationMethodReciprocate) supportedMethods[event.VerificationMethodReciprocate] = struct{}{}
} }
slices.Sort(helper.supportedMethods) helper.supportedMethods = maps.Keys(supportedMethods)
helper.supportedMethods = slices.Compact(helper.supportedMethods)
return &helper return &helper
} }
@ -332,6 +332,10 @@ func (vh *VerificationHelper) Init(ctx context.Context) error {
// StartVerification starts an interactive verification flow with the given // StartVerification starts an interactive verification flow with the given
// user via a to-device event. // user via a to-device event.
func (vh *VerificationHelper) StartVerification(ctx context.Context, to id.UserID) (id.VerificationTransactionID, error) { func (vh *VerificationHelper) StartVerification(ctx context.Context, to id.UserID) (id.VerificationTransactionID, error) {
if len(vh.supportedMethods) == 0 {
return "", fmt.Errorf("no supported verification methods")
}
txnID := id.NewVerificationTransactionID() txnID := id.NewVerificationTransactionID()
devices, err := vh.mach.CryptoStore.GetDevices(ctx, to) devices, err := vh.mach.CryptoStore.GetDevices(ctx, to)
@ -389,8 +393,8 @@ func (vh *VerificationHelper) StartVerification(ctx context.Context, to id.UserI
return txnID, nil return txnID, nil
} }
// StartVerification starts an interactive verification flow with the given // StartInRoomVerification starts an interactive verification flow with the
// user in the given room. // given user in the given room.
func (vh *VerificationHelper) StartInRoomVerification(ctx context.Context, roomID id.RoomID, to id.UserID) (id.VerificationTransactionID, error) { func (vh *VerificationHelper) StartInRoomVerification(ctx context.Context, roomID id.RoomID, to id.UserID) (id.VerificationTransactionID, error) {
log := vh.getLog(ctx).With(). log := vh.getLog(ctx).With().
Str("verification_action", "start in-room verification"). Str("verification_action", "start in-room verification").
@ -441,15 +445,34 @@ func (vh *VerificationHelper) AcceptVerification(ctx context.Context, txnID id.V
txn, ok := vh.activeTransactions[txnID] txn, ok := vh.activeTransactions[txnID]
if !ok { if !ok {
return fmt.Errorf("unknown transaction ID") return fmt.Errorf("unknown transaction ID")
} } else if txn.VerificationState != verificationStateRequested {
if txn.VerificationState != verificationStateRequested {
return fmt.Errorf("transaction is not in the requested state") return fmt.Errorf("transaction is not in the requested state")
} }
supportedMethods := map[event.VerificationMethod]struct{}{}
for _, method := range txn.TheirSupportedMethods {
switch method {
case event.VerificationMethodSAS:
if slices.Contains(vh.supportedMethods, event.VerificationMethodSAS) {
supportedMethods[event.VerificationMethodSAS] = struct{}{}
}
case event.VerificationMethodQRCodeShow:
if slices.Contains(vh.supportedMethods, event.VerificationMethodQRCodeScan) {
supportedMethods[event.VerificationMethodQRCodeScan] = struct{}{}
supportedMethods[event.VerificationMethodReciprocate] = struct{}{}
}
case event.VerificationMethodQRCodeScan:
if slices.Contains(vh.supportedMethods, event.VerificationMethodQRCodeShow) {
supportedMethods[event.VerificationMethodQRCodeShow] = struct{}{}
supportedMethods[event.VerificationMethodReciprocate] = struct{}{}
}
}
}
log.Info().Msg("Sending ready event") log.Info().Msg("Sending ready event")
readyEvt := &event.VerificationReadyEventContent{ readyEvt := &event.VerificationReadyEventContent{
FromDevice: vh.client.DeviceID, FromDevice: vh.client.DeviceID,
Methods: vh.supportedMethods, Methods: maps.Keys(supportedMethods),
} }
err := vh.sendVerificationEvent(ctx, txn, event.InRoomVerificationReady, readyEvt) err := vh.sendVerificationEvent(ctx, txn, event.InRoomVerificationReady, readyEvt)
if err != nil { if err != nil {
@ -457,7 +480,7 @@ func (vh *VerificationHelper) AcceptVerification(ctx context.Context, txnID id.V
} }
txn.VerificationState = verificationStateReady txn.VerificationState = verificationStateReady
if slices.Contains(txn.TheirSupportedMethods, event.VerificationMethodQRCodeShow) { if vh.scanQRCode != nil && slices.Contains(txn.TheirSupportedMethods, event.VerificationMethodQRCodeShow) {
vh.scanQRCode(ctx, txn.TransactionID) vh.scanQRCode(ctx, txn.TransactionID)
} }
@ -497,7 +520,7 @@ func (vh *VerificationHelper) sendVerificationEvent(ctx context.Context, txn *ve
Parsed: content, Parsed: content,
}) })
if err != nil { if err != nil {
return fmt.Errorf("failed to send start event: %w", err) return fmt.Errorf("failed to send %s event to %s: %w", evtType.String(), txn.RoomID, err)
} }
} else { } else {
content.(event.VerificationTransactionable).SetTransactionID(txn.TransactionID) content.(event.VerificationTransactionable).SetTransactionID(txn.TransactionID)
@ -508,15 +531,19 @@ func (vh *VerificationHelper) sendVerificationEvent(ctx context.Context, txn *ve
}} }}
_, err := vh.client.SendToDevice(ctx, evtType, &req) _, err := vh.client.SendToDevice(ctx, evtType, &req)
if err != nil { if err != nil {
return fmt.Errorf("failed to send start event: %w", err) return fmt.Errorf("failed to send %s event to %s: %w", evtType.String(), txn.TheirDevice, err)
} }
} }
return nil return nil
} }
// cancelVerificationTxn cancels a verification transaction with the given code
// and reason. It always returns an error, which is the formatted error message
// (this is allows the caller to return the result of this function call
// directly to expose the error to its caller).
func (vh *VerificationHelper) cancelVerificationTxn(ctx context.Context, txn *verificationTransaction, code event.VerificationCancelCode, reasonFmtStr string, fmtArgs ...any) error { func (vh *VerificationHelper) cancelVerificationTxn(ctx context.Context, txn *verificationTransaction, code event.VerificationCancelCode, reasonFmtStr string, fmtArgs ...any) error {
log := vh.getLog(ctx) log := vh.getLog(ctx)
reason := fmt.Sprintf(reasonFmtStr, fmtArgs...) reason := fmt.Errorf(reasonFmtStr, fmtArgs...).Error()
log.Info(). log.Info().
Stringer("transaction_id", txn.TransactionID). Stringer("transaction_id", txn.TransactionID).
Str("code", string(code)). Str("code", string(code)).
@ -528,11 +555,11 @@ func (vh *VerificationHelper) cancelVerificationTxn(ctx context.Context, txn *ve
} }
err := vh.sendVerificationEvent(ctx, txn, event.InRoomVerificationCancel, cancelEvt) err := vh.sendVerificationEvent(ctx, txn, event.InRoomVerificationCancel, cancelEvt)
if err != nil { if err != nil {
return err return fmt.Errorf("failed to send cancel verification event (code: %s, reason: %s): %w", code, reason, err)
} }
txn.VerificationState = verificationStateCancelled txn.VerificationState = verificationStateCancelled
vh.verificationCancelledCallback(ctx, txn.TransactionID, code, reason) vh.verificationCancelledCallback(ctx, txn.TransactionID, code, reason)
return nil return fmt.Errorf("verification cancelled (code: %s): %s", code, reason)
} }
func (vh *VerificationHelper) onVerificationRequest(ctx context.Context, evt *event.Event) { func (vh *VerificationHelper) onVerificationRequest(ctx context.Context, evt *event.Event) {
@ -568,7 +595,7 @@ func (vh *VerificationHelper) onVerificationRequest(ctx context.Context, evt *ev
return return
} }
if verificationRequest.TransactionID == "" { if len(verificationRequest.TransactionID) == 0 {
log.Warn().Msg("Ignoring verification request without a transaction ID") log.Warn().Msg("Ignoring verification request without a transaction ID")
return return
} }
@ -581,11 +608,33 @@ func (vh *VerificationHelper) onVerificationRequest(ctx context.Context, evt *ev
ctx = log.WithContext(ctx) ctx = log.WithContext(ctx)
log.Info().Msg("Received verification request") log.Info().Msg("Received verification request")
// Check if we support any of the methods listed
var supportsAnyMethod bool
for _, method := range verificationRequest.Methods {
switch method {
case event.VerificationMethodSAS:
supportsAnyMethod = slices.Contains(vh.supportedMethods, event.VerificationMethodSAS)
case event.VerificationMethodQRCodeScan:
supportsAnyMethod = slices.Contains(vh.supportedMethods, event.VerificationMethodQRCodeShow) &&
slices.Contains(verificationRequest.Methods, event.VerificationMethodReciprocate)
case event.VerificationMethodQRCodeShow:
supportsAnyMethod = slices.Contains(vh.supportedMethods, event.VerificationMethodQRCodeScan) &&
slices.Contains(verificationRequest.Methods, event.VerificationMethodReciprocate)
}
if supportsAnyMethod {
break
}
}
if !supportsAnyMethod {
log.Warn().Msg("Ignoring verification request that doesn't have any methods we support")
return
}
vh.activeTransactionsLock.Lock() vh.activeTransactionsLock.Lock()
_, ok := vh.activeTransactions[verificationRequest.TransactionID] existing, ok := vh.activeTransactions[verificationRequest.TransactionID]
if ok { if ok {
vh.activeTransactionsLock.Unlock() vh.activeTransactionsLock.Unlock()
log.Info().Msg("Ignoring verification request for an already active transaction") vh.cancelVerificationTxn(ctx, existing, event.VerificationCancelCodeUnexpectedMessage, "received a new verification request for the same transaction ID")
return return
} }
vh.activeTransactions[verificationRequest.TransactionID] = &verificationTransaction{ vh.activeTransactions[verificationRequest.TransactionID] = &verificationTransaction{
@ -607,7 +656,7 @@ func (vh *VerificationHelper) onVerificationReady(ctx context.Context, txn *veri
Logger() Logger()
if txn.VerificationState != verificationStateRequested { if txn.VerificationState != verificationStateRequested {
log.Warn().Msg("Ignoring verification ready event for a transaction that is not in the requested state") vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUnexpectedMessage, "verification ready event received for a transaction that is not in the requested state")
return return
} }
@ -633,7 +682,7 @@ func (vh *VerificationHelper) onVerificationReady(ctx context.Context, txn *veri
} }
devices, err := vh.mach.CryptoStore.GetDevices(ctx, txn.TheirUser) devices, err := vh.mach.CryptoStore.GetDevices(ctx, txn.TheirUser)
if err != nil { if err != nil {
vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUser, "failed to get devices for %s: %v", txn.TheirUser, err) vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUser, "failed to get devices for %s: %w", txn.TheirUser, err)
return return
} }
req := mautrix.ReqSendToDevice{Messages: map[id.UserID]map[id.DeviceID]*event.Content{txn.TheirUser: {}}} req := mautrix.ReqSendToDevice{Messages: map[id.UserID]map[id.DeviceID]*event.Content{txn.TheirUser: {}}}
@ -653,13 +702,13 @@ func (vh *VerificationHelper) onVerificationReady(ctx context.Context, txn *veri
} }
} }
if slices.Contains(txn.TheirSupportedMethods, event.VerificationMethodQRCodeShow) { if vh.scanQRCode != nil && slices.Contains(txn.TheirSupportedMethods, event.VerificationMethodQRCodeShow) {
vh.scanQRCode(ctx, txn.TransactionID) vh.scanQRCode(ctx, txn.TransactionID)
} }
err := vh.generateAndShowQRCode(ctx, txn) err := vh.generateAndShowQRCode(ctx, txn)
if err != nil { if err != nil {
vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUser, "failed to generate and show QR code: %v", err) vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUser, "failed to generate and show QR code: %w", err)
} }
} }
@ -680,8 +729,7 @@ func (vh *VerificationHelper) onVerificationStart(ctx context.Context, txn *veri
// We didn't sent a start event yet, so we have gotten ourselves // We didn't sent a start event yet, so we have gotten ourselves
// into a bad state. They've either sent two start events, or we // into a bad state. They've either sent two start events, or we
// have gone on to a new state. // have gone on to a new state.
vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUnexpectedMessage, vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUnexpectedMessage, "got repeat start event from other user")
"got repeat start event from other user")
return return
} }
@ -713,8 +761,7 @@ func (vh *VerificationHelper) onVerificationStart(ctx context.Context, txn *veri
txn.StartEventContent = startEvt txn.StartEventContent = startEvt
} }
} else if txn.VerificationState != verificationStateReady { } else if txn.VerificationState != verificationStateReady {
vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUnexpectedMessage, vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUnexpectedMessage, "got start event for transaction that is not in ready state")
"got start event for transaction that is not in ready state")
return return
} }
@ -722,7 +769,7 @@ func (vh *VerificationHelper) onVerificationStart(ctx context.Context, txn *veri
case event.VerificationMethodSAS: case event.VerificationMethodSAS:
txn.VerificationState = verificationStateSASStarted txn.VerificationState = verificationStateSASStarted
if err := vh.onVerificationStartSAS(ctx, txn, evt); err != nil { if err := vh.onVerificationStartSAS(ctx, txn, evt); err != nil {
vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUser, "failed to handle SAS verification start: %v", err) vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUser, "failed to handle SAS verification start: %w", err)
} }
case event.VerificationMethodReciprocate: case event.VerificationMethodReciprocate:
log.Info().Msg("Received reciprocate start event") log.Info().Msg("Received reciprocate start event")
@ -749,9 +796,10 @@ func (vh *VerificationHelper) onVerificationDone(ctx context.Context, txn *verif
vh.activeTransactionsLock.Lock() vh.activeTransactionsLock.Lock()
defer vh.activeTransactionsLock.Unlock() defer vh.activeTransactionsLock.Unlock()
if txn.VerificationState != verificationStateTheirQRScanned && txn.VerificationState != verificationStateSASMACExchanged { if !slices.Contains([]verificationState{
vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUnexpectedMessage, verificationStateTheirQRScanned, verificationStateOurQRScanned, verificationStateSASMACExchanged,
"got done event for transaction that is not in QR-scanned or MAC-exchanged state") }, txn.VerificationState) {
vh.cancelVerificationTxn(ctx, txn, event.VerificationCancelCodeUnexpectedMessage, "got done event for transaction that is not in QR-scanned or MAC-exchanged state")
return return
} }

View file

@ -220,6 +220,10 @@ const (
VerificationCancelCodeAccepted VerificationCancelCode = "m.accepted" VerificationCancelCodeAccepted VerificationCancelCode = "m.accepted"
VerificationCancelCodeSASMismatch VerificationCancelCode = "m.mismatched_sas" VerificationCancelCodeSASMismatch VerificationCancelCode = "m.mismatched_sas"
VerificationCancelCodeCommitmentMismatch VerificationCancelCode = "m.mismatched_commitment" VerificationCancelCodeCommitmentMismatch VerificationCancelCode = "m.mismatched_commitment"
// Non-spec codes
VerificationCancelCodeInternalError VerificationCancelCode = "com.beeper.internal_error"
VerificationCancelCodeMasterKeyNotTrusted VerificationCancelCode = "com.beeper.master_key_not_trusted" // the master key is not trusted by this device, but the QR code that was scanned was from a device that doesn't trust the master key
) )
// VerificationCancelEventContent represents the content of an // VerificationCancelEventContent represents the content of an