mirror of
https://mau.dev/mautrix/go.git
synced 2026-03-14 14:25:53 +01:00
crypto: remove old verification code
Signed-off-by: Sumner Evans <sumner@beeper.com>
This commit is contained in:
parent
b369efbc06
commit
6bfa468ee7
6 changed files with 15 additions and 1385 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue