Add some comments and other changes to crypto stuff

This commit is contained in:
Tulir Asokan 2020-06-23 21:42:33 +03:00
commit ea5cf3ee5e
12 changed files with 194 additions and 42 deletions

View file

@ -13,8 +13,10 @@ import (
)
type OlmAccount struct {
Internal olm.Account
Shared bool
Internal olm.Account
signingKey id.Curve25519
identityKey id.Ed25519
Shared bool
}
func NewOlmAccount() *OlmAccount {
@ -23,15 +25,35 @@ func NewOlmAccount() *OlmAccount {
}
}
func (account *OlmAccount) Keys() (id.Ed25519, id.Curve25519) {
if len(account.signingKey) == 0 || len(account.identityKey) == 0 {
account.identityKey, account.signingKey = account.Internal.IdentityKeys()
}
return account.identityKey, account.signingKey
}
func (account *OlmAccount) SigningKey() id.Curve25519 {
if len(account.signingKey) == 0 {
account.identityKey, account.signingKey = account.Internal.IdentityKeys()
}
return account.signingKey
}
func (account *OlmAccount) IdentityKey() id.Ed25519 {
if len(account.identityKey) == 0 {
account.identityKey, account.signingKey = account.Internal.IdentityKeys()
}
return account.identityKey
}
func (account *OlmAccount) getInitialKeys(userID id.UserID, deviceID id.DeviceID) *mautrix.DeviceKeys {
ed, curve := account.Internal.IdentityKeys()
deviceKeys := &mautrix.DeviceKeys{
UserID: userID,
DeviceID: deviceID,
Algorithms: []id.Algorithm{id.AlgorithmMegolmV1, id.AlgorithmOlmV1},
Keys: map[id.DeviceKeyID]string{
id.NewDeviceKeyID(id.KeyAlgorithmCurve25519, deviceID): string(curve),
id.NewDeviceKeyID(id.KeyAlgorithmEd25519, deviceID): string(ed),
id.NewDeviceKeyID(id.KeyAlgorithmCurve25519, deviceID): string(account.SigningKey()),
id.NewDeviceKeyID(id.KeyAlgorithmEd25519, deviceID): string(account.IdentityKey()),
},
}
@ -49,7 +71,7 @@ func (account *OlmAccount) getInitialKeys(userID id.UserID, deviceID id.DeviceID
}
func (account *OlmAccount) getOneTimeKeys(userID id.UserID, deviceID id.DeviceID, currentOTKCount int) map[id.KeyID]mautrix.OneTimeKey {
newCount := int(account.Internal.MaxNumberOfOneTimeKeys() / 2) - currentOTKCount
newCount := int(account.Internal.MaxNumberOfOneTimeKeys()/2) - currentOTKCount
if newCount > 0 {
account.Internal.GenOneTimeKeys(uint(newCount))
}

View file

@ -23,12 +23,13 @@ var (
DeviceKeyMismatch = errors.New("device keys in event and verified device info do not match")
)
type MegolmEvent struct {
type megolmEvent struct {
RoomID id.RoomID `json:"room_id"`
Type event.Type `json:"type"`
Content event.Content `json:"content"`
}
// DecryptMegolmEvent decrypts an m.room.encrypted event where the algorithm is m.megolm.v1.aes-sha2
func (mach *OlmMachine) DecryptMegolmEvent(evt *event.Event) (*event.Event, error) {
content, ok := evt.Content.Parsed.(*event.EncryptedEventContent)
if !ok {
@ -51,7 +52,7 @@ func (mach *OlmMachine) DecryptMegolmEvent(evt *event.Event) (*event.Event, erro
}
var verified bool
ownSigningKey, ownIdentityKey := mach.account.Internal.IdentityKeys()
ownSigningKey, ownIdentityKey := mach.account.Keys()
if content.DeviceID == mach.Client.DeviceID && sess.SigningKey == ownSigningKey && content.SenderKey == ownIdentityKey {
verified = true
} else {
@ -68,7 +69,7 @@ func (mach *OlmMachine) DecryptMegolmEvent(evt *event.Event) (*event.Event, erro
}
}
megolmEvt := &MegolmEvent{}
megolmEvt := &megolmEvent{}
err = json.Unmarshal(plaintext, &megolmEvt)
if err != nil {
return nil, errors.Wrap(err, "failed to parse megolm payload")

View file

@ -26,7 +26,8 @@ var (
RecipientKeyMismatch = errors.New("mismatched recipient key in olm payload")
)
type OlmEvent struct {
// DecryptedOlmEvent represents an event that was decrypted from an event encrypted with the m.olm.v1.curve25519-aes-sha2 algorithm.
type DecryptedOlmEvent struct {
Source *event.Event `json:"-"`
SenderKey id.SenderKey `json:"-"`
@ -41,15 +42,14 @@ type OlmEvent struct {
Content event.Content `json:"content"`
}
func (mach *OlmMachine) decryptOlmEvent(evt *event.Event) (*OlmEvent, error) {
func (mach *OlmMachine) decryptOlmEvent(evt *event.Event) (*DecryptedOlmEvent, error) {
content, ok := evt.Content.Parsed.(*event.EncryptedEventContent)
if !ok {
return nil, IncorrectEncryptedContentType
} else if content.Algorithm != id.AlgorithmOlmV1 {
return nil, UnsupportedAlgorithm
}
_, ownKey := mach.account.Internal.IdentityKeys()
ownContent, ok := content.OlmCiphertext[ownKey]
ownContent, ok := content.OlmCiphertext[mach.account.SigningKey()]
if !ok {
return nil, NotEncryptedForMe
}
@ -65,7 +65,7 @@ type OlmEventKeys struct {
Ed25519 id.Ed25519 `json:"ed25519"`
}
func (mach *OlmMachine) decryptOlmCiphertext(sender id.UserID, deviceID id.DeviceID, senderKey id.SenderKey, olmType id.OlmMsgType, ciphertext string) (*OlmEvent, error) {
func (mach *OlmMachine) decryptOlmCiphertext(sender id.UserID, deviceID id.DeviceID, senderKey id.SenderKey, olmType id.OlmMsgType, ciphertext string) (*DecryptedOlmEvent, error) {
if olmType != id.OlmMsgTypePreKey && olmType != id.OlmMsgTypeMsg {
return nil, UnsupportedOlmMessageType
}
@ -107,7 +107,7 @@ func (mach *OlmMachine) decryptOlmCiphertext(sender id.UserID, deviceID id.Devic
}
}
var olmEvt OlmEvent
var olmEvt DecryptedOlmEvent
err = json.Unmarshal(plaintext, &olmEvt)
if err != nil {
return nil, errors.Wrap(err, "failed to parse olm payload")
@ -116,7 +116,7 @@ func (mach *OlmMachine) decryptOlmCiphertext(sender id.UserID, deviceID id.Devic
return nil, SenderMismatch
} else if mach.Client.UserID != olmEvt.Recipient {
return nil, RecipientMismatch
} else if ed25519, _ := mach.account.Internal.IdentityKeys(); ed25519 != olmEvt.RecipientKeys.Ed25519 {
} else if mach.account.IdentityKey() != olmEvt.RecipientKeys.Ed25519 {
return nil, RecipientKeyMismatch
}
@ -165,7 +165,7 @@ func (mach *OlmMachine) createInboundSession(senderKey id.SenderKey, ciphertext
if err != nil {
return nil, err
}
mach.SaveAccount()
mach.saveAccount()
err = mach.CryptoStore.AddSession(senderKey, session)
if err != nil {
mach.Log.Error("Failed to store created inbound session: %v", err)

View file

@ -92,6 +92,10 @@ func (mach *OlmMachine) fetchKeys(users []id.UserID, sinceToken string, includeU
return data
}
// OnDevicesChanged finds all shared rooms with the given user and invalidates outbound sessions in those rooms.
//
// This is called automatically whenever a device list change is noticed in ProcessSyncResponse and usually does
// not need to be called manually.
func (mach *OlmMachine) OnDevicesChanged(userID id.UserID) {
for _, roomID := range mach.StateStore.FindSharedRooms(userID) {
mach.Log.Debug("Devices of %s changed, invalidating group session for %s", userID, roomID)

View file

@ -39,6 +39,15 @@ type rawMegolmEvent struct {
Content interface{} `json:"content"`
}
// IsShareError returns true if the error is caused by the lack of an outgoing megolm session and can be solved with OlmMachine.ShareGroupSession
func IsShareError(err error) bool {
return err == SessionExpired || err == SessionNotShared || err == NoGroupSession
}
// EncryptMegolmEvent encrypts data with the m.megolm.v1.aes-sha2 algorithm.
//
// If you use the event.Content struct, make sure you pass a pointer to the struct,
// as JSON serialization will not work correctly otherwise.
func (mach *OlmMachine) EncryptMegolmEvent(roomID id.RoomID, evtType event.Type, content interface{}) (*event.EncryptedEventContent, error) {
mach.Log.Trace("Encrypting event of type %s for %s", evtType.Type, roomID)
session, err := mach.CryptoStore.GetOutboundGroupSession(roomID)
@ -63,10 +72,9 @@ func (mach *OlmMachine) EncryptMegolmEvent(roomID id.RoomID, evtType event.Type,
if err != nil {
mach.Log.Warn("Failed to update megolm session in crypto store after encrypting: %v", err)
}
_, idKey := mach.account.Internal.IdentityKeys()
return &event.EncryptedEventContent{
Algorithm: id.AlgorithmMegolmV1,
SenderKey: idKey,
SenderKey: mach.account.SigningKey(),
DeviceID: mach.Client.DeviceID,
SessionID: session.ID(),
MegolmCiphertext: ciphertext,
@ -76,11 +84,15 @@ func (mach *OlmMachine) EncryptMegolmEvent(roomID id.RoomID, evtType event.Type,
func (mach *OlmMachine) newOutboundGroupSession(roomID id.RoomID) *OutboundGroupSession {
session := NewOutboundGroupSession(roomID)
signingKey, idKey := mach.account.Internal.IdentityKeys()
signingKey, idKey := mach.account.Keys()
mach.createGroupSession(idKey, signingKey, roomID, session.ID(), session.Internal.Key())
return session
}
// ShareGroupSession shares a group session for a specific room with all the devices of the given user list.
//
// For devices with TrustStateBlacklisted, a m.room_key.withheld event with code=m.blacklisted is sent.
// If AllowUnverifiedDevices is false, a similar event with code=m.unverified is sent to devices with TrustStateUnset
func (mach *OlmMachine) ShareGroupSession(roomID id.RoomID, users []id.UserID) error {
mach.Log.Trace("Sharing group session for room %s to %v", roomID, users)
session, err := mach.CryptoStore.GetOutboundGroupSession(roomID)
@ -159,18 +171,33 @@ func (mach *OlmMachine) ShareGroupSession(roomID id.RoomID, users []id.UserID) e
func (mach *OlmMachine) encryptGroupSessionForUser(session *OutboundGroupSession, userID id.UserID, devices map[id.DeviceID]*DeviceIdentity, output map[id.DeviceID]*event.Content, missingOutput map[id.DeviceID]*DeviceIdentity) {
for deviceID, device := range devices {
userKey := UserDevice{UserID: userID, DeviceID: deviceID}
if userID == mach.Client.UserID && deviceID == mach.Client.DeviceID {
session.Users[userKey] = OGSIgnored
}
// TODO blacklisting and verification checking should be done around here
if state := session.Users[userKey]; state != OGSNotShared {
continue
}
deviceSession, err := mach.CryptoStore.GetLatestSession(device.IdentityKey)
if err != nil {
} else if userID == mach.Client.UserID && deviceID == mach.Client.DeviceID {
session.Users[userKey] = OGSIgnored
} else if device.Trust == TrustStateBlacklisted {
mach.Log.Debug("Not encrypting group session %s for %s of %s: device is blacklisted", session.ID(), deviceID, userID)
output[deviceID] = &event.Content{Parsed: event.RoomKeyWithheldEventContent{
RoomID: session.RoomID,
Algorithm: id.AlgorithmMegolmV1,
SessionID: session.ID(),
SenderKey: mach.account.SigningKey(),
Code: event.RoomKeyWithheldBlacklisted,
Reason: "Device is blacklisted",
}}
session.Users[userKey] = OGSIgnored
} else if !mach.AllowUnverifiedDevices && device.Trust == TrustStateUnset {
mach.Log.Debug("Not encrypting group session %s for %s of %s: device is not verified", session.ID(), deviceID, userID)
output[deviceID] = &event.Content{Parsed: event.RoomKeyWithheldEventContent{
RoomID: session.RoomID,
Algorithm: id.AlgorithmMegolmV1,
SessionID: session.ID(),
SenderKey: mach.account.SigningKey(),
Code: event.RoomKeyWithheldUnverified,
Reason: "Device is not verified",
}}
session.Users[userKey] = OGSIgnored
} else if deviceSession, err := mach.CryptoStore.GetLatestSession(device.IdentityKey); err != nil {
mach.Log.Error("Failed to get session for %s of %s: %v", deviceID, userID, err)
} else if deviceSession == nil {
mach.Log.Warn("Didn't find a session for %s of %s", deviceID, userID)

View file

@ -18,11 +18,10 @@ import (
)
func (mach *OlmMachine) encryptOlmEvent(session *OlmSession, recipient *DeviceIdentity, evtType event.Type, content event.Content) *event.EncryptedEventContent {
selfSigningKey, selfIdentityKey := mach.account.Internal.IdentityKeys()
evt := &OlmEvent{
evt := &DecryptedOlmEvent{
Sender: mach.Client.UserID,
SenderDevice: mach.Client.DeviceID,
Keys: OlmEventKeys{Ed25519: selfSigningKey},
Keys: OlmEventKeys{Ed25519: mach.account.IdentityKey()},
Recipient: recipient.UserID,
RecipientKeys: OlmEventKeys{Ed25519: recipient.SigningKey},
Type: evtType,
@ -39,7 +38,7 @@ func (mach *OlmMachine) encryptOlmEvent(session *OlmSession, recipient *DeviceId
}
return &event.EncryptedEventContent{
Algorithm: id.AlgorithmOlmV1,
SenderKey: selfIdentityKey,
SenderKey: mach.account.SigningKey(),
OlmCiphertext: event.OlmCiphertexts{
recipient.IdentityKey: {
Type: msgType,

View file

@ -14,6 +14,8 @@ import (
"maunium.net/go/mautrix/event"
)
// Logger is a simple logging struct for OlmMachine.
// Implementations are recommended to use fmt.Sprintf and manually add a newline after the message.
type Logger interface {
Error(message string, args ...interface{})
Warn(message string, args ...interface{})
@ -21,6 +23,7 @@ type Logger interface {
Trace(message string, args ...interface{})
}
// OlmMachine is the main struct for handling Matrix end-to-end encryption.
type OlmMachine struct {
Client *mautrix.Client
Log Logger
@ -28,23 +31,31 @@ type OlmMachine struct {
CryptoStore Store
StateStore StateStore
AllowUnverifiedDevices bool
account *OlmAccount
}
// StateStore is used by OlmMachine to get room state information that's needed for encryption.
type StateStore interface {
IsEncrypted(id.RoomID) bool
FindSharedRooms(id.UserID) []id.RoomID
}
// NewOlmMachine creates an OlmMachine with the given client, logger and stores.
func NewOlmMachine(client *mautrix.Client, log Logger, cryptoStore Store, stateStore StateStore) *OlmMachine {
return &OlmMachine{
Client: client,
Log: log,
CryptoStore: cryptoStore,
StateStore: stateStore,
AllowUnverifiedDevices: true,
}
}
// Load loads the Olm account information from the crypto store. If there's no olm account, a new one is created.
// This must be called before using the machine.
func (mach *OlmMachine) Load() (err error) {
mach.account, err = mach.CryptoStore.GetAccount()
if err != nil {
@ -58,19 +69,21 @@ func (mach *OlmMachine) Load() (err error) {
return nil
}
func (mach *OlmMachine) SaveAccount() {
func (mach *OlmMachine) saveAccount() {
err := mach.CryptoStore.PutAccount(mach.account)
if err != nil {
mach.Log.Error("Failed to save account: %v", err)
}
}
// FlushStore calls the Flush method of the CryptoStore.
func (mach *OlmMachine) FlushStore() error {
return mach.CryptoStore.Flush()
}
// Fingerprint returns the fingerprint of the Olm account that can be used for non-interactive verification.
func (mach *OlmMachine) Fingerprint() string {
signingKey, _ := mach.account.Internal.IdentityKeys()
signingKey := mach.account.SigningKey()
spacedSigningKey := make([]byte, len(signingKey)+(len(signingKey)-1)/4)
var ptr = 0
for i, chr := range signingKey {
@ -84,6 +97,11 @@ func (mach *OlmMachine) Fingerprint() string {
return string(spacedSigningKey)
}
// ProcessSyncResponse processes a single /sync response.
//
// This can be easily registered into a mautrix client using .OnSync():
//
// client.Syncer.(*mautrix.DefaultSyncer).OnSync(c.crypto.ProcessSyncResponse)
func (mach *OlmMachine) ProcessSyncResponse(resp *mautrix.RespSync, since string) {
if len(resp.DeviceLists.Changed) > 0 {
mach.Log.Trace("Device list changes in /sync: %v", resp.DeviceLists.Changed)
@ -110,6 +128,11 @@ func (mach *OlmMachine) ProcessSyncResponse(resp *mautrix.RespSync, since string
}
}
// HandleMemberEvent handles a single membership event.
//
// Currently this is not automatically called, so you must add a listener yourself:
//
// client.Syncer.(*mautrix.DefaultSyncer).OnSync(c.crypto.ProcessSyncResponse)
func (mach *OlmMachine) HandleMemberEvent(evt *event.Event) {
if !mach.StateStore.IsEncrypted(evt.RoomID) {
return
@ -139,6 +162,8 @@ func (mach *OlmMachine) HandleMemberEvent(evt *event.Event) {
}
}
// HandleToDeviceEvent handles a single to-device event. This is automatically called by ProcessSyncResponse, so you
// don't need to add any custom handlers if you use that method.
func (mach *OlmMachine) HandleToDeviceEvent(evt *event.Event) {
switch content := evt.Content.Parsed.(type) {
case *event.EncryptedEventContent:
@ -176,7 +201,7 @@ func (mach *OlmMachine) createGroupSession(senderKey id.SenderKey, signingKey id
mach.Log.Trace("Created inbound group session %s/%s/%s", roomID, senderKey, sessionID)
}
func (mach *OlmMachine) receiveRoomKey(evt *OlmEvent, content *event.RoomKeyEventContent) {
func (mach *OlmMachine) receiveRoomKey(evt *DecryptedOlmEvent, content *event.RoomKeyEventContent) {
// TODO nio had a comment saying "handle this better" for the case where evt.Keys.Ed25519 is none?
if content.Algorithm != id.AlgorithmMegolmV1 || evt.Keys.Ed25519 == "" {
return
@ -185,7 +210,11 @@ func (mach *OlmMachine) receiveRoomKey(evt *OlmEvent, content *event.RoomKeyEven
mach.createGroupSession(evt.SenderKey, evt.Keys.Ed25519, content.RoomID, content.SessionID, content.SessionKey)
}
// ShareKeys returns a key upload request.
// ShareKeys uploads necessary keys to the server.
//
// If the Olm account hasn't been shared, the account keys will be uploaded.
// If currentOTKCount is less than half of the limit (100 / 2 = 50), enough one-time keys will be uploaded so exactly
// half of the limit is filled.
func (mach *OlmMachine) ShareKeys(currentOTKCount int) error {
var deviceKeys *mautrix.DeviceKeys
if !mach.account.Shared {
@ -207,7 +236,7 @@ func (mach *OlmMachine) ShareKeys(currentOTKCount int) error {
return err
}
mach.account.Shared = true
mach.SaveAccount()
mach.saveAccount()
mach.Log.Trace("Shared keys and saved account")
return nil
}

View file

@ -15,6 +15,7 @@ import (
"maunium.net/go/mautrix/id"
)
// TrustState determines how trusted a device is.
type TrustState int
const (
@ -24,6 +25,7 @@ const (
TrustStateIgnored
)
// DeviceIdentity contains the identity details of a device and some additional info.
type DeviceIdentity struct {
UserID id.UserID
DeviceID id.DeviceID
@ -35,31 +37,68 @@ type DeviceIdentity struct {
Name string
}
// Store is used by OlmMachine to store Olm and Megolm sessions, user device lists and message indices.
//
// General implementation details:
// * Get methods should not return errors if the requested data does not exist in the store, they should simply return nil.
// * Update methods may assume that the pointer is the same as what has earlier been added to or fetched from the store.
type Store interface {
// Flush ensures that everything in the store is persisted to disk.
// This doesn't have to do anything, e.g. for database-backed implementations that persist everything immediately.
Flush() error
// PutAccount updates the OlmAccount in the store.
PutAccount(*OlmAccount) error
// GetAccount returns the OlmAccount in the store that was previously inserted with PutAccount.
GetAccount() (*OlmAccount, error)
HasSession(id.SenderKey) bool
GetSessions(id.SenderKey) (OlmSessionList, error)
GetLatestSession(id.SenderKey) (*OlmSession, error)
// AddSession inserts an Olm session into the store.
AddSession(id.SenderKey, *OlmSession) error
// HasSession returns whether or not the store has an Olm session with the given sender key.
HasSession(id.SenderKey) bool
// GetSessions returns all Olm sessions in the store with the given sender key.
GetSessions(id.SenderKey) (OlmSessionList, error)
// GetLatestSession returns the session with the highest session ID (lexiographically sorting).
// It's usually safe to return the most recently added session if sorting by session ID is too difficult.
GetLatestSession(id.SenderKey) (*OlmSession, error)
// UpdateSession updates a session that has previously been inserted with AddSession.
UpdateSession(id.SenderKey, *OlmSession) error
// PutGroupSession inserts an inbound Megolm session into the store.
PutGroupSession(id.RoomID, id.SenderKey, id.SessionID, *InboundGroupSession) error
// GetGroupSession gets an inbound Megolm session from the store.
GetGroupSession(id.RoomID, id.SenderKey, id.SessionID) (*InboundGroupSession, error)
// AddOutboundGroupSession inserts the given outbound Megolm session into the store.
//
// The store should index inserted sessions by the RoomID field to support getting and removing sessions.
// There will only be one outbound session per room ID at a time.
AddOutboundGroupSession(*OutboundGroupSession) error
// UpdateOutboundGroupSession updates the given outbound Megolm session in the store.
UpdateOutboundGroupSession(*OutboundGroupSession) error
// GetOutboundGroupSession gets the stored outbound Megolm session for the given room ID from the store.
GetOutboundGroupSession(id.RoomID) (*OutboundGroupSession, error)
// RemoveOutboundGroupSession removes the stored outbound Megolm session for the given room ID.
RemoveOutboundGroupSession(id.RoomID) error
// ValidateMessageIndex validates that the given message details aren't from a replay attack.
//
// Implementations should store a map from (senderKey, sessionID, index) to (eventID, timestamp), then use that map
// to check whether or not the message index is valid:
//
// * If the map key doesn't exist, the given values should be stored and this should return true.
// * If the map key exists and the stored values match the given values, this should return true.
// * If the map key exists, but the stored values do not match the given values, this should return false.
ValidateMessageIndex(senderKey id.SenderKey, sessionID id.SessionID, eventID id.EventID, index uint, timestamp int64) bool
// GetDevices returns a map from device ID to DeviceIdentity containing all devices of a given user.
GetDevices(id.UserID) (map[id.DeviceID]*DeviceIdentity, error)
// GetDevice returns a specific device of a given user.
GetDevice(id.UserID, id.DeviceID) (*DeviceIdentity, error)
// PutDevices overrides the stored device list for the given user with the given list.
PutDevices(id.UserID, map[id.DeviceID]*DeviceIdentity) error
// FilterTrackedUsers returns a filtered version of the given list that only includes user IDs whose device lists
// have been stored with PutDevices. A user is considered tracked even if the PutDevices list was empty.
FilterTrackedUsers([]id.UserID) []id.UserID
}
@ -74,6 +113,7 @@ type messageIndexValue struct {
Timestamp int64
}
// GobStore is a simple Store implementation that dumps everything into a .gob file.
type GobStore struct {
lock sync.RWMutex
path string
@ -88,6 +128,7 @@ type GobStore struct {
var _ Store = (*GobStore)(nil)
// NewGobStore creates a new GobStore that saves everything to the given file.
func NewGobStore(path string) (*GobStore, error) {
gs := &GobStore{
path: path,

View file

@ -50,6 +50,7 @@ var TypeMap = map[Type]reflect.Type{
ToDeviceForwardedRoomKey: reflect.TypeOf(ForwardedRoomKeyEventContent{}),
ToDeviceRoomKeyRequest: reflect.TypeOf(RoomKeyRequestEventContent{}),
ToDeviceEncrypted: reflect.TypeOf(EncryptedEventContent{}),
ToDeviceRoomKeyWithheld: reflect.TypeOf(RoomKeyWithheldEventContent{}),
}
// Content stores the content of a Matrix event.
@ -174,6 +175,7 @@ func init() {
gob.Register(&RoomKeyEventContent{})
gob.Register(&ForwardedRoomKeyEventContent{})
gob.Register(&RoomKeyRequestEventContent{})
gob.Register(&RoomKeyWithheldEventContent{})
}
// Helper cast functions below
@ -367,3 +369,10 @@ func (content *Content) AsRoomKeyRequest() *RoomKeyRequestEventContent {
}
return casted
}
func (content *Content) AsRoomKeyWithheld() *RoomKeyWithheldEventContent {
casted, ok := content.Parsed.(*RoomKeyWithheldEventContent)
if !ok {
return &RoomKeyWithheldEventContent{}
}
return casted
}

View file

@ -120,3 +120,22 @@ type RequestedKeyInfo struct {
SenderKey id.SenderKey `json:"sender_key"`
SessionID id.SessionID `json:"session_id"`
}
type RoomKeyWithheldCode string
const (
RoomKeyWithheldBlacklisted RoomKeyWithheldCode = "m.blacklisted"
RoomKeyWithheldUnverified RoomKeyWithheldCode = "m.unverified"
RoomKeyWithheldUnauthorized RoomKeyWithheldCode = "m.unauthorized"
RoomKeyWithheldUnavailable RoomKeyWithheldCode = "m.unavailable"
RoomKeyWithheldNoOlmSession RoomKeyWithheldCode = "m.no_olm"
)
type RoomKeyWithheldEventContent struct {
RoomID id.RoomID `json:"room_id,omitempty"`
Algorithm id.Algorithm `json:"algorithm"`
SessionID id.SessionID `json:"session_id,omitempty"`
SenderKey id.SenderKey `json:"sender_key"`
Code RoomKeyWithheldCode `json:"code"`
Reason string `json:"reason,omitempty"`
}

View file

@ -89,7 +89,7 @@ func (et *Type) GuessClass() TypeClass {
return AccountDataEventType
case EventRedaction.Type, EventMessage.Type, EventEncrypted.Type, EventReaction.Type, EventSticker.Type:
return MessageEventType
case ToDeviceRoomKey.Type, ToDeviceRoomKeyRequest.Type, ToDeviceForwardedRoomKey.Type:
case ToDeviceRoomKey.Type, ToDeviceRoomKeyRequest.Type, ToDeviceForwardedRoomKey.Type, ToDeviceRoomKeyWithheld.Type:
return ToDeviceEventType
default:
return UnknownEventType
@ -176,4 +176,5 @@ var (
ToDeviceRoomKeyRequest = Type{"m.room_key_request", ToDeviceEventType}
ToDeviceForwardedRoomKey = Type{"m.forwarded_room_key", ToDeviceEventType}
ToDeviceEncrypted = Type{"m.room.encrypted", ToDeviceEventType}
ToDeviceRoomKeyWithheld = Type{"m.room_key.withheld", ToDeviceEventType}
)

View file

@ -1,3 +1,3 @@
package mautrix
const Version = "v0.5.2"
const Version = "v0.5.3"