// 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" "fmt" "maunium.net/go/mautrix" "maunium.net/go/mautrix/crypto/olm" "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/id" ) func (mach *OlmMachine) encryptOlmEvent(session *OlmSession, recipient *DeviceIdentity, evtType event.Type, content event.Content) *event.EncryptedEventContent { evt := &DecryptedOlmEvent{ Sender: mach.Client.UserID, SenderDevice: mach.Client.DeviceID, Keys: OlmEventKeys{Ed25519: mach.account.SigningKey()}, Recipient: recipient.UserID, RecipientKeys: OlmEventKeys{Ed25519: recipient.SigningKey}, Type: evtType, Content: content, } plaintext, err := json.Marshal(evt) if err != nil { panic(err) } mach.Log.Trace("Encrypting olm message for %s with session %s: %s", recipient.IdentityKey, session.ID(), session.Describe()) msgType, ciphertext := session.Encrypt(plaintext) err = mach.CryptoStore.UpdateSession(recipient.IdentityKey, session) if err != nil { mach.Log.Warn("Failed to update olm session in crypto store after encrypting: %v", err) } return &event.EncryptedEventContent{ Algorithm: id.AlgorithmOlmV1, SenderKey: mach.account.IdentityKey(), OlmCiphertext: event.OlmCiphertexts{ recipient.IdentityKey: { Type: msgType, Body: string(ciphertext), }, }, } } func (mach *OlmMachine) shouldCreateNewSession(identityKey id.IdentityKey) bool { if !mach.CryptoStore.HasSession(identityKey) { return true } mach.devicesToUnwedgeLock.Lock() _, shouldUnwedge := mach.devicesToUnwedge[identityKey] if shouldUnwedge { delete(mach.devicesToUnwedge, identityKey) } mach.devicesToUnwedgeLock.Unlock() return shouldUnwedge } func (mach *OlmMachine) createOutboundSessions(input map[id.UserID]map[id.DeviceID]*DeviceIdentity) error { request := make(mautrix.OneTimeKeysRequest) for userID, devices := range input { request[userID] = make(map[id.DeviceID]id.KeyAlgorithm) for deviceID, identity := range devices { if mach.shouldCreateNewSession(identity.IdentityKey) { request[userID][deviceID] = id.KeyAlgorithmSignedCurve25519 } } if len(request[userID]) == 0 { delete(request, userID) } } if len(request) == 0 { return nil } resp, err := mach.Client.ClaimKeys(&mautrix.ReqClaimKeys{ OneTimeKeys: request, Timeout: 10 * 1000, }) if err != nil { return fmt.Errorf("failed to claim keys: %w", err) } for userID, user := range resp.OneTimeKeys { for deviceID, oneTimeKeys := range user { var oneTimeKey mautrix.OneTimeKey var keyID id.KeyID for keyID, oneTimeKey = range oneTimeKeys { break } keyAlg, keyIndex := keyID.Parse() if keyAlg != id.KeyAlgorithmSignedCurve25519 { mach.Log.Warn("Unexpected key ID algorithm in one-time key response for %s of %s: %s", deviceID, userID, keyID) continue } identity := input[userID][deviceID] if ok, err := olm.VerifySignatureJSON(oneTimeKey, userID, deviceID.String(), identity.SigningKey); err != nil { mach.Log.Error("Failed to verify signature for %s of %s: %v", deviceID, userID, err) } else if !ok { mach.Log.Warn("Invalid signature for %s of %s", deviceID, userID) } else if sess, err := mach.account.Internal.NewOutboundSession(identity.IdentityKey, oneTimeKey.Key); err != nil { mach.Log.Error("Failed to create outbound session for %s of %s: %v", deviceID, userID, err) } else { wrapped := wrapSession(sess) err = mach.CryptoStore.AddSession(identity.IdentityKey, wrapped) if err != nil { mach.Log.Error("Failed to store created session for %s of %s: %v", deviceID, userID, err) } else { mach.Log.Debug("Created new Olm session with %s/%s (OTK ID: %d)", userID, deviceID, keyIndex) } } } } return nil }