mirror of
https://mau.dev/mautrix/go.git
synced 2026-03-14 14:25:53 +01:00
verificationhelper: better errors/logs and more aggressive cancellations
Signed-off-by: Sumner Evans <sumner.evans@automattic.com>
This commit is contained in:
parent
2e50f99e52
commit
3dbf8ef2f0
4 changed files with 119 additions and 62 deletions
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue