matrix-pretix/vendor/maunium.net/go/mautrix/crypto/verification_in_room.go

333 lines
13 KiB
Go

// 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 (
"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
}
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(evt.Sender, newContent, evt.ID.String(), evt.RoomID)
}
case *event.VerificationStartEventContent:
mach.handleVerificationStart(evt.Sender, content, content.RelatesTo.EventID.String(), 10*time.Minute, evt.RoomID)
case *event.VerificationReadyEventContent:
mach.handleInRoomVerificationReady(evt.Sender, evt.RoomID, content, content.RelatesTo.EventID.String())
case *event.VerificationAcceptEventContent:
mach.handleVerificationAccept(evt.Sender, content, content.RelatesTo.EventID.String())
case *event.VerificationKeyEventContent:
mach.handleVerificationKey(evt.Sender, content, content.RelatesTo.EventID.String())
case *event.VerificationMacEventContent:
mach.handleVerificationMAC(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(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(roomID, event.InRoomVerificationCancel, content)
if err != nil {
return err
}
_, err = mach.Client.SendMessageEvent(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(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(roomID, event.EventMessage, content)
if err != nil {
return "", err
}
resp, err := mach.Client.SendMessageEvent(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(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(roomID, event.InRoomVerificationReady, content)
if err != nil {
return err
}
_, err = mach.Client.SendMessageEvent(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(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(roomID, event.InRoomVerificationStart, content)
if err != nil {
return nil, err
}
_, err = mach.Client.SendMessageEvent(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(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(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.NewUtility().Sha256(string(publicKey) + string(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(roomID, event.InRoomVerificationAccept, content)
if err != nil {
return err
}
_, err = mach.Client.SendMessageEvent(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(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(roomID, event.InRoomVerificationKey, content)
if err != nil {
return err
}
_, err = mach.Client.SendMessageEvent(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(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("Error generating master key MAC: %v", err)
} else {
mach.Log.Debug("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("MAC of key %s is: %s", signingKey, pubKeyMac)
mach.Log.Debug("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(roomID, event.InRoomVerificationMAC, content)
if err != nil {
return err
}
_, err = mach.Client.SendMessageEvent(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(inRoomID id.RoomID, userID id.UserID, hooks VerificationHooks, timeout time.Duration) (string, error) {
return mach.newInRoomSASVerificationWithInner(inRoomID, &DeviceIdentity{UserID: userID}, hooks, "", timeout)
}
func (mach *OlmMachine) newInRoomSASVerificationWithInner(inRoomID id.RoomID, device *DeviceIdentity, hooks VerificationHooks, transactionID string, timeout time.Duration) (string, error) {
mach.Log.Debug("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(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(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(verState, transactionID, timeout)
return transactionID, nil
}
func (mach *OlmMachine) handleInRoomVerificationReady(userID id.UserID, roomID id.RoomID, content *event.VerificationReadyEventContent, transactionID string) {
device, err := mach.GetOrFetchDevice(userID, content.FromDevice)
if err != nil {
mach.Log.Error("Error fetching device %v of user %v: %v", content.FromDevice, userID, err)
return
}
verState, err := mach.getTransactionState(transactionID, userID)
if err != nil {
mach.Log.Error("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(roomID, device, verState.hooks, transactionID, 10*time.Minute)
verState.lock.Unlock()
}
}