mirror of
https://mau.dev/mautrix/go.git
synced 2026-03-14 14:25:53 +01:00
Add initial outbound encryption
This commit is contained in:
parent
b07cb6dc1f
commit
fe82e2b914
22 changed files with 599 additions and 77 deletions
|
|
@ -752,7 +752,7 @@ func (cli *Client) UserTyping(roomID id.RoomID, typing bool, timeout int64) (res
|
|||
return
|
||||
}
|
||||
|
||||
func (cli *Client) SetPresence(status string) (err error) {
|
||||
func (cli *Client) SetPresence(status event.Presence) (err error) {
|
||||
req := ReqPresence{Presence: status}
|
||||
u := cli.BuildURL("presence", cli.UserID, "status")
|
||||
_, err = cli.MakeRequest("PUT", u, req, nil)
|
||||
|
|
@ -998,6 +998,12 @@ func (cli *Client) GetKeyChanges(from, to string) (resp *RespKeyChanges, err err
|
|||
return
|
||||
}
|
||||
|
||||
func (cli *Client) SendToDevice(eventType event.Type, req *ReqSendToDevice) (resp *RespSendToDevice, err error) {
|
||||
urlPath := cli.BuildURL("sendToDevice", eventType.String(), cli.TxnID())
|
||||
_, err = cli.MakeRequest("PUT", urlPath, req, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// GetPushRules returns the push notification rules for the global scope.
|
||||
func (cli *Client) GetPushRules() (*pushrules.PushRuleset, error) {
|
||||
return cli.GetScopedPushRules("global")
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ func (account *OlmAccount) getOneTimeKeys(userID id.UserID, deviceID id.DeviceID
|
|||
// TODO do we need unsigned curve25519 one-time keys at all?
|
||||
// this just signs all of them
|
||||
for keyID, key := range account.OneTimeKeys() {
|
||||
key := mautrix.OneTimeKey{Key: string(key)}
|
||||
key := mautrix.OneTimeKey{Key: key}
|
||||
signature, _ := account.SignJSON(key)
|
||||
key.Signatures = mautrix.Signatures{
|
||||
userID: {
|
||||
|
|
|
|||
|
|
@ -19,9 +19,11 @@ var (
|
|||
IncorrectEncryptedContentType = errors.New("event content is not instance of *event.EncryptedEventContent")
|
||||
NoSessionFound = errors.New("failed to decrypt megolm event: no session with given ID found")
|
||||
DuplicateMessageIndex = errors.New("duplicate message index")
|
||||
WrongRoom = errors.New("encrypted megolm event is not intended for this room")
|
||||
)
|
||||
|
||||
type MegolmEvent struct {
|
||||
RoomID id.RoomID `json:"room_id"`
|
||||
Type event.Type `json:"type"`
|
||||
Content event.Content `json:"content"`
|
||||
}
|
||||
|
|
@ -52,6 +54,8 @@ func (mach *OlmMachine) DecryptMegolmEvent(evt *event.Event) (*event.Event, erro
|
|||
err = json.Unmarshal(plaintext, &megolmEvt)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse megolm payload")
|
||||
} else if megolmEvt.RoomID != evt.RoomID {
|
||||
return nil, WrongRoom
|
||||
}
|
||||
err = megolmEvt.Content.ParseRaw(megolmEvt.Type)
|
||||
if err != nil && !event.IsUnsupportedContentType(err) {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ type OlmEvent struct {
|
|||
Content event.Content `json:"content"`
|
||||
}
|
||||
|
||||
func (mach *OlmMachine) DecryptOlmEvent(evt *event.Event) (*OlmEvent, error) {
|
||||
func (mach *OlmMachine) decryptOlmEvent(evt *event.Event) (*OlmEvent, error) {
|
||||
content, ok := evt.Content.Parsed.(*event.EncryptedEventContent)
|
||||
if !ok {
|
||||
return nil, IncorrectEncryptedContentType
|
||||
|
|
@ -49,27 +49,32 @@ func (mach *OlmMachine) DecryptOlmEvent(evt *event.Event) (*OlmEvent, error) {
|
|||
return nil, UnsupportedAlgorithm
|
||||
}
|
||||
_, ownKey := mach.account.IdentityKeys()
|
||||
ownContent, ok := content.OlmCiphertext[string(ownKey)]
|
||||
ownContent, ok := content.OlmCiphertext[ownKey]
|
||||
if !ok {
|
||||
return nil, NotEncryptedForMe
|
||||
}
|
||||
return mach.decryptOlmEvent(evt, content.SenderKey, ownContent.Type, ownContent.Body)
|
||||
decrypted, err := mach.decryptOlmCiphertext(evt.Sender, content.SenderKey, ownContent.Type, ownContent.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decrypted.Source = evt
|
||||
return decrypted, nil
|
||||
}
|
||||
|
||||
type OlmEventKeys struct {
|
||||
Ed25519 id.Ed25519 `json:"ed25519"`
|
||||
}
|
||||
|
||||
func (mach *OlmMachine) decryptOlmEvent(evt *event.Event, senderKey id.SenderKey, olmType id.OlmMsgType, ciphertext string) (*OlmEvent, error) {
|
||||
func (mach *OlmMachine) decryptOlmCiphertext(sender id.UserID, senderKey id.SenderKey, olmType id.OlmMsgType, ciphertext string) (*OlmEvent, error) {
|
||||
if olmType != id.OlmMsgTypePreKey && olmType != id.OlmMsgTypeMsg {
|
||||
return nil, UnsupportedOlmMessageType
|
||||
}
|
||||
|
||||
plaintext, err := mach.tryDecryptOlmEvent(senderKey, olmType, ciphertext)
|
||||
plaintext, err := mach.tryDecryptOlmCiphertext(senderKey, olmType, ciphertext)
|
||||
if err != nil {
|
||||
if err == DecryptionFailedWithMatchingSession {
|
||||
mach.log.Warn("Found matching session yet decryption failed for sender %s with key %s", evt.Sender, senderKey)
|
||||
mach.markDeviceForUnwedging(evt.Sender, senderKey)
|
||||
mach.log.Warn("Found matching session yet decryption failed for sender %s with key %s", sender, senderKey)
|
||||
mach.markDeviceForUnwedging(sender, senderKey)
|
||||
}
|
||||
return nil, errors.Wrap(err, "failed to decrypt olm event")
|
||||
}
|
||||
|
|
@ -79,13 +84,13 @@ func (mach *OlmMachine) decryptOlmEvent(evt *event.Event, senderKey id.SenderKey
|
|||
// New sessions can only be created if it's a prekey message, we can't decrypt the message
|
||||
// if it isn't one at this point in time anymore, so return early.
|
||||
if olmType != id.OlmMsgTypePreKey {
|
||||
mach.markDeviceForUnwedging(evt.Sender, senderKey)
|
||||
mach.markDeviceForUnwedging(sender, senderKey)
|
||||
return nil, DecryptionFailedForNormalMessage
|
||||
}
|
||||
|
||||
session, err := mach.createInboundSession(senderKey, ciphertext)
|
||||
if err != nil {
|
||||
mach.markDeviceForUnwedging(evt.Sender, senderKey)
|
||||
mach.markDeviceForUnwedging(sender, senderKey)
|
||||
return nil, errors.Wrap(err, "failed to create new session from prekey message")
|
||||
}
|
||||
|
||||
|
|
@ -100,7 +105,7 @@ func (mach *OlmMachine) decryptOlmEvent(evt *event.Event, senderKey id.SenderKey
|
|||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse olm payload")
|
||||
}
|
||||
if evt.Sender != olmEvt.Sender {
|
||||
if sender != olmEvt.Sender {
|
||||
return nil, SenderMismatch
|
||||
} else if mach.client.UserID != olmEvt.Recipient {
|
||||
return nil, RecipientMismatch
|
||||
|
|
@ -113,13 +118,12 @@ func (mach *OlmMachine) decryptOlmEvent(evt *event.Event, senderKey id.SenderKey
|
|||
return nil, errors.Wrap(err, "failed to parse content of olm payload event")
|
||||
}
|
||||
|
||||
olmEvt.Source = evt
|
||||
olmEvt.SenderKey = senderKey
|
||||
|
||||
return &olmEvt, nil
|
||||
}
|
||||
|
||||
func (mach *OlmMachine) tryDecryptOlmEvent(senderKey id.SenderKey, olmType id.OlmMsgType, ciphertext string) ([]byte, error) {
|
||||
func (mach *OlmMachine) tryDecryptOlmCiphertext(senderKey id.SenderKey, olmType id.OlmMsgType, ciphertext string) ([]byte, error) {
|
||||
sessions, err := mach.store.GetSessions(senderKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get session for %s", senderKey)
|
||||
|
|
|
|||
116
crypto/devicelist.go
Normal file
116
crypto/devicelist.go
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
// Copyright (c) 2020 Tulir Asokan
|
||||
//
|
||||
// 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 (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix/crypto/olm"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
func (mach *OlmMachine) FetchKeys(users []id.UserID, sinceToken string) {
|
||||
req := &mautrix.ReqQueryKeys{
|
||||
DeviceKeys: mautrix.DeviceKeysRequest{},
|
||||
Timeout: 10 * 1000,
|
||||
Token: sinceToken,
|
||||
}
|
||||
for _, userID := range users {
|
||||
req.DeviceKeys[userID] = mautrix.DeviceIDList{}
|
||||
}
|
||||
mach.log.Trace("Querying keys for %v", users)
|
||||
resp, err := mach.client.QueryKeys(req)
|
||||
if err != nil {
|
||||
mach.log.Warn("Failed to query keys: %v", err)
|
||||
return
|
||||
}
|
||||
for server, err := range resp.Failures {
|
||||
mach.log.Warn("Query keys failure for %s: %v", server, err)
|
||||
}
|
||||
mach.log.Trace("Query key result received with %d users", len(resp.DeviceKeys))
|
||||
for userID, devices := range resp.DeviceKeys {
|
||||
delete(req.DeviceKeys, userID)
|
||||
|
||||
newDevices := make(map[id.DeviceID]*DeviceIdentity)
|
||||
existingDevices, err := mach.store.GetDevices(userID)
|
||||
if err != nil {
|
||||
mach.log.Warn("Failed to get existing devices for %s: %v", userID, err)
|
||||
existingDevices = make(map[id.DeviceID]*DeviceIdentity)
|
||||
}
|
||||
mach.log.Trace("Updating devices for %s, got %d devices, have %d in store", userID, len(devices), len(existingDevices))
|
||||
for deviceID, deviceKeys := range devices {
|
||||
existing := existingDevices[deviceID]
|
||||
mach.log.Trace("Validating device %s of %s", deviceID, userID)
|
||||
newDevice, err := mach.validateDevice(userID, deviceID, deviceKeys, existing)
|
||||
if err != nil {
|
||||
mach.log.Error("Failed to validate device %s of %s: %v", deviceID, userID, err)
|
||||
} else if newDevice != nil {
|
||||
newDevices[deviceID] = newDevice
|
||||
}
|
||||
}
|
||||
mach.log.Trace("Storing new device list for %s containing %d devices", userID, len(newDevices))
|
||||
err = mach.store.PutDevices(userID, newDevices)
|
||||
if err != nil {
|
||||
mach.log.Warn("Failed to update device list for %s: %v", userID, err)
|
||||
}
|
||||
}
|
||||
for userID := range req.DeviceKeys {
|
||||
mach.log.Warn("Didn't get any keys for user %s", userID)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
MismatchingDeviceID = errors.New("mismatching device ID in parameter and keys object")
|
||||
MismatchingUserID = errors.New("mismatching user ID in parameter and keys object")
|
||||
MismatchingSigningKey = errors.New("received update for device with different signing key")
|
||||
NoSigningKeyFound = errors.New("didn't find ed25519 signing key")
|
||||
NoIdentityKeyFound = errors.New("didn't find curve25519 identity key")
|
||||
InvalidKeySignature = errors.New("invalid signature on device keys")
|
||||
)
|
||||
|
||||
func (mach *OlmMachine) validateDevice(userID id.UserID, deviceID id.DeviceID, deviceKeys mautrix.DeviceKeys, existing *DeviceIdentity) (*DeviceIdentity, error) {
|
||||
if deviceID != deviceKeys.DeviceID {
|
||||
return nil, MismatchingDeviceID
|
||||
} else if userID != deviceKeys.UserID {
|
||||
return nil, MismatchingUserID
|
||||
}
|
||||
|
||||
signingKey := deviceKeys.Keys.GetEd25519(deviceID)
|
||||
identityKey := deviceKeys.Keys.GetCurve25519(deviceID)
|
||||
if signingKey == "" {
|
||||
return nil, NoSigningKeyFound
|
||||
} else if identityKey == "" {
|
||||
return nil, NoIdentityKeyFound
|
||||
}
|
||||
|
||||
if existing != nil && existing.SigningKey != signingKey {
|
||||
return existing, MismatchingSigningKey
|
||||
}
|
||||
|
||||
ok, err := olm.VerifySignatureJSON(deviceKeys, userID, deviceID, signingKey)
|
||||
if err != nil {
|
||||
return existing, errors.Wrap(err, "failed to verify signature")
|
||||
} else if !ok {
|
||||
return existing, InvalidKeySignature
|
||||
}
|
||||
|
||||
name, ok := deviceKeys.Unsigned["device_display_name"].(string)
|
||||
if !ok {
|
||||
name = string(deviceID)
|
||||
}
|
||||
|
||||
return &DeviceIdentity{
|
||||
UserID: userID,
|
||||
DeviceID: deviceID,
|
||||
IdentityKey: identityKey,
|
||||
SigningKey: signingKey,
|
||||
Trust: TrustStateUnset,
|
||||
Name: name,
|
||||
Deleted: false,
|
||||
}, nil
|
||||
}
|
||||
136
crypto/encryptmegolm.go
Normal file
136
crypto/encryptmegolm.go
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
// Copyright (c) 2020 Tulir Asokan
|
||||
//
|
||||
// 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"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
var (
|
||||
AlreadyShared = errors.New("group session already shared")
|
||||
NoGroupSession = errors.New("no group session created")
|
||||
)
|
||||
|
||||
func (mach *OlmMachine) EncryptMegolmEvent(roomID id.RoomID, evtType event.Type, content event.Content) (*event.EncryptedEventContent, error) {
|
||||
session, err := mach.store.GetOutboundGroupSession(roomID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get outbound group session")
|
||||
} else if session == nil {
|
||||
return nil, NoGroupSession
|
||||
}
|
||||
plaintext, err := json.Marshal(&MegolmEvent{
|
||||
RoomID: roomID,
|
||||
Type: evtType,
|
||||
Content: content,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ciphertext, err := session.Encrypt(plaintext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, idKey := mach.account.IdentityKeys()
|
||||
return &event.EncryptedEventContent{
|
||||
Algorithm: id.AlgorithmMegolmV1,
|
||||
SenderKey: idKey,
|
||||
DeviceID: mach.client.DeviceID,
|
||||
SessionID: session.ID(),
|
||||
MegolmCiphertext: ciphertext,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (mach *OlmMachine) newOutboundGroupSession(roomID id.RoomID) *OutboundGroupSession {
|
||||
session := NewOutboundGroupSession()
|
||||
signingKey, idKey := mach.account.IdentityKeys()
|
||||
mach.createGroupSession(idKey, signingKey, roomID, session.ID(), session.Key())
|
||||
return session
|
||||
}
|
||||
|
||||
func (mach *OlmMachine) ShareGroupSession(roomID id.RoomID, users []id.UserID) error {
|
||||
mach.log.Trace("Sharing group session for room %s", roomID)
|
||||
session, err := mach.store.GetOutboundGroupSession(roomID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get previous outbound group session")
|
||||
}
|
||||
if session == nil || session.Expired() {
|
||||
session = mach.newOutboundGroupSession(roomID)
|
||||
} else if session.Shared {
|
||||
return AlreadyShared
|
||||
}
|
||||
|
||||
keyContent := event.Content{Parsed: &event.RoomKeyEventContent{
|
||||
Algorithm: id.AlgorithmMegolmV1,
|
||||
RoomID: roomID,
|
||||
SessionID: session.ID(),
|
||||
SessionKey: session.Key(),
|
||||
}}
|
||||
|
||||
toDevice := &mautrix.ReqSendToDevice{Messages: make(map[id.UserID]map[id.DeviceID]*event.Content)}
|
||||
|
||||
for _, userID := range users {
|
||||
devices, err := mach.store.GetDevices(userID)
|
||||
if err != nil {
|
||||
mach.log.Warn("Failed to get devices of %s", userID)
|
||||
continue
|
||||
}
|
||||
if len(devices) == 0 {
|
||||
mach.FetchKeys([]id.UserID{userID}, "")
|
||||
devices, err = mach.store.GetDevices(userID)
|
||||
if err != nil {
|
||||
mach.log.Warn("Failed to get devices of %s", userID)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
toDeviceMessages := make(map[id.DeviceID]*event.Content)
|
||||
toDevice.Messages[userID] = toDeviceMessages
|
||||
|
||||
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.store.GetLatestSession(device.IdentityKey)
|
||||
if err != nil {
|
||||
mach.log.Warn("Failed to get session for %s of %s: %v", deviceID, userID, err)
|
||||
continue
|
||||
} else if deviceSession == nil {
|
||||
// TODO we should probably be creating these sessions somewhere
|
||||
deviceSession, err = mach.createOutboundSession(userID, deviceID, device.IdentityKey, device.SigningKey)
|
||||
if err != nil {
|
||||
mach.log.Warn("Failed to create session for %s of %s: %v", deviceID, userID, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
content := mach.encryptOlmEvent(deviceSession, device, event.ToDeviceRoomKey, keyContent)
|
||||
toDeviceMessages[deviceID] = &event.Content{Parsed: content}
|
||||
session.Users[userKey] = OGSAlreadyShared
|
||||
}
|
||||
}
|
||||
|
||||
_, err = mach.client.SendToDevice(event.ToDeviceEncrypted, toDevice)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to share group session")
|
||||
}
|
||||
session.Shared = true
|
||||
return mach.store.PutOutboundGroupSession(roomID, session)
|
||||
}
|
||||
89
crypto/encryptolm.go
Normal file
89
crypto/encryptolm.go
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
// Copyright (c) 2020 Tulir Asokan
|
||||
//
|
||||
// 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"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix/crypto/olm"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
var (
|
||||
NoOneTimeKeyReceived = errors.New("no one-time key received")
|
||||
InvalidOTKSignature = errors.New("invalid signature on one-time key")
|
||||
)
|
||||
|
||||
func (mach *OlmMachine) encryptOlmEvent(session *OlmSession, recipient *DeviceIdentity, evtType event.Type, content event.Content) *event.EncryptedEventContent {
|
||||
selfSigningKey, selfIdentityKey := mach.account.IdentityKeys()
|
||||
evt := &OlmEvent{
|
||||
Sender: mach.client.UserID,
|
||||
SenderDevice: mach.client.DeviceID,
|
||||
Keys: OlmEventKeys{Ed25519: selfSigningKey},
|
||||
Recipient: recipient.UserID,
|
||||
RecipientKeys: OlmEventKeys{Ed25519: recipient.SigningKey},
|
||||
Type: evtType,
|
||||
Content: content,
|
||||
}
|
||||
plaintext, err := json.Marshal(evt)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
msgType, ciphertext := session.Encrypt(plaintext)
|
||||
return &event.EncryptedEventContent{
|
||||
Algorithm: id.AlgorithmOlmV1,
|
||||
SenderKey: selfIdentityKey,
|
||||
OlmCiphertext: event.OlmCiphertexts{
|
||||
recipient.IdentityKey: {
|
||||
Type: msgType,
|
||||
Body: string(ciphertext),
|
||||
},
|
||||
},
|
||||
}
|
||||
// TODO this probably needs to be done somewhere
|
||||
//sess, err := mach.store.GetLatestSession(recipientKey)
|
||||
//if err != nil {
|
||||
// return nil, errors.Wrap(err, "failed to get session")
|
||||
//}
|
||||
//if sess == nil {
|
||||
// sess, err = mach.createOutboundSession(recipient, recipientKey)
|
||||
// if err != nil {
|
||||
// return nil, errors.Wrap(err, "failed to create session")
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
func (mach *OlmMachine) createOutboundSession(userID id.UserID, deviceID id.DeviceID, identityKey id.Curve25519, signingKey id.Ed25519) (*OlmSession, error) {
|
||||
resp, err := mach.client.ClaimKeys(&mautrix.ReqClaimKeys{
|
||||
OneTimeKeys: mautrix.OneTimeKeysRequest{userID: {deviceID: id.KeyAlgorithmSignedCurve25519}},
|
||||
Timeout: 10 * 1000,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to claim keys")
|
||||
}
|
||||
deviceKeyID := id.NewDeviceKeyID(id.KeyAlgorithmSignedCurve25519, deviceID)
|
||||
oneTimeKey, ok := resp.OneTimeKeys[userID][deviceKeyID]
|
||||
if !ok {
|
||||
return nil, NoOneTimeKeyReceived
|
||||
}
|
||||
ok, err = olm.VerifySignatureJSON(oneTimeKey, userID, deviceID, signingKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to verify signature")
|
||||
} else if !ok {
|
||||
return nil, InvalidOTKSignature
|
||||
}
|
||||
sess, err := mach.account.NewOutboundSession(identityKey, oneTimeKey.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wrapped := wrapSession(sess)
|
||||
return wrapped, mach.store.AddSession(identityKey, wrapped)
|
||||
}
|
||||
|
|
@ -57,7 +57,11 @@ func (mach *OlmMachine) SaveAccount() {
|
|||
}
|
||||
}
|
||||
|
||||
func (mach *OlmMachine) ProcessSyncResponse(resp *mautrix.RespSync) {
|
||||
func (mach *OlmMachine) ProcessSyncResponse(resp *mautrix.RespSync, since string) {
|
||||
if len(resp.DeviceLists.Changed) > 0 {
|
||||
mach.FetchKeys(resp.DeviceLists.Changed, since)
|
||||
}
|
||||
|
||||
for _, evt := range resp.ToDevice.Events {
|
||||
mach.log.Trace("Got to-device event %s from %s", evt.Type.Type, evt.Sender)
|
||||
evt.Type.Class = event.ToDeviceEventType
|
||||
|
|
@ -82,7 +86,7 @@ func (mach *OlmMachine) ProcessSyncResponse(resp *mautrix.RespSync) {
|
|||
func (mach *OlmMachine) HandleToDeviceEvent(evt *event.Event) {
|
||||
switch evt.Content.Parsed.(type) {
|
||||
case *event.EncryptedEventContent:
|
||||
decryptedEvt, err := mach.DecryptOlmEvent(evt)
|
||||
decryptedEvt, err := mach.decryptOlmEvent(evt)
|
||||
if err != nil {
|
||||
mach.log.Error("Failed to decrypt to-device event: %v", err)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ func (a *Account) MarshalJSON() ([]byte, error) {
|
|||
}
|
||||
|
||||
func (a *Account) UnmarshalJSON(data []byte) error {
|
||||
if data[0] != '"' || data[len(data)-1] != '"' {
|
||||
if len(data) == 0 || data[0] != '"' || data[len(data)-1] != '"' {
|
||||
return InputNotJSONString
|
||||
}
|
||||
if a.int == nil {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ func InboundGroupSessionFromPickled(pickled, key []byte) (*InboundGroupSession,
|
|||
}
|
||||
|
||||
// NewInboundGroupSession creates a new inbound group session from a key
|
||||
// exported from OutboundGroupSession.SessionKey(). Returns error on failure.
|
||||
// exported from OutboundGroupSession.Key(). Returns error on failure.
|
||||
// If the sessionKey is not valid base64 the error will be
|
||||
// "OLM_INVALID_BASE64". If the session_key is invalid the error will be
|
||||
// "OLM_BAD_SESSION_KEY".
|
||||
|
|
@ -173,7 +173,7 @@ func (s *InboundGroupSession) MarshalJSON() ([]byte, error) {
|
|||
}
|
||||
|
||||
func (s *InboundGroupSession) UnmarshalJSON(data []byte) error {
|
||||
if data[0] != '"' || data[len(data)-1] != '"' {
|
||||
if len(data) == 0 || data[0] != '"' || data[len(data)-1] != '"' {
|
||||
return InputNotJSONString
|
||||
}
|
||||
if s.int == nil {
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ func (s *OutboundGroupSession) MarshalJSON() ([]byte, error) {
|
|||
}
|
||||
|
||||
func (s *OutboundGroupSession) UnmarshalJSON(data []byte) error {
|
||||
if data[0] != '"' || data[len(data)-1] != '"' {
|
||||
if len(data) == 0 || data[0] != '"' || data[len(data)-1] != '"' {
|
||||
return InputNotJSONString
|
||||
}
|
||||
if s.int == nil {
|
||||
|
|
@ -219,8 +219,8 @@ func (s *OutboundGroupSession) sessionKeyLen() uint {
|
|||
return uint(C.olm_outbound_group_session_key_length((*C.OlmOutboundGroupSession)(s.int)))
|
||||
}
|
||||
|
||||
// SessionKey returns the base64-encoded current ratchet key for this session.
|
||||
func (s *OutboundGroupSession) SessionKey() string {
|
||||
// Key returns the base64-encoded current ratchet key for this session.
|
||||
func (s *OutboundGroupSession) Key() string {
|
||||
sessionKey := make([]byte, s.sessionKeyLen())
|
||||
r := C.olm_outbound_group_session_key(
|
||||
(*C.OlmOutboundGroupSession)(s.int),
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ func (s *Session) MarshalJSON() ([]byte, error) {
|
|||
}
|
||||
|
||||
func (s *Session) UnmarshalJSON(data []byte) error {
|
||||
if data[0] != '"' || data[len(data)-1] != '"' {
|
||||
if len(data) == 0 || len(data) == 0 || data[0] != '"' || data[len(data)-1] != '"' {
|
||||
return InputNotJSONString
|
||||
}
|
||||
if s == nil {
|
||||
|
|
@ -275,9 +275,9 @@ func (s *Session) EncryptMsgType() id.OlmMsgType {
|
|||
|
||||
// Encrypt encrypts a message using the Session. Returns the encrypted message
|
||||
// as base64.
|
||||
func (s *Session) Encrypt(plaintext string) (id.OlmMsgType, string) {
|
||||
func (s *Session) Encrypt(plaintext []byte) (id.OlmMsgType, []byte) {
|
||||
if len(plaintext) == 0 {
|
||||
plaintext = " "
|
||||
panic(EmptyInput)
|
||||
}
|
||||
// Make the slice be at least length 1
|
||||
random := make([]byte, s.encryptRandomLen()+1)
|
||||
|
|
@ -289,7 +289,7 @@ func (s *Session) Encrypt(plaintext string) (id.OlmMsgType, string) {
|
|||
message := make([]byte, s.encryptMsgLen(len(plaintext)))
|
||||
r := C.olm_encrypt(
|
||||
(*C.OlmSession)(s.int),
|
||||
unsafe.Pointer(&([]byte(plaintext))[0]),
|
||||
unsafe.Pointer(&plaintext[0]),
|
||||
C.size_t(len(plaintext)),
|
||||
unsafe.Pointer(&random[0]),
|
||||
C.size_t(len(random)),
|
||||
|
|
@ -298,7 +298,7 @@ func (s *Session) Encrypt(plaintext string) (id.OlmMsgType, string) {
|
|||
if r == errorVal() {
|
||||
panic(s.lastError())
|
||||
}
|
||||
return messageType, string(message[:r])
|
||||
return messageType, message[:r]
|
||||
}
|
||||
|
||||
// Decrypt decrypts a message using the Session. Returns the the plain-text on
|
||||
|
|
|
|||
|
|
@ -107,8 +107,11 @@ var gjsonEscaper = strings.NewReplacer(
|
|||
|
||||
func gjsonPath(path ...string) string {
|
||||
var result strings.Builder
|
||||
for _, part := range path {
|
||||
for i, part := range path {
|
||||
_, _ = gjsonEscaper.WriteString(&result, part)
|
||||
if i < len(path) - 1 {
|
||||
result.WriteRune('.')
|
||||
}
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
|
|
@ -130,6 +133,10 @@ func (u *Utility) VerifySignatureJSON(obj interface{}, userID id.UserID, deviceI
|
|||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
objJSON, err = sjson.DeleteBytes(objJSON, "signatures")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
objJSONString := string(canonicaljson.CanonicalJSONAssumeValid(objJSON))
|
||||
return u.VerifySignature(objJSONString, key, sig.Str)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
|
@ -21,14 +22,33 @@ var (
|
|||
SessionExpired = errors.New("session has expired")
|
||||
)
|
||||
|
||||
type UserDevice struct {
|
||||
UserID id.UserID
|
||||
DeviceID id.DeviceID
|
||||
// OlmSessionList is a list of OlmSessions. It implements sort.Interface in a way that sorts items
|
||||
// in reverse alphabetic order, which means the newest session is first.
|
||||
type OlmSessionList []*OlmSession
|
||||
|
||||
func (o OlmSessionList) Len() int {
|
||||
return len(o)
|
||||
}
|
||||
|
||||
func (o OlmSessionList) Less(i, j int) bool {
|
||||
return strings.Compare(string(o[i].ID()), string(o[j].ID())) > 0
|
||||
}
|
||||
|
||||
func (o OlmSessionList) Swap(i, j int) {
|
||||
o[i], o[j] = o[j], o[i]
|
||||
}
|
||||
|
||||
type OlmSession struct {
|
||||
olm.Session
|
||||
ExpirationMixin
|
||||
id id.SessionID
|
||||
}
|
||||
|
||||
func (session *OlmSession) ID() id.SessionID {
|
||||
if session.id == "" {
|
||||
session.id = session.Session.ID()
|
||||
}
|
||||
return session.id
|
||||
}
|
||||
|
||||
func wrapSession(session *olm.Session) *OlmSession {
|
||||
|
|
@ -53,7 +73,7 @@ func (account *OlmAccount) NewInboundSessionFrom(senderKey id.Curve25519, cipher
|
|||
return wrapSession(session), nil
|
||||
}
|
||||
|
||||
func (session *OlmSession) Encrypt(plaintext string) (id.OlmMsgType, string) {
|
||||
func (session *OlmSession) Encrypt(plaintext []byte) (id.OlmMsgType, []byte) {
|
||||
session.UseTime = time.Now()
|
||||
return session.Session.Encrypt(plaintext)
|
||||
}
|
||||
|
|
@ -87,6 +107,19 @@ func NewInboundGroupSession(senderKey id.SenderKey, signingKey id.Ed25519, roomI
|
|||
}, nil
|
||||
}
|
||||
|
||||
type OGSState int
|
||||
|
||||
const (
|
||||
OGSNotShared OGSState = iota
|
||||
OGSAlreadyShared
|
||||
OGSIgnored
|
||||
)
|
||||
|
||||
type UserDevice struct {
|
||||
UserID id.UserID
|
||||
DeviceID id.DeviceID
|
||||
}
|
||||
|
||||
type OutboundGroupSession struct {
|
||||
olm.OutboundGroupSession
|
||||
|
||||
|
|
@ -94,9 +127,25 @@ type OutboundGroupSession struct {
|
|||
MaxMessages int
|
||||
MessageCount int
|
||||
|
||||
UsersSharedWith []UserDevice
|
||||
UsersIgnored []UserDevice
|
||||
Shared bool
|
||||
Users map[UserDevice]OGSState
|
||||
Shared bool
|
||||
}
|
||||
|
||||
func NewOutboundGroupSession() *OutboundGroupSession {
|
||||
return &OutboundGroupSession{
|
||||
OutboundGroupSession: *olm.NewOutboundGroupSession(),
|
||||
ExpirationMixin: ExpirationMixin{
|
||||
TimeMixin: TimeMixin{
|
||||
CreationTime: time.Now(),
|
||||
UseTime: time.Now(),
|
||||
},
|
||||
MaxAge: 7 * 24 * time.Hour,
|
||||
},
|
||||
// TODO take MaxMessages and MaxAge from the m.room.create event
|
||||
MaxMessages: 100,
|
||||
Shared: false,
|
||||
Users: make(map[UserDevice]OGSState),
|
||||
}
|
||||
}
|
||||
|
||||
func (ogs *OutboundGroupSession) Expired() bool {
|
||||
|
|
|
|||
103
crypto/store.go
103
crypto/store.go
|
|
@ -9,22 +9,50 @@ package crypto
|
|||
import (
|
||||
"encoding/gob"
|
||||
"os"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
type TrustState int
|
||||
|
||||
const (
|
||||
TrustStateUnset TrustState = iota
|
||||
TrustStateVerified
|
||||
TrustStateBlacklisted
|
||||
TrustStateIgnored
|
||||
)
|
||||
|
||||
type DeviceIdentity struct {
|
||||
UserID id.UserID
|
||||
DeviceID id.DeviceID
|
||||
IdentityKey id.Curve25519
|
||||
SigningKey id.Ed25519
|
||||
|
||||
Trust TrustState
|
||||
Deleted bool
|
||||
Name string
|
||||
}
|
||||
|
||||
type Store interface {
|
||||
PutAccount(*OlmAccount) error
|
||||
GetAccount() (*OlmAccount, error)
|
||||
|
||||
GetSessions(id.SenderKey) ([]*OlmSession, error)
|
||||
GetSessions(id.SenderKey) (OlmSessionList, error)
|
||||
GetLatestSession(id.SenderKey) (*OlmSession, error)
|
||||
AddSession(id.SenderKey, *OlmSession) error
|
||||
|
||||
PutGroupSession(id.RoomID, id.SenderKey, id.SessionID, *InboundGroupSession) error
|
||||
GetGroupSession(id.RoomID, id.SenderKey, id.SessionID) (*InboundGroupSession, error)
|
||||
|
||||
PutOutboundGroupSession(id.RoomID, *OutboundGroupSession) error
|
||||
GetOutboundGroupSession(id.RoomID) (*OutboundGroupSession, error)
|
||||
|
||||
ValidateMessageIndex(senderKey id.SenderKey, sessionID id.SessionID, eventID id.EventID, index uint, timestamp int64) bool
|
||||
|
||||
GetDevices(id.UserID) (map[id.DeviceID]*DeviceIdentity, error)
|
||||
PutDevices(id.UserID, map[id.DeviceID]*DeviceIdentity) error
|
||||
}
|
||||
|
||||
type messageIndexKey struct {
|
||||
|
|
@ -39,21 +67,27 @@ type messageIndexValue struct {
|
|||
}
|
||||
|
||||
type GobStore struct {
|
||||
lock sync.Mutex
|
||||
lock sync.RWMutex
|
||||
path string
|
||||
|
||||
Account *OlmAccount
|
||||
Sessions map[id.SenderKey][]*OlmSession
|
||||
GroupSessions map[id.RoomID]map[id.SenderKey]map[id.SessionID]*InboundGroupSession
|
||||
MessageIndices map[messageIndexKey]messageIndexValue
|
||||
Account *OlmAccount
|
||||
Sessions map[id.SenderKey]OlmSessionList
|
||||
GroupSessions map[id.RoomID]map[id.SenderKey]map[id.SessionID]*InboundGroupSession
|
||||
OutGroupSessions map[id.RoomID]*OutboundGroupSession
|
||||
MessageIndices map[messageIndexKey]messageIndexValue
|
||||
Devices map[id.UserID]map[id.DeviceID]*DeviceIdentity
|
||||
}
|
||||
|
||||
var _ Store = &GobStore{}
|
||||
|
||||
func NewGobStore(path string) (*GobStore, error) {
|
||||
gs := &GobStore{
|
||||
path: path,
|
||||
Sessions: make(map[id.SenderKey][]*OlmSession),
|
||||
GroupSessions: make(map[id.RoomID]map[id.SenderKey]map[id.SessionID]*InboundGroupSession),
|
||||
MessageIndices: make(map[messageIndexKey]messageIndexValue),
|
||||
path: path,
|
||||
Sessions: make(map[id.SenderKey]OlmSessionList),
|
||||
GroupSessions: make(map[id.RoomID]map[id.SenderKey]map[id.SessionID]*InboundGroupSession),
|
||||
OutGroupSessions: make(map[id.RoomID]*OutboundGroupSession),
|
||||
MessageIndices: make(map[messageIndexKey]messageIndexValue),
|
||||
Devices: make(map[id.UserID]map[id.DeviceID]*DeviceIdentity),
|
||||
}
|
||||
return gs, gs.load()
|
||||
}
|
||||
|
|
@ -93,7 +127,7 @@ func (gs *GobStore) PutAccount(account *OlmAccount) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func (gs *GobStore) GetSessions(senderKey id.SenderKey) ([]*OlmSession, error) {
|
||||
func (gs *GobStore) GetSessions(senderKey id.SenderKey) (OlmSessionList, error) {
|
||||
gs.lock.Lock()
|
||||
sessions, ok := gs.Sessions[senderKey]
|
||||
if !ok {
|
||||
|
|
@ -108,11 +142,22 @@ func (gs *GobStore) AddSession(senderKey id.SenderKey, session *OlmSession) erro
|
|||
gs.lock.Lock()
|
||||
sessions, _ := gs.Sessions[senderKey]
|
||||
gs.Sessions[senderKey] = append(sessions, session)
|
||||
sort.Sort(gs.Sessions[senderKey])
|
||||
err := gs.save()
|
||||
gs.lock.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (gs *GobStore) GetLatestSession(senderKey id.SenderKey) (*OlmSession, error) {
|
||||
gs.lock.Lock()
|
||||
sessions, ok := gs.Sessions[senderKey]
|
||||
gs.lock.Unlock()
|
||||
if !ok || len(sessions) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return sessions[0], nil
|
||||
}
|
||||
|
||||
func (gs *GobStore) getGroupSessions(roomID id.RoomID, senderKey id.SenderKey) map[id.SessionID]*InboundGroupSession {
|
||||
room, ok := gs.GroupSessions[roomID]
|
||||
if !ok {
|
||||
|
|
@ -146,6 +191,24 @@ func (gs *GobStore) GetGroupSession(roomID id.RoomID, senderKey id.SenderKey, se
|
|||
return session, nil
|
||||
}
|
||||
|
||||
func (gs *GobStore) PutOutboundGroupSession(roomID id.RoomID, session *OutboundGroupSession) error {
|
||||
gs.lock.Lock()
|
||||
gs.OutGroupSessions[roomID] = session
|
||||
err := gs.save()
|
||||
gs.lock.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (gs *GobStore) GetOutboundGroupSession(roomID id.RoomID) (*OutboundGroupSession, error) {
|
||||
gs.lock.RLock()
|
||||
session, ok := gs.OutGroupSessions[roomID]
|
||||
gs.lock.RUnlock()
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (gs *GobStore) ValidateMessageIndex(senderKey id.SenderKey, sessionID id.SessionID, eventID id.EventID, index uint, timestamp int64) bool {
|
||||
gs.lock.Lock()
|
||||
defer gs.lock.Unlock()
|
||||
|
|
@ -168,3 +231,21 @@ func (gs *GobStore) ValidateMessageIndex(senderKey id.SenderKey, sessionID id.Se
|
|||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (gs *GobStore) GetDevices(userID id.UserID) (map[id.DeviceID]*DeviceIdentity, error) {
|
||||
gs.lock.RLock()
|
||||
devices, ok := gs.Devices[userID]
|
||||
if !ok {
|
||||
devices = make(map[id.DeviceID]*DeviceIdentity)
|
||||
}
|
||||
gs.lock.RUnlock()
|
||||
return devices, nil
|
||||
}
|
||||
|
||||
func (gs *GobStore) PutDevices(userID id.UserID, devices map[id.DeviceID]*DeviceIdentity) error {
|
||||
gs.lock.Lock()
|
||||
gs.Devices[userID] = devices
|
||||
err := gs.save()
|
||||
gs.lock.Unlock()
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,12 +28,6 @@ type Tag struct {
|
|||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-direct
|
||||
type DirectChatsEventContent map[id.UserID][]id.RoomID
|
||||
|
||||
// PushRulesEventContent represents the content of a m.push_rules account data event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-push-rules
|
||||
//type PushRulesEventContent struct {
|
||||
// Global *pushrules.PushRuleset `json:"global"`
|
||||
//}
|
||||
|
||||
// FullyReadEventContent represents the content of a m.fully_read account data event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-fully-read
|
||||
type FullyReadEventContent struct {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ var TypeMap = map[Type]reflect.Type{
|
|||
StateHistoryVisibility: reflect.TypeOf(HistoryVisibilityEventContent{}),
|
||||
StateGuestAccess: reflect.TypeOf(GuestAccessEventContent{}),
|
||||
StatePinnedEvents: reflect.TypeOf(PinnedEventsEventContent{}),
|
||||
StateEncryption: reflect.TypeOf(EncryptionEventContent{}),
|
||||
|
||||
EventMessage: reflect.TypeOf(MessageEventContent{}),
|
||||
EventSticker: reflect.TypeOf(MessageEventContent{}),
|
||||
|
|
@ -42,7 +43,6 @@ var TypeMap = map[Type]reflect.Type{
|
|||
AccountDataDirectChats: reflect.TypeOf(DirectChatsEventContent{}),
|
||||
AccountDataFullyRead: reflect.TypeOf(FullyReadEventContent{}),
|
||||
AccountDataIgnoredUserList: reflect.TypeOf(IgnoredUserListEventContent{}),
|
||||
//AccountDataPushRules: reflect.TypeOf(PushRulesEventContent{}),
|
||||
|
||||
EphemeralEventTyping: reflect.TypeOf(TypingEventContent{}),
|
||||
EphemeralEventReceipt: reflect.TypeOf(ReceiptEventContent{}),
|
||||
|
|
@ -75,7 +75,7 @@ func (content *Content) MarshalJSON() ([]byte, error) {
|
|||
if content.Raw == nil {
|
||||
if content.Parsed == nil {
|
||||
if content.VeryRaw == nil {
|
||||
return []byte("null"), nil
|
||||
return []byte("{}"), nil
|
||||
}
|
||||
return content.VeryRaw, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,15 +29,15 @@ type EncryptionEventContent struct {
|
|||
type EncryptedEventContent struct {
|
||||
Algorithm id.Algorithm `json:"algorithm"`
|
||||
SenderKey id.SenderKey `json:"sender_key"`
|
||||
DeviceID id.DeviceID `json:"device_id"`
|
||||
SessionID id.SessionID `json:"session_id"`
|
||||
DeviceID id.DeviceID `json:"device_id,omitempty"`
|
||||
SessionID id.SessionID `json:"session_id,omitempty"`
|
||||
Ciphertext json.RawMessage `json:"ciphertext"`
|
||||
|
||||
MegolmCiphertext []byte `json:"-"`
|
||||
OlmCiphertext OlmCiphertexts `json:"-"`
|
||||
}
|
||||
|
||||
type OlmCiphertexts map[string]struct {
|
||||
type OlmCiphertexts map[id.Curve25519]struct {
|
||||
Body string `json:"body"`
|
||||
Type id.OlmMsgType `json:"type"`
|
||||
}
|
||||
|
|
@ -54,10 +54,10 @@ func (content *EncryptedEventContent) UnmarshalJSON(data []byte) error {
|
|||
content.OlmCiphertext = make(OlmCiphertexts)
|
||||
return json.Unmarshal(content.Ciphertext, &content.OlmCiphertext)
|
||||
case id.AlgorithmMegolmV1:
|
||||
if content.Ciphertext[0] != '"' || content.Ciphertext[len(content.Ciphertext)-1] != '"' {
|
||||
if len(content.Ciphertext) == 0 || content.Ciphertext[0] != '"' || content.Ciphertext[len(content.Ciphertext)-1] != '"' {
|
||||
return olm.InputNotJSONString
|
||||
}
|
||||
content.MegolmCiphertext = content.Ciphertext[1:len(content.Ciphertext)-1]
|
||||
content.MegolmCiphertext = content.Ciphertext[1 : len(content.Ciphertext)-1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -68,9 +68,9 @@ func (content *EncryptedEventContent) MarshalJSON() ([]byte, error) {
|
|||
case id.AlgorithmOlmV1:
|
||||
content.Ciphertext, err = json.Marshal(content.OlmCiphertext)
|
||||
case id.AlgorithmMegolmV1:
|
||||
content.Ciphertext = make([]byte, len(content.MegolmCiphertext) + 2)
|
||||
content.Ciphertext = make([]byte, len(content.MegolmCiphertext)+2)
|
||||
content.Ciphertext[0] = '"'
|
||||
content.Ciphertext[len(content.Ciphertext) - 1] = '"'
|
||||
content.Ciphertext[len(content.Ciphertext)-1] = '"'
|
||||
copy(content.Ciphertext[1:len(content.Ciphertext)-1], content.MegolmCiphertext)
|
||||
}
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -30,9 +30,9 @@ const (
|
|||
type KeyAlgorithm string
|
||||
|
||||
const (
|
||||
KeyAlgorithmCurve25519 = "curve25519"
|
||||
KeyAlgorithmEd25519 = "ed25519"
|
||||
KeyAlgorithmSignedCurve25519 = "signed_curve25519"
|
||||
KeyAlgorithmCurve25519 KeyAlgorithm = "curve25519"
|
||||
KeyAlgorithmEd25519 KeyAlgorithm = "ed25519"
|
||||
KeyAlgorithmSignedCurve25519 KeyAlgorithm = "signed_curve25519"
|
||||
)
|
||||
|
||||
// A SessionID is an arbitrary string that identifies an Olm or Megolm session.
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import (
|
|||
"maunium.net/go/mautrix/event"
|
||||
)
|
||||
|
||||
// EventContent represents the content of a m.push_rules account data event.
|
||||
// https://matrix.org/docs/spec/client_server/r0.6.0#m-push-rules
|
||||
type EventContent struct {
|
||||
Ruleset *PushRuleset `json:"global"`
|
||||
}
|
||||
|
|
|
|||
50
requests.go
50
requests.go
|
|
@ -102,7 +102,7 @@ type ReqTyping struct {
|
|||
}
|
||||
|
||||
type ReqPresence struct {
|
||||
Presence string `json:"presence"`
|
||||
Presence event.Presence `json:"presence"`
|
||||
}
|
||||
|
||||
type ReqAliasCreate struct {
|
||||
|
|
@ -110,7 +110,7 @@ type ReqAliasCreate struct {
|
|||
}
|
||||
|
||||
type OneTimeKey struct {
|
||||
Key string `json:"key"`
|
||||
Key id.Curve25519 `json:"key"`
|
||||
IsSigned bool `json:"-"`
|
||||
Signatures Signatures `json:"signatures,omitempty"`
|
||||
Unsigned map[string]interface{} `json:"unsigned,omitempty"`
|
||||
|
|
@ -121,7 +121,7 @@ type serializableOTK OneTimeKey
|
|||
func (otk *OneTimeKey) UnmarshalJSON(data []byte) error {
|
||||
err := json.Unmarshal(data, (*serializableOTK)(otk))
|
||||
if err != nil {
|
||||
var key string
|
||||
var key id.Curve25519
|
||||
err := json.Unmarshal(data, &key)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -150,25 +150,53 @@ type ReqUploadKeys struct {
|
|||
}
|
||||
|
||||
type DeviceKeys struct {
|
||||
UserID id.UserID `json:"user_id"`
|
||||
DeviceID id.DeviceID `json:"device_id"`
|
||||
Algorithms []id.Algorithm `json:"algorithms"`
|
||||
Keys map[id.DeviceKeyID]string `json:"keys"`
|
||||
Signatures Signatures `json:"signatures"`
|
||||
Unsigned map[string]interface{} `json:"unsigned,omitempty"`
|
||||
UserID id.UserID `json:"user_id"`
|
||||
DeviceID id.DeviceID `json:"device_id"`
|
||||
Algorithms []id.Algorithm `json:"algorithms"`
|
||||
Keys KeyMap `json:"keys"`
|
||||
Signatures Signatures `json:"signatures"`
|
||||
Unsigned map[string]interface{} `json:"unsigned,omitempty"`
|
||||
}
|
||||
|
||||
type KeyMap map[id.DeviceKeyID]string
|
||||
|
||||
func (km KeyMap) GetEd25519(deviceID id.DeviceID) id.Ed25519 {
|
||||
val, ok := km[id.NewDeviceKeyID(id.KeyAlgorithmEd25519, deviceID)]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return id.Ed25519(val)
|
||||
}
|
||||
|
||||
func (km KeyMap) GetCurve25519(deviceID id.DeviceID) id.Curve25519 {
|
||||
val, ok := km[id.NewDeviceKeyID(id.KeyAlgorithmCurve25519, deviceID)]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return id.Curve25519(val)
|
||||
}
|
||||
|
||||
type Signatures map[id.UserID]map[id.DeviceKeyID]string
|
||||
|
||||
type ReqQueryKeys struct {
|
||||
DeviceKeys map[id.UserID][]id.DeviceID `json:"device_keys"`
|
||||
DeviceKeys DeviceKeysRequest `json:"device_keys"`
|
||||
|
||||
Timeout int64 `json:"timeout,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
}
|
||||
|
||||
type DeviceKeysRequest map[id.UserID]DeviceIDList
|
||||
|
||||
type DeviceIDList []id.DeviceID
|
||||
|
||||
type ReqClaimKeys struct {
|
||||
OneTimeKeys map[id.UserID]map[id.DeviceID]string `json:"one_time_keys"`
|
||||
OneTimeKeys OneTimeKeysRequest `json:"one_time_keys"`
|
||||
|
||||
Timeout int64 `json:"timeout,omitempty"`
|
||||
}
|
||||
|
||||
type OneTimeKeysRequest map[id.UserID]map[id.DeviceID]id.KeyAlgorithm
|
||||
|
||||
type ReqSendToDevice struct {
|
||||
Messages map[id.UserID]map[id.DeviceID]*event.Content `json:"messages"`
|
||||
}
|
||||
|
|
@ -240,12 +240,12 @@ type RespUploadKeys struct {
|
|||
}
|
||||
|
||||
type RespQueryKeys struct {
|
||||
Failures map[string]map[string]interface{} `json:"failures"`
|
||||
Failures map[string]interface{} `json:"failures"`
|
||||
DeviceKeys map[id.UserID]map[id.DeviceID]DeviceKeys `json:"device_keys"`
|
||||
}
|
||||
|
||||
type RespClaimKeys struct {
|
||||
Failures map[string]map[string]interface{} `json:"failures"`
|
||||
Failures map[string]interface{} `json:"failures"`
|
||||
OneTimeKeys map[id.UserID]map[id.DeviceKeyID]OneTimeKey `json:"one_time_keys"`
|
||||
}
|
||||
|
||||
|
|
@ -253,3 +253,5 @@ type RespKeyChanges struct {
|
|||
Changed []id.UserID `json:"changed"`
|
||||
Left []id.UserID `json:"left"`
|
||||
}
|
||||
|
||||
type RespSendToDevice struct{}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue