crypto: remove old verification code

Signed-off-by: Sumner Evans <sumner@beeper.com>
This commit is contained in:
Sumner Evans 2024-01-19 13:53:24 -07:00
commit 6bfa468ee7
No known key found for this signature in database
GPG key ID: 8904527AB50022FD
6 changed files with 15 additions and 1385 deletions

View file

@ -15,7 +15,6 @@ import (
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/crypto/olm"
"maunium.net/go/mautrix/crypto/signatures"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
@ -35,31 +34,6 @@ var (
ErrMismatchingMasterKeyMAC = errors.New("mismatching cross-signing master key MAC")
)
func (mach *OlmMachine) fetchMasterKey(ctx context.Context, device *id.Device, content *event.VerificationMacEventContent, verState *verificationState, transactionID string) (id.Ed25519, error) {
crossSignKeys, err := mach.CryptoStore.GetCrossSigningKeys(ctx, device.UserID)
if err != nil {
return "", fmt.Errorf("failed to fetch cross-signing keys: %w", err)
}
masterKey, ok := crossSignKeys[id.XSUsageMaster]
if !ok {
return "", ErrCrossSigningMasterKeyNotFound
}
masterKeyID := id.NewKeyID(id.KeyAlgorithmEd25519, masterKey.Key.String())
masterKeyMAC, ok := content.Mac[masterKeyID]
if !ok {
return masterKey.Key, ErrMasterKeyMACNotFound
}
expectedMasterKeyMAC, _, err := mach.getPKAndKeysMAC(verState.sas, device.UserID, device.DeviceID,
mach.Client.UserID, mach.Client.DeviceID, transactionID, masterKey.Key, masterKeyID, content.Mac)
if err != nil {
return masterKey.Key, fmt.Errorf("failed to calculate expected MAC for master key: %w", err)
}
if masterKeyMAC != expectedMasterKeyMAC {
err = fmt.Errorf("%w: expected %s, got %s", ErrMismatchingMasterKeyMAC, expectedMasterKeyMAC, masterKeyMAC)
}
return masterKey.Key, err
}
// SignUser creates a cross-signing signature for a user, stores it and uploads it to the server.
func (mach *OlmMachine) SignUser(ctx context.Context, userID id.UserID, masterKey id.Ed25519) error {
if userID == mach.Client.UserID {

View file

@ -222,6 +222,21 @@ func (mach *OlmMachine) rejectKeyRequest(ctx context.Context, rejection KeyShare
}
}
// sendToOneDevice sends a to-device event to a single device.
func (mach *OlmMachine) sendToOneDevice(ctx context.Context, userID id.UserID, deviceID id.DeviceID, eventType event.Type, content interface{}) error {
_, err := mach.Client.SendToDevice(ctx, eventType, &mautrix.ReqSendToDevice{
Messages: map[id.UserID]map[id.DeviceID]*event.Content{
userID: {
deviceID: {
Parsed: content,
},
},
},
})
return err
}
func (mach *OlmMachine) defaultAllowKeyShare(ctx context.Context, device *id.Device, evt event.RequestedKeyInfo) *KeyShareRejection {
log := mach.machOrContextLog(ctx)
if mach.Client.UserID != device.UserID {

View file

@ -44,10 +44,6 @@ type OlmMachine struct {
AllowKeyShare func(context.Context, *id.Device, event.RequestedKeyInfo) *KeyShareRejection
DefaultSASTimeout time.Duration
// AcceptVerificationFrom determines whether the machine will accept verification requests from this device.
AcceptVerificationFrom func(string, *id.Device, id.RoomID) (VerificationRequestResponse, VerificationHooks)
account *OlmAccount
roomKeyRequestFilled *sync.Map
@ -112,12 +108,6 @@ func NewOlmMachine(client *mautrix.Client, log *zerolog.Logger, cryptoStore Stor
SendKeysMinTrust: id.TrustStateUnset,
ShareKeysMinTrust: id.TrustStateCrossSignedTOFU,
DefaultSASTimeout: 10 * time.Minute,
AcceptVerificationFrom: func(string, *id.Device, id.RoomID) (VerificationRequestResponse, VerificationHooks) {
// Reject requests by default. Users need to override this to return appropriate verification hooks.
return RejectRequest, nil
},
roomKeyRequestFilled: &sync.Map{},
keyVerificationTransactionState: &sync.Map{},
@ -412,19 +402,6 @@ func (mach *OlmMachine) HandleToDeviceEvent(ctx context.Context, evt *event.Even
go mach.HandleRoomKeyRequest(ctx, evt.Sender, content)
case *event.BeeperRoomKeyAckEventContent:
mach.HandleBeeperRoomKeyAck(ctx, evt.Sender, content)
// verification cases
case *event.VerificationStartEventContent:
mach.handleVerificationStart(ctx, evt.Sender, content, content.TransactionID, 10*time.Minute, "")
case *event.VerificationAcceptEventContent:
mach.handleVerificationAccept(ctx, evt.Sender, content, content.TransactionID)
case *event.VerificationKeyEventContent:
mach.handleVerificationKey(ctx, evt.Sender, content, content.TransactionID)
case *event.VerificationMacEventContent:
mach.handleVerificationMAC(ctx, evt.Sender, content, content.TransactionID)
case *event.VerificationCancelEventContent:
mach.handleVerificationCancel(evt.Sender, content, content.TransactionID)
case *event.VerificationRequestEventContent:
mach.handleVerificationRequest(ctx, evt.Sender, content, content.TransactionID, "")
case *event.RoomKeyWithheldEventContent:
mach.HandleRoomKeyWithheld(ctx, content)
case *event.SecretRequestEventContent:

View file

@ -1,801 +0,0 @@
// Copyright (c) 2020 Nikos Filippakis
//
// 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"
"encoding/json"
"errors"
"fmt"
"math/rand"
"sort"
"strconv"
"strings"
"sync"
"time"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/crypto/canonicaljson"
"maunium.net/go/mautrix/crypto/olm"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
var (
ErrUnknownUserForTransaction = errors.New("unknown user for transaction")
ErrTransactionAlreadyExists = errors.New("transaction already exists")
// ErrUnknownTransaction is returned when a key verification message is received with an unknown transaction ID.
ErrUnknownTransaction = errors.New("unknown transaction")
// ErrUnknownVerificationMethod is returned when the verification method in a received m.key.verification.start is unknown.
ErrUnknownVerificationMethod = errors.New("unknown verification method")
)
type VerificationHooks interface {
// VerifySASMatch receives the generated SAS and its method, as well as the device that is being verified.
// It returns whether the given SAS match with the SAS displayed on other device.
VerifySASMatch(otherDevice *id.Device, sas SASData) bool
// VerificationMethods returns the list of supported verification methods in order of preference.
// It must contain at least the decimal method.
VerificationMethods() []VerificationMethod
OnCancel(cancelledByUs bool, reason string, reasonCode event.VerificationCancelCode)
OnSuccess()
}
type VerificationRequestResponse int
const (
AcceptRequest VerificationRequestResponse = iota
RejectRequest
IgnoreRequest
)
// sendToOneDevice sends a to-device event to a single device.
func (mach *OlmMachine) sendToOneDevice(ctx context.Context, userID id.UserID, deviceID id.DeviceID, eventType event.Type, content interface{}) error {
_, err := mach.Client.SendToDevice(ctx, eventType, &mautrix.ReqSendToDevice{
Messages: map[id.UserID]map[id.DeviceID]*event.Content{
userID: {
deviceID: {
Parsed: content,
},
},
},
})
return err
}
func (mach *OlmMachine) getPKAndKeysMAC(sas *olm.SAS, sendingUser id.UserID, sendingDevice id.DeviceID, receivingUser id.UserID, receivingDevice id.DeviceID,
transactionID string, signingKey id.SigningKey, mainKeyID id.KeyID, keys map[id.KeyID]string) (string, string, error) {
sasInfo := "MATRIX_KEY_VERIFICATION_MAC" +
sendingUser.String() + sendingDevice.String() +
receivingUser.String() + receivingDevice.String() +
transactionID
// get key IDs from key map
keyIDStrings := make([]string, len(keys))
i := 0
for keyID := range keys {
keyIDStrings[i] = keyID.String()
i++
}
sort.Sort(sort.StringSlice(keyIDStrings))
keyIDString := strings.Join(keyIDStrings, ",")
pubKeyMac, err := sas.CalculateMAC([]byte(signingKey), []byte(sasInfo+mainKeyID.String()))
if err != nil {
return "", "", err
}
mach.Log.Trace().Msgf("sas.CalculateMAC(\"%s\", \"%s\") -> \"%s\"", signingKey, sasInfo+mainKeyID.String(), string(pubKeyMac))
keysMac, err := sas.CalculateMAC([]byte(keyIDString), []byte(sasInfo+"KEY_IDS"))
if err != nil {
return "", "", err
}
mach.Log.Trace().Msgf("sas.CalculateMAC(\"%s\", \"%s\") -> \"%s\"", keyIDString, sasInfo+"KEY_IDS", string(keysMac))
return string(pubKeyMac), string(keysMac), nil
}
// verificationState holds all the information needed for the state of a SAS verification with another device.
type verificationState struct {
sas *olm.SAS
otherDevice *id.Device
initiatedByUs bool
verificationStarted bool
keyReceived bool
sasMatched chan bool
commitment string
startEventCanonical string
chosenSASMethod VerificationMethod
hooks VerificationHooks
extendTimeout context.CancelFunc
inRoomID id.RoomID
lock sync.Mutex
}
// getTransactionState retrieves the given transaction's state, or cancels the transaction if it cannot be found or there is a mismatch.
func (mach *OlmMachine) getTransactionState(ctx context.Context, transactionID string, userID id.UserID) (*verificationState, error) {
verStateInterface, ok := mach.keyVerificationTransactionState.Load(userID.String() + ":" + transactionID)
if !ok {
_ = mach.SendSASVerificationCancel(ctx, userID, id.DeviceID("*"), transactionID, "Unknown transaction: "+transactionID, event.VerificationCancelUnknownTransaction)
return nil, ErrUnknownTransaction
}
verState := verStateInterface.(*verificationState)
if verState.otherDevice.UserID != userID {
reason := fmt.Sprintf("Unknown user for transaction %v: %v", transactionID, userID)
if verState.inRoomID == "" {
_ = mach.SendSASVerificationCancel(ctx, userID, id.DeviceID("*"), transactionID, reason, event.VerificationCancelUserMismatch)
} else {
_ = mach.SendInRoomSASVerificationCancel(ctx, verState.inRoomID, userID, transactionID, reason, event.VerificationCancelUserMismatch)
}
mach.keyVerificationTransactionState.Delete(userID.String() + ":" + transactionID)
return nil, fmt.Errorf("%w %s: %s", ErrUnknownUserForTransaction, transactionID, userID)
}
return verState, nil
}
// handleVerificationStart handles an incoming m.key.verification.start message.
// It initializes the state for this SAS verification process and stores it.
func (mach *OlmMachine) handleVerificationStart(ctx context.Context, userID id.UserID, content *event.VerificationStartEventContent, transactionID string, timeout time.Duration, inRoomID id.RoomID) {
mach.Log.Debug().Msgf("Received verification start from %v", content.FromDevice)
otherDevice, err := mach.GetOrFetchDevice(ctx, userID, content.FromDevice)
if err != nil {
mach.Log.Error().Msgf("Could not find device %v of user %v", content.FromDevice, userID)
return
}
warnAndCancel := func(logReason, cancelReason string) {
mach.Log.Warn().Msgf("Canceling verification transaction %v as it %s", transactionID, logReason)
if inRoomID == "" {
_ = mach.SendSASVerificationCancel(ctx, otherDevice.UserID, otherDevice.DeviceID, transactionID, cancelReason, event.VerificationCancelUnknownMethod)
} else {
_ = mach.SendInRoomSASVerificationCancel(ctx, inRoomID, otherDevice.UserID, transactionID, cancelReason, event.VerificationCancelUnknownMethod)
}
}
switch {
case content.Method != event.VerificationMethodSAS:
warnAndCancel("is not SAS", "Only SAS method is supported")
case !content.SupportsKeyAgreementProtocol(event.KeyAgreementCurve25519HKDFSHA256):
warnAndCancel("does not support key agreement protocol curve25519-hkdf-sha256",
"Only curve25519-hkdf-sha256 key agreement protocol is supported")
case !content.SupportsHashMethod(event.VerificationHashSHA256):
warnAndCancel("does not support SHA256 hashing", "Only SHA256 hashing is supported")
case !content.SupportsMACMethod(event.HKDFHMACSHA256):
warnAndCancel("does not support MAC method hkdf-hmac-sha256", "Only hkdf-hmac-sha256 MAC method is supported")
case !content.SupportsSASMethod(event.SASDecimal):
warnAndCancel("does not support decimal SAS", "Decimal SAS method must be supported")
default:
mach.actuallyStartVerification(ctx, userID, content, otherDevice, transactionID, timeout, inRoomID)
}
}
func (mach *OlmMachine) actuallyStartVerification(ctx context.Context, userID id.UserID, content *event.VerificationStartEventContent, otherDevice *id.Device, transactionID string, timeout time.Duration, inRoomID id.RoomID) {
if inRoomID != "" && transactionID != "" {
verState, err := mach.getTransactionState(ctx, transactionID, userID)
if err != nil {
mach.Log.Error().Msgf("Failed to get transaction state for in-room verification %s start: %v", transactionID, err)
_ = mach.SendInRoomSASVerificationCancel(ctx, inRoomID, otherDevice.UserID, transactionID, "Internal state error in gomuks :(", "net.maunium.internal_error")
return
}
mach.timeoutAfter(ctx, verState, transactionID, timeout)
sasMethods := commonSASMethods(verState.hooks, content.ShortAuthenticationString)
err = mach.SendInRoomSASVerificationAccept(ctx, inRoomID, userID, content, transactionID, verState.sas.GetPubkey(), sasMethods)
if err != nil {
mach.Log.Error().Msgf("Error accepting in-room SAS verification: %v", err)
}
verState.chosenSASMethod = sasMethods[0]
verState.verificationStarted = true
return
}
resp, hooks := mach.AcceptVerificationFrom(transactionID, otherDevice, inRoomID)
if resp == AcceptRequest {
sasMethods := commonSASMethods(hooks, content.ShortAuthenticationString)
if len(sasMethods) == 0 {
mach.Log.Error().Msgf("No common SAS methods: %v", content.ShortAuthenticationString)
if inRoomID == "" {
_ = mach.SendSASVerificationCancel(ctx, otherDevice.UserID, otherDevice.DeviceID, transactionID, "No common SAS methods", event.VerificationCancelUnknownMethod)
} else {
_ = mach.SendInRoomSASVerificationCancel(ctx, inRoomID, otherDevice.UserID, transactionID, "No common SAS methods", event.VerificationCancelUnknownMethod)
}
return
}
verState := &verificationState{
sas: olm.NewSAS(),
otherDevice: otherDevice,
initiatedByUs: false,
verificationStarted: true,
keyReceived: false,
sasMatched: make(chan bool, 1),
hooks: hooks,
chosenSASMethod: sasMethods[0],
inRoomID: inRoomID,
}
verState.lock.Lock()
defer verState.lock.Unlock()
_, loaded := mach.keyVerificationTransactionState.LoadOrStore(userID.String()+":"+transactionID, verState)
if loaded {
// transaction already exists
mach.Log.Error().Msgf("Transaction %v already exists, canceling", transactionID)
if inRoomID == "" {
_ = mach.SendSASVerificationCancel(ctx, otherDevice.UserID, otherDevice.DeviceID, transactionID, "Transaction already exists", event.VerificationCancelUnexpectedMessage)
} else {
_ = mach.SendInRoomSASVerificationCancel(ctx, inRoomID, otherDevice.UserID, transactionID, "Transaction already exists", event.VerificationCancelUnexpectedMessage)
}
return
}
mach.timeoutAfter(ctx, verState, transactionID, timeout)
var err error
if inRoomID == "" {
err = mach.SendSASVerificationAccept(ctx, userID, content, verState.sas.GetPubkey(), sasMethods)
} else {
err = mach.SendInRoomSASVerificationAccept(ctx, inRoomID, userID, content, transactionID, verState.sas.GetPubkey(), sasMethods)
}
if err != nil {
mach.Log.Error().Msgf("Error accepting SAS verification: %v", err)
}
} else if resp == RejectRequest {
mach.Log.Debug().Msgf("Not accepting SAS verification %v from %v of user %v", transactionID, otherDevice.DeviceID, otherDevice.UserID)
var err error
if inRoomID == "" {
err = mach.SendSASVerificationCancel(ctx, otherDevice.UserID, otherDevice.DeviceID, transactionID, "Not accepted by user", event.VerificationCancelByUser)
} else {
err = mach.SendInRoomSASVerificationCancel(ctx, inRoomID, otherDevice.UserID, transactionID, "Not accepted by user", event.VerificationCancelByUser)
}
if err != nil {
mach.Log.Error().Msgf("Error canceling SAS verification: %v", err)
}
} else {
mach.Log.Debug().Msgf("Ignoring SAS verification %v from %v of user %v", transactionID, otherDevice.DeviceID, otherDevice.UserID)
}
}
func (mach *OlmMachine) timeoutAfter(ctx context.Context, verState *verificationState, transactionID string, timeout time.Duration) {
timeoutCtx, timeoutCancel := context.WithTimeout(ctx, timeout)
verState.extendTimeout = timeoutCancel
go func() {
mapKey := verState.otherDevice.UserID.String() + ":" + transactionID
for {
<-timeoutCtx.Done()
// when timeout context is done
verState.lock.Lock()
// if transaction not active anymore, return
if _, ok := mach.keyVerificationTransactionState.Load(mapKey); !ok {
verState.lock.Unlock()
return
}
if timeoutCtx.Err() == context.DeadlineExceeded {
// if deadline exceeded cancel due to timeout
mach.keyVerificationTransactionState.Delete(mapKey)
_ = mach.callbackAndCancelSASVerification(ctx, verState, transactionID, "Timed out", event.VerificationCancelByTimeout)
mach.Log.Warn().Msgf("Verification transaction %v is canceled due to timing out", transactionID)
verState.lock.Unlock()
return
}
// otherwise the cancel func was called, so the timeout is reset
mach.Log.Debug().Msgf("Extending timeout for transaction %v", transactionID)
timeoutCtx, timeoutCancel = context.WithTimeout(context.Background(), timeout)
verState.extendTimeout = timeoutCancel
verState.lock.Unlock()
}
}()
}
// handleVerificationAccept handles an incoming m.key.verification.accept message.
// It continues the SAS verification process by sending the SAS key message to the other device.
func (mach *OlmMachine) handleVerificationAccept(ctx context.Context, userID id.UserID, content *event.VerificationAcceptEventContent, transactionID string) {
mach.Log.Debug().Msgf("Received verification accept for transaction %v", transactionID)
verState, err := mach.getTransactionState(ctx, transactionID, userID)
if err != nil {
mach.Log.Error().Msgf("Error getting transaction state: %v", err)
return
}
verState.lock.Lock()
defer verState.lock.Unlock()
verState.extendTimeout()
if !verState.initiatedByUs || verState.verificationStarted {
// unexpected accept at this point
mach.Log.Warn().Msgf("Unexpected verification accept message for transaction %v", transactionID)
mach.keyVerificationTransactionState.Delete(userID.String() + ":" + transactionID)
_ = mach.callbackAndCancelSASVerification(ctx, verState, transactionID, "Unexpected accept message", event.VerificationCancelUnexpectedMessage)
return
}
sasMethods := commonSASMethods(verState.hooks, content.ShortAuthenticationString)
if content.KeyAgreementProtocol != event.KeyAgreementCurve25519HKDFSHA256 ||
content.Hash != event.VerificationHashSHA256 ||
content.MessageAuthenticationCode != event.HKDFHMACSHA256 ||
len(sasMethods) == 0 {
mach.Log.Warn().Msgf("Canceling verification transaction %v due to unknown parameter", transactionID)
mach.keyVerificationTransactionState.Delete(userID.String() + ":" + transactionID)
_ = mach.callbackAndCancelSASVerification(ctx, verState, transactionID, "Verification uses unknown method", event.VerificationCancelUnknownMethod)
return
}
key := verState.sas.GetPubkey()
verState.commitment = content.Commitment
verState.chosenSASMethod = sasMethods[0]
verState.verificationStarted = true
if verState.inRoomID == "" {
err = mach.SendSASVerificationKey(ctx, userID, verState.otherDevice.DeviceID, transactionID, string(key))
} else {
err = mach.SendInRoomSASVerificationKey(ctx, verState.inRoomID, userID, transactionID, string(key))
}
if err != nil {
mach.Log.Error().Msgf("Error sending SAS key to other device: %v", err)
return
}
}
// handleVerificationKey handles an incoming m.key.verification.key message.
// It stores the other device's public key in order to acquire the SAS shared secret.
func (mach *OlmMachine) handleVerificationKey(ctx context.Context, userID id.UserID, content *event.VerificationKeyEventContent, transactionID string) {
mach.Log.Debug().Msgf("Got verification key for transaction %v: %v", transactionID, content.Key)
verState, err := mach.getTransactionState(ctx, transactionID, userID)
if err != nil {
mach.Log.Error().Msgf("Error getting transaction state: %v", err)
return
}
verState.lock.Lock()
defer verState.lock.Unlock()
verState.extendTimeout()
device := verState.otherDevice
if !verState.verificationStarted || verState.keyReceived {
// unexpected key at this point
mach.Log.Warn().Msgf("Unexpected verification key message for transaction %v", transactionID)
mach.keyVerificationTransactionState.Delete(userID.String() + ":" + transactionID)
_ = mach.callbackAndCancelSASVerification(ctx, verState, transactionID, "Unexpected key message", event.VerificationCancelUnexpectedMessage)
return
}
if err := verState.sas.SetTheirKey([]byte(content.Key)); err != nil {
mach.Log.Error().Msgf("Error setting other device's key: %v", err)
return
}
verState.keyReceived = true
if verState.initiatedByUs {
// verify commitment string from accept message now
expectedCommitment := olm.SHA256B64([]byte(content.Key + verState.startEventCanonical))
mach.Log.Debug().Msgf("Received commitment: %v Expected: %v", verState.commitment, expectedCommitment)
if expectedCommitment != verState.commitment {
mach.Log.Warn().Msgf("Canceling verification transaction %v due to commitment mismatch", transactionID)
mach.keyVerificationTransactionState.Delete(userID.String() + ":" + transactionID)
_ = mach.callbackAndCancelSASVerification(ctx, verState, transactionID, "Commitment mismatch", event.VerificationCancelCommitmentMismatch)
return
}
} else {
// if verification was initiated by other device, send out our key now
key := verState.sas.GetPubkey()
if verState.inRoomID == "" {
err = mach.SendSASVerificationKey(ctx, userID, device.DeviceID, transactionID, string(key))
} else {
err = mach.SendInRoomSASVerificationKey(ctx, verState.inRoomID, userID, transactionID, string(key))
}
if err != nil {
mach.Log.Error().Msgf("Error sending SAS key to other device: %v", err)
return
}
}
// compare the SAS keys in a new goroutine and, when the verification is complete, send out the MAC
var initUserID, acceptUserID id.UserID
var initDeviceID, acceptDeviceID id.DeviceID
var initKey, acceptKey string
if verState.initiatedByUs {
initUserID = mach.Client.UserID
initDeviceID = mach.Client.DeviceID
initKey = string(verState.sas.GetPubkey())
acceptUserID = device.UserID
acceptDeviceID = device.DeviceID
acceptKey = content.Key
} else {
initUserID = device.UserID
initDeviceID = device.DeviceID
initKey = content.Key
acceptUserID = mach.Client.UserID
acceptDeviceID = mach.Client.DeviceID
acceptKey = string(verState.sas.GetPubkey())
}
// use the prefered SAS method to generate a SAS
sasMethod := verState.chosenSASMethod
sas, err := sasMethod.GetVerificationSAS(initUserID, initDeviceID, initKey, acceptUserID, acceptDeviceID, acceptKey, transactionID, verState.sas)
if err != nil {
mach.Log.Error().Msgf("Error generating SAS (method %v): %v", sasMethod.Type(), err)
return
}
mach.Log.Debug().Msgf("Generated SAS (%v): %v", sasMethod.Type(), sas)
go func() {
result := verState.hooks.VerifySASMatch(device, sas)
mach.sasCompared(ctx, result, transactionID, verState)
}()
}
// sasCompared is called asynchronously. It waits for the SAS to be compared for the verification to proceed.
// If the SAS match, then our MAC is sent out. Otherwise the transaction is canceled.
func (mach *OlmMachine) sasCompared(ctx context.Context, didMatch bool, transactionID string, verState *verificationState) {
verState.lock.Lock()
defer verState.lock.Unlock()
verState.extendTimeout()
if didMatch {
verState.sasMatched <- true
var err error
if verState.inRoomID == "" {
err = mach.SendSASVerificationMAC(ctx, verState.otherDevice.UserID, verState.otherDevice.DeviceID, transactionID, verState.sas)
} else {
err = mach.SendInRoomSASVerificationMAC(ctx, verState.inRoomID, verState.otherDevice.UserID, verState.otherDevice.DeviceID, transactionID, verState.sas)
}
if err != nil {
mach.Log.Error().Msgf("Error sending verification MAC to other device: %v", err)
}
} else {
verState.sasMatched <- false
}
}
// handleVerificationMAC handles an incoming m.key.verification.mac message.
// It verifies the other device's MAC and if the MAC is valid it marks the device as trusted.
func (mach *OlmMachine) handleVerificationMAC(ctx context.Context, userID id.UserID, content *event.VerificationMacEventContent, transactionID string) {
mach.Log.Debug().Msgf("Got MAC for verification %v: %v, MAC for keys: %v", transactionID, content.Mac, content.Keys)
verState, err := mach.getTransactionState(ctx, transactionID, userID)
if err != nil {
mach.Log.Error().Msgf("Error getting transaction state: %v", err)
return
}
verState.lock.Lock()
defer verState.lock.Unlock()
verState.extendTimeout()
device := verState.otherDevice
// we are done with this SAS verification in all cases so we forget about it
mach.keyVerificationTransactionState.Delete(userID.String() + ":" + transactionID)
if !verState.verificationStarted || !verState.keyReceived {
// unexpected MAC at this point
mach.Log.Warn().Msgf("Unexpected MAC message for transaction %v", transactionID)
_ = mach.callbackAndCancelSASVerification(ctx, verState, transactionID, "Unexpected MAC message", event.VerificationCancelUnexpectedMessage)
return
}
// do this in another goroutine as the match result might take a long time to arrive
go func() {
matched := <-verState.sasMatched
verState.lock.Lock()
defer verState.lock.Unlock()
if !matched {
mach.Log.Warn().Msgf("SAS do not match! Canceling transaction %v", transactionID)
_ = mach.callbackAndCancelSASVerification(ctx, verState, transactionID, "SAS do not match", event.VerificationCancelSASMismatch)
return
}
keyID := id.NewKeyID(id.KeyAlgorithmEd25519, device.DeviceID.String())
expectedPKMAC, expectedKeysMAC, err := mach.getPKAndKeysMAC(verState.sas, device.UserID, device.DeviceID,
mach.Client.UserID, mach.Client.DeviceID, transactionID, device.SigningKey, keyID, content.Mac)
if err != nil {
mach.Log.Error().Msgf("Error generating MAC to match with received MAC: %v", err)
return
}
mach.Log.Debug().Msgf("Expected %s keys MAC, got %s", expectedKeysMAC, content.Keys)
if content.Keys != expectedKeysMAC {
mach.Log.Warn().Msgf("Canceling verification transaction %v due to mismatched keys MAC", transactionID)
_ = mach.callbackAndCancelSASVerification(ctx, verState, transactionID, "Mismatched keys MACs", event.VerificationCancelKeyMismatch)
return
}
mach.Log.Debug().Msgf("Expected %s PK MAC, got %s", expectedPKMAC, content.Mac[keyID])
if content.Mac[keyID] != expectedPKMAC {
mach.Log.Warn().Msgf("Canceling verification transaction %v due to mismatched PK MAC", transactionID)
_ = mach.callbackAndCancelSASVerification(ctx, verState, transactionID, "Mismatched PK MACs", event.VerificationCancelKeyMismatch)
return
}
// we can finally trust this device
device.Trust = id.TrustStateVerified
err = mach.CryptoStore.PutDevice(ctx, device.UserID, device)
if err != nil {
mach.Log.Warn().Msgf("Failed to put device after verifying: %v", err)
}
if mach.CrossSigningKeys != nil {
if device.UserID == mach.Client.UserID {
err := mach.SignOwnDevice(ctx, device)
if err != nil {
mach.Log.Error().Msgf("Failed to cross-sign own device %s: %v", device.DeviceID, err)
} else {
mach.Log.Debug().Msgf("Cross-signed own device %v after SAS verification", device.DeviceID)
}
} else {
masterKey, err := mach.fetchMasterKey(ctx, device, content, verState, transactionID)
if err != nil {
mach.Log.Warn().Msgf("Failed to fetch %s's master key: %v", device.UserID, err)
} else {
if err := mach.SignUser(ctx, device.UserID, masterKey); err != nil {
mach.Log.Error().Msgf("Failed to cross-sign master key of %s: %v", device.UserID, err)
} else {
mach.Log.Debug().Msgf("Cross-signed master key of %v after SAS verification", device.UserID)
}
}
}
} else {
// TODO ask user to unlock cross-signing keys?
mach.Log.Debug().Msgf("Cross-signing keys not cached, not signing %s/%s", device.UserID, device.DeviceID)
}
mach.Log.Debug().Msgf("Device %v of user %v verified successfully!", device.DeviceID, device.UserID)
verState.hooks.OnSuccess()
}()
}
// handleVerificationCancel handles an incoming m.key.verification.cancel message.
// It cancels the verification process for the given reason.
func (mach *OlmMachine) handleVerificationCancel(userID id.UserID, content *event.VerificationCancelEventContent, transactionID string) {
// make sure to not reply with a cancel to not cause a loop of cancel messages
// this verification will get canceled even if the senders do not match
verStateInterface, ok := mach.keyVerificationTransactionState.Load(userID.String() + ":" + transactionID)
if ok {
go verStateInterface.(*verificationState).hooks.OnCancel(false, content.Reason, content.Code)
}
mach.keyVerificationTransactionState.Delete(userID.String() + ":" + transactionID)
mach.Log.Warn().Msgf("SAS verification %v was canceled by %v with reason: %v (%v)",
transactionID, userID, content.Reason, content.Code)
}
// handleVerificationRequest handles an incoming m.key.verification.request message.
func (mach *OlmMachine) handleVerificationRequest(ctx context.Context, userID id.UserID, content *event.VerificationRequestEventContent, transactionID string, inRoomID id.RoomID) {
mach.Log.Debug().Msgf("Received verification request from %v", content.FromDevice)
otherDevice, err := mach.GetOrFetchDevice(ctx, userID, content.FromDevice)
if err != nil {
mach.Log.Error().Msgf("Could not find device %v of user %v", content.FromDevice, userID)
return
}
if !content.SupportsVerificationMethod(event.VerificationMethodSAS) {
mach.Log.Warn().Msgf("Canceling verification transaction %v as SAS is not supported", transactionID)
if inRoomID == "" {
_ = mach.SendSASVerificationCancel(ctx, otherDevice.UserID, otherDevice.DeviceID, transactionID, "Only SAS method is supported", event.VerificationCancelUnknownMethod)
} else {
_ = mach.SendInRoomSASVerificationCancel(ctx, inRoomID, otherDevice.UserID, transactionID, "Only SAS method is supported", event.VerificationCancelUnknownMethod)
}
return
}
resp, hooks := mach.AcceptVerificationFrom(transactionID, otherDevice, inRoomID)
if resp == AcceptRequest {
mach.Log.Debug().Msgf("Accepting SAS verification %v from %v of user %v", transactionID, otherDevice.DeviceID, otherDevice.UserID)
if inRoomID == "" {
_, err = mach.NewSASVerificationWith(ctx, otherDevice, hooks, transactionID, mach.DefaultSASTimeout)
} else {
if err := mach.SendInRoomSASVerificationReady(ctx, inRoomID, transactionID); err != nil {
mach.Log.Error().Msgf("Error sending in-room SAS verification ready: %v", err)
}
if mach.Client.UserID < otherDevice.UserID {
// up to us to send the start message
_, err = mach.newInRoomSASVerificationWithInner(ctx, inRoomID, otherDevice, hooks, transactionID, mach.DefaultSASTimeout)
}
}
if err != nil {
mach.Log.Error().Msgf("Error accepting SAS verification request: %v", err)
}
} else if resp == RejectRequest {
mach.Log.Debug().Msgf("Rejecting SAS verification %v from %v of user %v", transactionID, otherDevice.DeviceID, otherDevice.UserID)
if inRoomID == "" {
_ = mach.SendSASVerificationCancel(ctx, otherDevice.UserID, otherDevice.DeviceID, transactionID, "Not accepted by user", event.VerificationCancelByUser)
} else {
_ = mach.SendInRoomSASVerificationCancel(ctx, inRoomID, otherDevice.UserID, transactionID, "Not accepted by user", event.VerificationCancelByUser)
}
} else {
mach.Log.Debug().Msgf("Ignoring SAS verification %v from %v of user %v", transactionID, otherDevice.DeviceID, otherDevice.UserID)
}
}
// NewSimpleSASVerificationWith starts the SAS verification process with another device with a default timeout,
// a generated transaction ID and support for both emoji and decimal SAS methods.
func (mach *OlmMachine) NewSimpleSASVerificationWith(ctx context.Context, device *id.Device, hooks VerificationHooks) (string, error) {
return mach.NewSASVerificationWith(ctx, device, hooks, "", mach.DefaultSASTimeout)
}
// NewSASVerificationWith starts the SAS verification process with another device.
// If the other device accepts the verification transaction, the methods in `hooks` will be used to verify the SAS match and to complete the transaction..
// If the transaction ID is empty, a new one is generated.
func (mach *OlmMachine) NewSASVerificationWith(ctx context.Context, device *id.Device, hooks VerificationHooks, transactionID string, timeout time.Duration) (string, error) {
if transactionID == "" {
transactionID = strconv.Itoa(rand.Int())
}
mach.Log.Debug().Msgf("Starting new verification transaction %v with device %v of user %v", transactionID, device.DeviceID, device.UserID)
verState := &verificationState{
sas: olm.NewSAS(),
otherDevice: device,
initiatedByUs: true,
verificationStarted: false,
keyReceived: false,
sasMatched: make(chan bool, 1),
hooks: hooks,
}
verState.lock.Lock()
defer verState.lock.Unlock()
startEvent, err := mach.SendSASVerificationStart(ctx, device.UserID, device.DeviceID, transactionID, hooks.VerificationMethods())
if err != nil {
return "", err
}
payload, err := json.Marshal(startEvent)
if err != nil {
return "", err
}
canonical, err := canonicaljson.CanonicalJSON(payload)
if err != nil {
return "", err
}
verState.startEventCanonical = string(canonical)
_, loaded := mach.keyVerificationTransactionState.LoadOrStore(device.UserID.String()+":"+transactionID, verState)
if loaded {
return "", ErrTransactionAlreadyExists
}
mach.timeoutAfter(ctx, verState, transactionID, timeout)
return transactionID, nil
}
// CancelSASVerification is used by the user to cancel a SAS verification process with the given reason.
func (mach *OlmMachine) CancelSASVerification(ctx context.Context, userID id.UserID, transactionID, reason string) error {
mapKey := userID.String() + ":" + transactionID
verStateInterface, ok := mach.keyVerificationTransactionState.Load(mapKey)
if !ok {
return ErrUnknownTransaction
}
verState := verStateInterface.(*verificationState)
verState.lock.Lock()
defer verState.lock.Unlock()
mach.Log.Trace().Msgf("User canceled verification transaction %v with reason: %v", transactionID, reason)
mach.keyVerificationTransactionState.Delete(mapKey)
return mach.callbackAndCancelSASVerification(ctx, verState, transactionID, reason, event.VerificationCancelByUser)
}
// SendSASVerificationCancel is used to manually send a SAS cancel message process with the given reason and cancellation code.
func (mach *OlmMachine) SendSASVerificationCancel(ctx context.Context, userID id.UserID, deviceID id.DeviceID, transactionID string, reason string, code event.VerificationCancelCode) error {
content := &event.VerificationCancelEventContent{
TransactionID: transactionID,
Reason: reason,
Code: code,
}
return mach.sendToOneDevice(ctx, userID, deviceID, event.ToDeviceVerificationCancel, content)
}
// SendSASVerificationStart is used to manually send the SAS verification start message to another device.
func (mach *OlmMachine) SendSASVerificationStart(ctx context.Context, toUserID id.UserID, toDeviceID id.DeviceID, transactionID string, methods []VerificationMethod) (*event.VerificationStartEventContent, error) {
sasMethods := make([]event.SASMethod, len(methods))
for i, method := range methods {
sasMethods[i] = method.Type()
}
content := &event.VerificationStartEventContent{
FromDevice: mach.Client.DeviceID,
TransactionID: transactionID,
Method: event.VerificationMethodSAS,
KeyAgreementProtocols: []event.KeyAgreementProtocol{event.KeyAgreementCurve25519HKDFSHA256},
Hashes: []event.VerificationHashMethod{event.VerificationHashSHA256},
MessageAuthenticationCodes: []event.MACMethod{event.HKDFHMACSHA256},
ShortAuthenticationString: sasMethods,
}
return content, mach.sendToOneDevice(ctx, toUserID, toDeviceID, event.ToDeviceVerificationStart, content)
}
// SendSASVerificationAccept is used to manually send an accept for a SAS verification process from a received m.key.verification.start event.
func (mach *OlmMachine) SendSASVerificationAccept(ctx context.Context, fromUser id.UserID, startEvent *event.VerificationStartEventContent, publicKey []byte, methods []VerificationMethod) error {
if startEvent.Method != event.VerificationMethodSAS {
reason := "Unknown verification method: " + string(startEvent.Method)
if err := mach.SendSASVerificationCancel(ctx, fromUser, startEvent.FromDevice, startEvent.TransactionID, reason, event.VerificationCancelUnknownMethod); err != nil {
return err
}
return ErrUnknownVerificationMethod
}
payload, err := json.Marshal(startEvent)
if err != nil {
return err
}
canonical, err := canonicaljson.CanonicalJSON(payload)
if err != nil {
return err
}
hash := olm.SHA256B64(append(publicKey, canonical...))
sasMethods := make([]event.SASMethod, len(methods))
for i, method := range methods {
sasMethods[i] = method.Type()
}
content := &event.VerificationAcceptEventContent{
TransactionID: startEvent.TransactionID,
Method: event.VerificationMethodSAS,
KeyAgreementProtocol: event.KeyAgreementCurve25519HKDFSHA256,
Hash: event.VerificationHashSHA256,
MessageAuthenticationCode: event.HKDFHMACSHA256,
ShortAuthenticationString: sasMethods,
Commitment: hash,
}
return mach.sendToOneDevice(ctx, fromUser, startEvent.FromDevice, event.ToDeviceVerificationAccept, content)
}
func (mach *OlmMachine) callbackAndCancelSASVerification(ctx context.Context, verState *verificationState, transactionID, reason string, code event.VerificationCancelCode) error {
go verState.hooks.OnCancel(true, reason, code)
return mach.SendSASVerificationCancel(ctx, verState.otherDevice.UserID, verState.otherDevice.DeviceID, transactionID, reason, code)
}
// SendSASVerificationKey sends the ephemeral public key for a device to the partner device.
func (mach *OlmMachine) SendSASVerificationKey(ctx context.Context, userID id.UserID, deviceID id.DeviceID, transactionID string, key string) error {
content := &event.VerificationKeyEventContent{
TransactionID: transactionID,
Key: key,
}
return mach.sendToOneDevice(ctx, userID, deviceID, event.ToDeviceVerificationKey, content)
}
// SendSASVerificationMAC is use the MAC of a device's key to the partner device.
func (mach *OlmMachine) SendSASVerificationMAC(ctx context.Context, userID id.UserID, deviceID id.DeviceID, transactionID string, sas *olm.SAS) error {
keyID := id.NewKeyID(id.KeyAlgorithmEd25519, mach.Client.DeviceID.String())
signingKey := mach.account.SigningKey()
keyIDsMap := map[id.KeyID]string{keyID: ""}
macMap := make(map[id.KeyID]string)
if mach.CrossSigningKeys != nil {
masterKey := mach.CrossSigningKeys.MasterKey.PublicKey
masterKeyID := id.NewKeyID(id.KeyAlgorithmEd25519, masterKey.String())
// add master key ID to key map
keyIDsMap[masterKeyID] = ""
masterKeyMAC, _, err := mach.getPKAndKeysMAC(sas, mach.Client.UserID, mach.Client.DeviceID,
userID, deviceID, transactionID, masterKey, masterKeyID, keyIDsMap)
if err != nil {
mach.Log.Error().Msgf("Error generating master key MAC: %v", err)
} else {
mach.Log.Debug().Msgf("Generated master key `%v` MAC: %v", masterKey, masterKeyMAC)
macMap[masterKeyID] = masterKeyMAC
}
}
pubKeyMac, keysMac, err := mach.getPKAndKeysMAC(sas, mach.Client.UserID, mach.Client.DeviceID, userID, deviceID, transactionID, signingKey, keyID, keyIDsMap)
if err != nil {
return err
}
mach.Log.Debug().Msgf("MAC of key %s is: %s", signingKey, pubKeyMac)
mach.Log.Debug().Msgf("MAC of key ID(s) %s is: %s", keyID, keysMac)
macMap[keyID] = pubKeyMac
content := &event.VerificationMacEventContent{
TransactionID: transactionID,
Keys: keysMac,
Mac: macMap,
}
return mach.sendToOneDevice(ctx, userID, deviceID, event.ToDeviceVerificationMAC, content)
}
func commonSASMethods(hooks VerificationHooks, otherDeviceMethods []event.SASMethod) []VerificationMethod {
methods := make([]VerificationMethod, 0)
for _, hookMethod := range hooks.VerificationMethods() {
for _, otherMethod := range otherDeviceMethods {
if hookMethod.Type() == otherMethod {
methods = append(methods, hookMethod)
break
}
}
}
return methods
}

View file

@ -1,334 +0,0 @@
// Copyright (c) 2020 Nikos Filippakis
//
// 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"
"encoding/json"
"errors"
"time"
"maunium.net/go/mautrix/crypto/canonicaljson"
"maunium.net/go/mautrix/crypto/olm"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
var (
ErrNoVerificationFromDevice = errors.New("from_device field is empty")
ErrNoVerificationMethods = errors.New("verification method list is empty")
ErrNoRelatesTo = errors.New("missing m.relates_to info")
)
// ProcessInRoomVerification is a callback that is to be called when a client receives a message
// related to in-room verification.
//
// Currently this is not automatically called, so you must add the listener yourself.
// Note that in-room verification events are wrapped in m.room.encrypted, but this expects the decrypted events.
func (mach *OlmMachine) ProcessInRoomVerification(evt *event.Event) error {
if evt.Sender == mach.Client.UserID {
// nothing to do if the message is our own
return nil
}
if relatable, ok := evt.Content.Parsed.(event.Relatable); !ok || relatable.OptionalGetRelatesTo() == nil {
return ErrNoRelatesTo
}
ctx := context.TODO()
switch content := evt.Content.Parsed.(type) {
case *event.MessageEventContent:
if content.MsgType == event.MsgVerificationRequest {
if content.FromDevice == "" {
return ErrNoVerificationFromDevice
}
if content.Methods == nil {
return ErrNoVerificationMethods
}
newContent := &event.VerificationRequestEventContent{
FromDevice: content.FromDevice,
Methods: content.Methods,
Timestamp: evt.Timestamp,
TransactionID: evt.ID.String(),
}
mach.handleVerificationRequest(ctx, evt.Sender, newContent, evt.ID.String(), evt.RoomID)
}
case *event.VerificationStartEventContent:
mach.handleVerificationStart(ctx, evt.Sender, content, content.RelatesTo.EventID.String(), 10*time.Minute, evt.RoomID)
case *event.VerificationReadyEventContent:
mach.handleInRoomVerificationReady(ctx, evt.Sender, evt.RoomID, content, content.RelatesTo.EventID.String())
case *event.VerificationAcceptEventContent:
mach.handleVerificationAccept(ctx, evt.Sender, content, content.RelatesTo.EventID.String())
case *event.VerificationKeyEventContent:
mach.handleVerificationKey(ctx, evt.Sender, content, content.RelatesTo.EventID.String())
case *event.VerificationMacEventContent:
mach.handleVerificationMAC(ctx, evt.Sender, content, content.RelatesTo.EventID.String())
case *event.VerificationCancelEventContent:
mach.handleVerificationCancel(evt.Sender, content, content.RelatesTo.EventID.String())
}
return nil
}
// SendInRoomSASVerificationCancel is used to manually send an in-room SAS cancel message process with the given reason and cancellation code.
func (mach *OlmMachine) SendInRoomSASVerificationCancel(ctx context.Context, roomID id.RoomID, userID id.UserID, transactionID string, reason string, code event.VerificationCancelCode) error {
content := &event.VerificationCancelEventContent{
RelatesTo: &event.RelatesTo{Type: event.RelReference, EventID: id.EventID(transactionID)},
Reason: reason,
Code: code,
To: userID,
}
encrypted, err := mach.EncryptMegolmEvent(ctx, roomID, event.InRoomVerificationCancel, content)
if err != nil {
return err
}
_, err = mach.Client.SendMessageEvent(ctx, roomID, event.EventEncrypted, encrypted)
return err
}
// SendInRoomSASVerificationRequest is used to manually send an in-room SAS verification request message to another user.
func (mach *OlmMachine) SendInRoomSASVerificationRequest(ctx context.Context, roomID id.RoomID, toUserID id.UserID, methods []VerificationMethod) (string, error) {
content := &event.MessageEventContent{
MsgType: event.MsgVerificationRequest,
FromDevice: mach.Client.DeviceID,
Methods: []event.VerificationMethod{event.VerificationMethodSAS},
To: toUserID,
}
encrypted, err := mach.EncryptMegolmEvent(ctx, roomID, event.EventMessage, content)
if err != nil {
return "", err
}
resp, err := mach.Client.SendMessageEvent(ctx, roomID, event.EventEncrypted, encrypted)
if err != nil {
return "", err
}
return resp.EventID.String(), nil
}
// SendInRoomSASVerificationReady is used to manually send an in-room SAS verification ready message to another user.
func (mach *OlmMachine) SendInRoomSASVerificationReady(ctx context.Context, roomID id.RoomID, transactionID string) error {
content := &event.VerificationReadyEventContent{
FromDevice: mach.Client.DeviceID,
Methods: []event.VerificationMethod{event.VerificationMethodSAS},
RelatesTo: &event.RelatesTo{Type: event.RelReference, EventID: id.EventID(transactionID)},
}
encrypted, err := mach.EncryptMegolmEvent(ctx, roomID, event.InRoomVerificationReady, content)
if err != nil {
return err
}
_, err = mach.Client.SendMessageEvent(ctx, roomID, event.EventEncrypted, encrypted)
return err
}
// SendInRoomSASVerificationStart is used to manually send the in-room SAS verification start message to another user.
func (mach *OlmMachine) SendInRoomSASVerificationStart(ctx context.Context, roomID id.RoomID, toUserID id.UserID, transactionID string, methods []VerificationMethod) (*event.VerificationStartEventContent, error) {
sasMethods := make([]event.SASMethod, len(methods))
for i, method := range methods {
sasMethods[i] = method.Type()
}
content := &event.VerificationStartEventContent{
FromDevice: mach.Client.DeviceID,
RelatesTo: &event.RelatesTo{Type: event.RelReference, EventID: id.EventID(transactionID)},
Method: event.VerificationMethodSAS,
KeyAgreementProtocols: []event.KeyAgreementProtocol{event.KeyAgreementCurve25519HKDFSHA256},
Hashes: []event.VerificationHashMethod{event.VerificationHashSHA256},
MessageAuthenticationCodes: []event.MACMethod{event.HKDFHMACSHA256},
ShortAuthenticationString: sasMethods,
To: toUserID,
}
encrypted, err := mach.EncryptMegolmEvent(ctx, roomID, event.InRoomVerificationStart, content)
if err != nil {
return nil, err
}
_, err = mach.Client.SendMessageEvent(ctx, roomID, event.EventEncrypted, encrypted)
return content, err
}
// SendInRoomSASVerificationAccept is used to manually send an accept for an in-room SAS verification process from a received m.key.verification.start event.
func (mach *OlmMachine) SendInRoomSASVerificationAccept(ctx context.Context, roomID id.RoomID, fromUser id.UserID, startEvent *event.VerificationStartEventContent, transactionID string, publicKey []byte, methods []VerificationMethod) error {
if startEvent.Method != event.VerificationMethodSAS {
reason := "Unknown verification method: " + string(startEvent.Method)
if err := mach.SendInRoomSASVerificationCancel(ctx, roomID, fromUser, transactionID, reason, event.VerificationCancelUnknownMethod); err != nil {
return err
}
return ErrUnknownVerificationMethod
}
payload, err := json.Marshal(startEvent)
if err != nil {
return err
}
canonical, err := canonicaljson.CanonicalJSON(payload)
if err != nil {
return err
}
hash := olm.SHA256B64(append(publicKey, canonical...))
sasMethods := make([]event.SASMethod, len(methods))
for i, method := range methods {
sasMethods[i] = method.Type()
}
content := &event.VerificationAcceptEventContent{
RelatesTo: &event.RelatesTo{Type: event.RelReference, EventID: id.EventID(transactionID)},
Method: event.VerificationMethodSAS,
KeyAgreementProtocol: event.KeyAgreementCurve25519HKDFSHA256,
Hash: event.VerificationHashSHA256,
MessageAuthenticationCode: event.HKDFHMACSHA256,
ShortAuthenticationString: sasMethods,
Commitment: hash,
To: fromUser,
}
encrypted, err := mach.EncryptMegolmEvent(ctx, roomID, event.InRoomVerificationAccept, content)
if err != nil {
return err
}
_, err = mach.Client.SendMessageEvent(ctx, roomID, event.EventEncrypted, encrypted)
return err
}
// SendInRoomSASVerificationKey sends the ephemeral public key for a device to the partner device for an in-room verification.
func (mach *OlmMachine) SendInRoomSASVerificationKey(ctx context.Context, roomID id.RoomID, userID id.UserID, transactionID string, key string) error {
content := &event.VerificationKeyEventContent{
RelatesTo: &event.RelatesTo{Type: event.RelReference, EventID: id.EventID(transactionID)},
Key: key,
To: userID,
}
encrypted, err := mach.EncryptMegolmEvent(ctx, roomID, event.InRoomVerificationKey, content)
if err != nil {
return err
}
_, err = mach.Client.SendMessageEvent(ctx, roomID, event.EventEncrypted, encrypted)
return err
}
// SendInRoomSASVerificationMAC sends the MAC of a device's key to the partner device for an in-room verification.
func (mach *OlmMachine) SendInRoomSASVerificationMAC(ctx context.Context, roomID id.RoomID, userID id.UserID, deviceID id.DeviceID, transactionID string, sas *olm.SAS) error {
keyID := id.NewKeyID(id.KeyAlgorithmEd25519, mach.Client.DeviceID.String())
signingKey := mach.account.SigningKey()
keyIDsMap := map[id.KeyID]string{keyID: ""}
macMap := make(map[id.KeyID]string)
if mach.CrossSigningKeys != nil {
masterKey := mach.CrossSigningKeys.MasterKey.PublicKey
masterKeyID := id.NewKeyID(id.KeyAlgorithmEd25519, masterKey.String())
// add master key ID to key map
keyIDsMap[masterKeyID] = ""
masterKeyMAC, _, err := mach.getPKAndKeysMAC(sas, mach.Client.UserID, mach.Client.DeviceID,
userID, deviceID, transactionID, masterKey, masterKeyID, keyIDsMap)
if err != nil {
mach.Log.Error().Msgf("Error generating master key MAC: %v", err)
} else {
mach.Log.Debug().Msgf("Generated master key `%v` MAC: %v", masterKey, masterKeyMAC)
macMap[masterKeyID] = masterKeyMAC
}
}
pubKeyMac, keysMac, err := mach.getPKAndKeysMAC(sas, mach.Client.UserID, mach.Client.DeviceID, userID, deviceID, transactionID, signingKey, keyID, keyIDsMap)
if err != nil {
return err
}
mach.Log.Debug().Msgf("MAC of key %s is: %s", signingKey, pubKeyMac)
mach.Log.Debug().Msgf("MAC of key ID(s) %s is: %s", keyID, keysMac)
macMap[keyID] = pubKeyMac
content := &event.VerificationMacEventContent{
RelatesTo: &event.RelatesTo{Type: event.RelReference, EventID: id.EventID(transactionID)},
Keys: keysMac,
Mac: macMap,
To: userID,
}
encrypted, err := mach.EncryptMegolmEvent(ctx, roomID, event.InRoomVerificationMAC, content)
if err != nil {
return err
}
_, err = mach.Client.SendMessageEvent(ctx, roomID, event.EventEncrypted, encrypted)
return err
}
// NewInRoomSASVerificationWith starts the in-room SAS verification process with another user in the given room.
// It returns the generated transaction ID.
func (mach *OlmMachine) NewInRoomSASVerificationWith(ctx context.Context, inRoomID id.RoomID, userID id.UserID, hooks VerificationHooks, timeout time.Duration) (string, error) {
return mach.newInRoomSASVerificationWithInner(ctx, inRoomID, &id.Device{UserID: userID}, hooks, "", timeout)
}
func (mach *OlmMachine) newInRoomSASVerificationWithInner(ctx context.Context, inRoomID id.RoomID, device *id.Device, hooks VerificationHooks, transactionID string, timeout time.Duration) (string, error) {
mach.Log.Debug().Msgf("Starting new in-room verification transaction user %v", device.UserID)
request := transactionID == ""
if request {
var err error
// get new transaction ID from the request message event ID
transactionID, err = mach.SendInRoomSASVerificationRequest(ctx, inRoomID, device.UserID, hooks.VerificationMethods())
if err != nil {
return "", err
}
}
verState := &verificationState{
sas: olm.NewSAS(),
otherDevice: device,
initiatedByUs: true,
verificationStarted: false,
keyReceived: false,
sasMatched: make(chan bool, 1),
hooks: hooks,
inRoomID: inRoomID,
}
verState.lock.Lock()
defer verState.lock.Unlock()
if !request {
// start in-room verification
startEvent, err := mach.SendInRoomSASVerificationStart(ctx, inRoomID, device.UserID, transactionID, hooks.VerificationMethods())
if err != nil {
return "", err
}
payload, err := json.Marshal(startEvent)
if err != nil {
return "", err
}
canonical, err := canonicaljson.CanonicalJSON(payload)
if err != nil {
return "", err
}
verState.startEventCanonical = string(canonical)
}
mach.keyVerificationTransactionState.Store(device.UserID.String()+":"+transactionID, verState)
mach.timeoutAfter(ctx, verState, transactionID, timeout)
return transactionID, nil
}
func (mach *OlmMachine) handleInRoomVerificationReady(ctx context.Context, userID id.UserID, roomID id.RoomID, content *event.VerificationReadyEventContent, transactionID string) {
device, err := mach.GetOrFetchDevice(ctx, userID, content.FromDevice)
if err != nil {
mach.Log.Error().Msgf("Error fetching device %v of user %v: %v", content.FromDevice, userID, err)
return
}
verState, err := mach.getTransactionState(ctx, transactionID, userID)
if err != nil {
mach.Log.Error().Msgf("Error getting transaction state: %v", err)
return
}
//mach.keyVerificationTransactionState.Delete(userID.String() + ":" + transactionID)
if mach.Client.UserID < userID {
// up to us to send the start message
verState.lock.Lock()
mach.newInRoomSASVerificationWithInner(ctx, roomID, device, verState.hooks, transactionID, 10*time.Minute)
verState.lock.Unlock()
}
}

View file

@ -1,201 +0,0 @@
// Copyright (c) 2020 Nikos Filippakis
//
// 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 (
"fmt"
"maunium.net/go/mautrix/crypto/olm"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
// SASData contains the data that users need to verify.
type SASData interface {
Type() event.SASMethod
}
// VerificationMethod describes a method for generating a SAS.
type VerificationMethod interface {
// GetVerificationSAS uses the user, device ID and key of the user who initiated the verification transaction,
// the user, device ID and key of the user who accepted, the transaction ID and the SAS object to generate a SAS.
// The SAS can be any type, such as an array of numbers or emojis.
GetVerificationSAS(initUserID id.UserID, initDeviceID id.DeviceID, initKey string,
acceptUserID id.UserID, acceptDeviceID id.DeviceID, acceptKey string,
transactionID string, sas *olm.SAS) (SASData, error)
// Type returns the type of this SAS method
Type() event.SASMethod
}
const sasInfoFormat = "MATRIX_KEY_VERIFICATION_SAS|%s|%s|%s|%s|%s|%s|%s"
// VerificationMethodDecimal describes the decimal SAS method.
type VerificationMethodDecimal struct{}
// DecimalSASData contains the verification numbers for the decimal SAS method.
type DecimalSASData [3]uint
// Type returns the decimal SAS method type.
func (DecimalSASData) Type() event.SASMethod {
return event.SASDecimal
}
// GetVerificationSAS generates the three numbers that need to match with the other device for a verification to be valid.
func (VerificationMethodDecimal) GetVerificationSAS(initUserID id.UserID, initDeviceID id.DeviceID, initKey string,
acceptUserID id.UserID, acceptDeviceID id.DeviceID, acceptKey string,
transactionID string, sas *olm.SAS) (SASData, error) {
sasInfo := fmt.Sprintf(sasInfoFormat,
initUserID, initDeviceID, initKey,
acceptUserID, acceptDeviceID, acceptKey,
transactionID)
sasBytes, err := sas.GenerateBytes([]byte(sasInfo), 5)
if err != nil {
return DecimalSASData{0, 0, 0}, err
}
numbers := DecimalSASData{
(uint(sasBytes[0])<<5 | uint(sasBytes[1])>>3) + 1000,
(uint(sasBytes[1]&0x7)<<10 | uint(sasBytes[2])<<2 | uint(sasBytes[3]>>6)) + 1000,
(uint(sasBytes[3]&0x3F)<<7 | uint(sasBytes[4])>>1) + 1000,
}
return numbers, nil
}
// Type returns the decimal SAS method type.
func (VerificationMethodDecimal) Type() event.SASMethod {
return event.SASDecimal
}
var allEmojis = [...]VerificationEmoji{
{'🐶', "Dog"},
{'🐱', "Cat"},
{'🦁', "Lion"},
{'🐎', "Horse"},
{'🦄', "Unicorn"},
{'🐷', "Pig"},
{'🐘', "Elephant"},
{'🐰', "Rabbit"},
{'🐼', "Panda"},
{'🐓', "Rooster"},
{'🐧', "Penguin"},
{'🐢', "Turtle"},
{'🐟', "Fish"},
{'🐙', "Octopus"},
{'🦋', "Butterfly"},
{'🌷', "Flower"},
{'🌳', "Tree"},
{'🌵', "Cactus"},
{'🍄', "Mushroom"},
{'🌏', "Globe"},
{'🌙', "Moon"},
{'☁', "Cloud"},
{'🔥', "Fire"},
{'🍌', "Banana"},
{'🍎', "Apple"},
{'🍓', "Strawberry"},
{'🌽', "Corn"},
{'🍕', "Pizza"},
{'🎂', "Cake"},
{'❤', "Heart"},
{'😀', "Smiley"},
{'🤖', "Robot"},
{'🎩', "Hat"},
{'👓', "Glasses"},
{'🔧', "Spanner"},
{'🎅', "Santa"},
{'👍', "Thumbs Up"},
{'☂', "Umbrella"},
{'⌛', "Hourglass"},
{'⏰', "Clock"},
{'🎁', "Gift"},
{'💡', "Light Bulb"},
{'📕', "Book"},
{'✏', "Pencil"},
{'📎', "Paperclip"},
{'✂', "Scissors"},
{'🔒', "Lock"},
{'🔑', "Key"},
{'🔨', "Hammer"},
{'☎', "Telephone"},
{'🏁', "Flag"},
{'🚂', "Train"},
{'🚲', "Bicycle"},
{'✈', "Aeroplane"},
{'🚀', "Rocket"},
{'🏆', "Trophy"},
{'⚽', "Ball"},
{'🎸', "Guitar"},
{'🎺', "Trumpet"},
{'🔔', "Bell"},
{'⚓', "Anchor"},
{'🎧', "Headphones"},
{'📁', "Folder"},
{'📌', "Pin"},
}
// VerificationEmoji describes an emoji that might be sent for verifying devices.
type VerificationEmoji struct {
Emoji rune
Description string
}
func (vm VerificationEmoji) GetEmoji() rune {
return vm.Emoji
}
func (vm VerificationEmoji) GetDescription() string {
return vm.Description
}
// EmojiSASData contains the verification emojis for the emoji SAS method.
type EmojiSASData [7]VerificationEmoji
// Type returns the emoji SAS method type.
func (EmojiSASData) Type() event.SASMethod {
return event.SASEmoji
}
// VerificationMethodEmoji describes the emoji SAS method.
type VerificationMethodEmoji struct{}
// GetVerificationSAS generates the three numbers that need to match with the other device for a verification to be valid.
func (VerificationMethodEmoji) GetVerificationSAS(initUserID id.UserID, initDeviceID id.DeviceID, initKey string,
acceptUserID id.UserID, acceptDeviceID id.DeviceID, acceptKey string,
transactionID string, sas *olm.SAS) (SASData, error) {
sasInfo := fmt.Sprintf(sasInfoFormat,
initUserID, initDeviceID, initKey,
acceptUserID, acceptDeviceID, acceptKey,
transactionID)
var emojis EmojiSASData
sasBytes, err := sas.GenerateBytes([]byte(sasInfo), 6)
if err != nil {
return emojis, err
}
sasNum := uint64(sasBytes[0])<<40 | uint64(sasBytes[1])<<32 | uint64(sasBytes[2])<<24 |
uint64(sasBytes[3])<<16 | uint64(sasBytes[4])<<8 | uint64(sasBytes[5])
for i := 0; i < len(emojis); i++ {
// take nth group of 6 bits
emojiIdx := (sasNum >> uint(48-(i+1)*6)) & 0x3F
emoji := allEmojis[emojiIdx]
emojis[i] = emoji
}
return emojis, nil
}
// Type returns the emoji SAS method type.
func (VerificationMethodEmoji) Type() event.SASMethod {
return event.SASEmoji
}