2024-08-01 19:17:05 +00:00
|
|
|
//go:build e2ee
|
2021-07-16 14:41:08 +00:00
|
|
|
// +build e2ee
|
|
|
|
|
2021-07-16 13:33:51 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2024-08-01 19:17:05 +00:00
|
|
|
"fmt"
|
2021-07-16 13:33:51 +00:00
|
|
|
"strings"
|
|
|
|
|
2024-08-01 19:17:05 +00:00
|
|
|
lm "github.com/hrfee/jfa-go/logmessages"
|
2021-07-16 13:33:51 +00:00
|
|
|
"maunium.net/go/mautrix"
|
|
|
|
"maunium.net/go/mautrix/crypto"
|
|
|
|
"maunium.net/go/mautrix/event"
|
|
|
|
"maunium.net/go/mautrix/id"
|
|
|
|
)
|
|
|
|
|
2021-07-16 16:11:17 +00:00
|
|
|
type Crypto struct {
|
|
|
|
cryptoStore *crypto.GobStore
|
|
|
|
olm *crypto.OlmMachine
|
|
|
|
}
|
|
|
|
|
2021-07-16 14:41:08 +00:00
|
|
|
func MatrixE2EE() bool { return true }
|
|
|
|
|
2021-07-16 13:33:51 +00:00
|
|
|
type stateStore struct {
|
|
|
|
isEncrypted *map[id.RoomID]bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *stateStore) IsEncrypted(roomID id.RoomID) bool {
|
|
|
|
// encrypted, ok := (*m.isEncrypted)[roomID]
|
|
|
|
// return ok && encrypted
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *stateStore) GetEncryptionEvent(roomID id.RoomID) *event.EncryptionEventContent {
|
|
|
|
return &event.EncryptionEventContent{
|
|
|
|
Algorithm: id.AlgorithmMegolmV1,
|
|
|
|
RotationPeriodMillis: 7 * 24 * 60 * 60 * 1000,
|
|
|
|
RotationPeriodMessages: 100,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Users are assumed to only have one common channel with the bot, so we can stub this out.
|
|
|
|
func (m *stateStore) FindSharedRooms(userID id.UserID) []id.RoomID {
|
|
|
|
// for _, user := range m.app.storage.matrix {
|
|
|
|
// if id.UserID(user.UserID) == userID {
|
|
|
|
// return []id.RoomID{id.RoomID(user.RoomID)}
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
return []id.RoomID{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *MatrixDaemon) getUserIDs(roomID id.RoomID) (list []id.UserID, err error) {
|
|
|
|
members, err := d.bot.JoinedMembers(roomID)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
list = make([]id.UserID, len(members.Joined))
|
|
|
|
i := 0
|
|
|
|
for id := range members.Joined {
|
|
|
|
list[i] = id
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
type olmLogger struct {
|
|
|
|
app *appContext
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o olmLogger) Error(message string, args ...interface{}) {
|
2024-08-01 19:17:05 +00:00
|
|
|
o.app.err.Printf(lm.MatrixOlmLog, fmt.Sprintf(message, args))
|
2021-07-16 13:33:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (o olmLogger) Warn(message string, args ...interface{}) {
|
2024-08-01 19:17:05 +00:00
|
|
|
o.app.info.Printf(lm.MatrixOlmLog, fmt.Sprintf(message, args))
|
2021-07-16 13:33:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (o olmLogger) Debug(message string, args ...interface{}) {
|
2024-08-01 19:17:05 +00:00
|
|
|
o.app.debug.Printf(lm.MatrixOlmLog, fmt.Sprintf(message, args))
|
2021-07-16 13:33:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (o olmLogger) Trace(message string, args ...interface{}) {
|
|
|
|
if strings.HasPrefix(message, "Got membership state event") {
|
|
|
|
return
|
|
|
|
}
|
2024-08-01 19:17:05 +00:00
|
|
|
o.app.debug.Printf(lm.MatrixOlmTracelog, fmt.Sprintf(message, args))
|
2021-07-16 13:33:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func InitMatrixCrypto(d *MatrixDaemon) (err error) {
|
|
|
|
d.Encryption = d.app.config.Section("matrix").Key("encryption").MustBool(false)
|
|
|
|
if !d.Encryption {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for _, user := range d.app.storage.matrix {
|
|
|
|
d.isEncrypted[id.RoomID(user.RoomID)] = user.Encrypted
|
|
|
|
}
|
|
|
|
dbPath := d.app.config.Section("files").Key("matrix_sql").String()
|
|
|
|
// If the db is maintained after restart, element reports "The secure channel with the sender was corrupted" when sending a message from the bot.
|
|
|
|
// This obviously isn't right, but it seems to work.
|
|
|
|
// Since its not really used anyway, just use the deprecated GobStore. This reduces cgo usage anyway.
|
|
|
|
var cryptoStore *crypto.GobStore
|
|
|
|
cryptoStore, err = crypto.NewGobStore(dbPath)
|
|
|
|
// d.db, err = sql.Open("sqlite3", dbPath)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
olmLog := &olmLogger{d.app}
|
|
|
|
// deviceID := "jfa-go" + commit
|
|
|
|
// cryptoStore := crypto.NewSQLCryptoStore(d.db, "sqlite3", string(d.userID)+deviceID, id.DeviceID(deviceID), []byte("jfa-go"), olmLog)
|
|
|
|
// err = cryptoStore.CreateTables()
|
|
|
|
// if err != nil {
|
|
|
|
// return
|
|
|
|
// }
|
2021-07-16 16:11:17 +00:00
|
|
|
olm := crypto.NewOlmMachine(d.bot, olmLog, cryptoStore, &stateStore{&d.isEncrypted})
|
|
|
|
olm.AllowUnverifiedDevices = true
|
2021-07-20 14:34:39 +00:00
|
|
|
err = olm.Load()
|
2021-07-16 13:33:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2021-07-16 16:11:17 +00:00
|
|
|
d.crypto = Crypto{
|
|
|
|
cryptoStore: cryptoStore,
|
|
|
|
olm: olm,
|
|
|
|
}
|
2021-07-16 13:33:51 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func HandleSyncerCrypto(startTime int64, d *MatrixDaemon, syncer *mautrix.DefaultSyncer) {
|
|
|
|
if !d.Encryption {
|
|
|
|
return
|
|
|
|
}
|
2021-07-16 14:41:08 +00:00
|
|
|
syncer.OnSync(func(resp *mautrix.RespSync, since string) bool {
|
2021-07-16 16:11:17 +00:00
|
|
|
d.crypto.olm.ProcessSyncResponse(resp, since)
|
2021-07-16 13:33:51 +00:00
|
|
|
return true
|
|
|
|
})
|
2021-07-16 14:41:08 +00:00
|
|
|
syncer.OnEventType(event.StateMember, func(source mautrix.EventSource, evt *event.Event) {
|
2021-07-16 16:11:17 +00:00
|
|
|
d.crypto.olm.HandleMemberEvent(evt)
|
2021-07-16 13:33:51 +00:00
|
|
|
// if evt.Content.AsMember().Membership != event.MembershipJoin {
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
// userIDs, err := d.getUserIDs(evt.RoomID)
|
|
|
|
// if err != nil || len(userIDs) < 2 {
|
|
|
|
// fmt.Println("FS", err)
|
|
|
|
// return
|
|
|
|
// }
|
2021-07-16 16:11:17 +00:00
|
|
|
// err = d.crypto.olm.ShareGroupSession(evt.RoomID, userIDs)
|
2021-07-16 13:33:51 +00:00
|
|
|
// if err != nil {
|
|
|
|
// fmt.Println("FS", err)
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
})
|
2021-07-16 14:41:08 +00:00
|
|
|
syncer.OnEventType(event.EventEncrypted, func(source mautrix.EventSource, evt *event.Event) {
|
2021-07-16 13:33:51 +00:00
|
|
|
if evt.Timestamp < startTime {
|
|
|
|
return
|
|
|
|
}
|
2021-07-16 16:11:17 +00:00
|
|
|
decrypted, err := d.crypto.olm.DecryptMegolmEvent(evt)
|
2021-07-20 14:34:39 +00:00
|
|
|
// if strings.Contains(err.Error(), crypto.NoSessionFound.Error()) {
|
|
|
|
// d.app.err.Printf("Failed to decrypt Matrix message: no session found")
|
|
|
|
// return
|
|
|
|
// }
|
2021-07-16 13:33:51 +00:00
|
|
|
if err != nil {
|
2024-08-01 19:17:05 +00:00
|
|
|
d.app.err.Printf(lm.FailedDecryptMatrixMessage, err)
|
2021-07-16 13:33:51 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
d.handleMessage(source, decrypted)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func CryptoShutdown(d *MatrixDaemon) {
|
|
|
|
if d.Encryption {
|
2021-07-16 16:11:17 +00:00
|
|
|
d.crypto.olm.FlushStore()
|
2021-07-16 13:33:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func EncryptRoom(d *MatrixDaemon, room *mautrix.RespCreateRoom, userID id.UserID) (encrypted bool) {
|
|
|
|
if !d.Encryption {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
_, err := d.bot.SendStateEvent(room.RoomID, event.StateEncryption, "", &event.EncryptionEventContent{
|
|
|
|
Algorithm: id.AlgorithmMegolmV1,
|
|
|
|
RotationPeriodMillis: 7 * 24 * 60 * 60 * 1000,
|
|
|
|
RotationPeriodMessages: 100,
|
|
|
|
})
|
|
|
|
if err == nil {
|
|
|
|
encrypted = true
|
|
|
|
} else {
|
2024-08-01 19:17:05 +00:00
|
|
|
d.app.debug.Printf(lm.FailedEnableMatrixEncryption, room.RoomID, err)
|
2021-07-16 13:33:51 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
d.isEncrypted[room.RoomID] = encrypted
|
|
|
|
var userIDs []id.UserID
|
|
|
|
userIDs, err = d.getUserIDs(room.RoomID)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
userIDs = append(userIDs, userID)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func SendEncrypted(d *MatrixDaemon, content *event.MessageEventContent, roomID id.RoomID) (err error) {
|
|
|
|
if !d.Encryption {
|
|
|
|
err = d.send(content, roomID)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var encrypted *event.EncryptedEventContent
|
2021-07-16 16:11:17 +00:00
|
|
|
encrypted, err = d.crypto.olm.EncryptMegolmEvent(roomID, event.EventMessage, content)
|
2021-07-16 13:33:51 +00:00
|
|
|
if err == crypto.SessionExpired || err == crypto.SessionNotShared || err == crypto.NoGroupSession {
|
2021-07-16 16:11:17 +00:00
|
|
|
// err = d.crypto.olm.ShareGroupSession(id.RoomID(user.RoomID), []id.UserID{id.UserID(user.UserID), d.userID})
|
2021-07-16 13:33:51 +00:00
|
|
|
var userIDs []id.UserID
|
|
|
|
userIDs, err = d.getUserIDs(roomID)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2021-07-16 16:11:17 +00:00
|
|
|
err = d.crypto.olm.ShareGroupSession(roomID, userIDs)
|
2021-07-16 13:33:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2021-07-16 16:11:17 +00:00
|
|
|
encrypted, err = d.crypto.olm.EncryptMegolmEvent(roomID, event.EventMessage, content)
|
2021-07-16 13:33:51 +00:00
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
_, err = d.bot.SendMessageEvent(roomID, event.EventEncrypted, &event.Content{Parsed: encrypted})
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|