diff options
author | RaindropsSys <contact@minteck.org> | 2023-04-24 14:03:36 +0200 |
---|---|---|
committer | RaindropsSys <contact@minteck.org> | 2023-04-24 14:03:36 +0200 |
commit | 633c92eae865e957121e08de634aeee11a8b3992 (patch) | |
tree | 09d881bee1dae0b6eee49db1dfaf0f500240606c /includes/external/matrix/node_modules/matrix-js-sdk/lib/crypto/olmlib.js | |
parent | c4657e4509733699c0f26a3c900bab47e915d5a0 (diff) | |
download | pluralconnect-633c92eae865e957121e08de634aeee11a8b3992.tar.gz pluralconnect-633c92eae865e957121e08de634aeee11a8b3992.tar.bz2 pluralconnect-633c92eae865e957121e08de634aeee11a8b3992.zip |
Updated 18 files, added 1692 files and deleted includes/system/compare.inc (automated)
Diffstat (limited to 'includes/external/matrix/node_modules/matrix-js-sdk/lib/crypto/olmlib.js')
-rw-r--r-- | includes/external/matrix/node_modules/matrix-js-sdk/lib/crypto/olmlib.js | 467 |
1 files changed, 467 insertions, 0 deletions
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/lib/crypto/olmlib.js b/includes/external/matrix/node_modules/matrix-js-sdk/lib/crypto/olmlib.js new file mode 100644 index 0000000..0b5b14c --- /dev/null +++ b/includes/external/matrix/node_modules/matrix-js-sdk/lib/crypto/olmlib.js @@ -0,0 +1,467 @@ +"use strict"; + +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.OLM_ALGORITHM = exports.MEGOLM_BACKUP_ALGORITHM = exports.MEGOLM_ALGORITHM = void 0; +exports.decodeBase64 = decodeBase64; +exports.encodeBase64 = encodeBase64; +exports.encodeUnpaddedBase64 = encodeUnpaddedBase64; +exports.encryptMessageForDevice = encryptMessageForDevice; +exports.ensureOlmSessionsForDevices = ensureOlmSessionsForDevices; +exports.getExistingOlmSessions = getExistingOlmSessions; +exports.isOlmEncrypted = isOlmEncrypted; +exports.pkSign = pkSign; +exports.pkVerify = pkVerify; +exports.verifySignature = verifySignature; +var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); +var _anotherJson = _interopRequireDefault(require("another-json")); +var _logger = require("../logger"); +var _event = require("../@types/event"); +var _utils = require("../utils"); +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } +var Algorithm; +/** + * matrix algorithm tag for olm + */ +(function (Algorithm) { + Algorithm["Olm"] = "m.olm.v1.curve25519-aes-sha2"; + Algorithm["Megolm"] = "m.megolm.v1.aes-sha2"; + Algorithm["MegolmBackup"] = "m.megolm_backup.v1.curve25519-aes-sha2"; +})(Algorithm || (Algorithm = {})); +const OLM_ALGORITHM = Algorithm.Olm; + +/** + * matrix algorithm tag for megolm + */ +exports.OLM_ALGORITHM = OLM_ALGORITHM; +const MEGOLM_ALGORITHM = Algorithm.Megolm; + +/** + * matrix algorithm tag for megolm backups + */ +exports.MEGOLM_ALGORITHM = MEGOLM_ALGORITHM; +const MEGOLM_BACKUP_ALGORITHM = Algorithm.MegolmBackup; +exports.MEGOLM_BACKUP_ALGORITHM = MEGOLM_BACKUP_ALGORITHM; +/** + * Encrypt an event payload for an Olm device + * + * @param resultsObject - The `ciphertext` property + * of the m.room.encrypted event to which to add our result + * + * @param olmDevice - olm.js wrapper + * @param payloadFields - fields to include in the encrypted payload + * + * Returns a promise which resolves (to undefined) when the payload + * has been encrypted into `resultsObject` + */ +async function encryptMessageForDevice(resultsObject, ourUserId, ourDeviceId, olmDevice, recipientUserId, recipientDevice, payloadFields) { + const deviceKey = recipientDevice.getIdentityKey(); + const sessionId = await olmDevice.getSessionIdForDevice(deviceKey); + if (sessionId === null) { + // If we don't have a session for a device then + // we can't encrypt a message for it. + _logger.logger.log(`[olmlib.encryptMessageForDevice] Unable to find Olm session for device ` + `${recipientUserId}:${recipientDevice.deviceId}`); + return; + } + _logger.logger.log(`[olmlib.encryptMessageForDevice] Using Olm session ${sessionId} for device ` + `${recipientUserId}:${recipientDevice.deviceId}`); + const payload = _objectSpread({ + sender: ourUserId, + // TODO this appears to no longer be used whatsoever + sender_device: ourDeviceId, + // Include the Ed25519 key so that the recipient knows what + // device this message came from. + // We don't need to include the curve25519 key since the + // recipient will already know this from the olm headers. + // When combined with the device keys retrieved from the + // homeserver signed by the ed25519 key this proves that + // the curve25519 key and the ed25519 key are owned by + // the same device. + keys: { + ed25519: olmDevice.deviceEd25519Key + }, + // include the recipient device details in the payload, + // to avoid unknown key attacks, per + // https://github.com/vector-im/vector-web/issues/2483 + recipient: recipientUserId, + recipient_keys: { + ed25519: recipientDevice.getFingerprint() + } + }, payloadFields); + + // TODO: technically, a bunch of that stuff only needs to be included for + // pre-key messages: after that, both sides know exactly which devices are + // involved in the session. If we're looking to reduce data transfer in the + // future, we could elide them for subsequent messages. + + resultsObject[deviceKey] = await olmDevice.encryptMessage(deviceKey, sessionId, JSON.stringify(payload)); +} +/** + * Get the existing olm sessions for the given devices, and the devices that + * don't have olm sessions. + * + * + * + * @param devicesByUser - map from userid to list of devices to ensure sessions for + * + * @returns resolves to an array. The first element of the array is a + * a map of user IDs to arrays of deviceInfo, representing the devices that + * don't have established olm sessions. The second element of the array is + * a map from userId to deviceId to {@link OlmSessionResult} + */ +async function getExistingOlmSessions(olmDevice, baseApis, devicesByUser) { + // map user Id → DeviceInfo[] + const devicesWithoutSession = new _utils.MapWithDefault(() => []); + // map user Id → device Id → IExistingOlmSession + const sessions = new _utils.MapWithDefault(() => new Map()); + const promises = []; + for (const [userId, devices] of Object.entries(devicesByUser)) { + for (const deviceInfo of devices) { + const deviceId = deviceInfo.deviceId; + const key = deviceInfo.getIdentityKey(); + promises.push((async () => { + const sessionId = await olmDevice.getSessionIdForDevice(key, true); + if (sessionId === null) { + devicesWithoutSession.getOrCreate(userId).push(deviceInfo); + } else { + sessions.getOrCreate(userId).set(deviceId, { + device: deviceInfo, + sessionId: sessionId + }); + } + })()); + } + } + await Promise.all(promises); + return [devicesWithoutSession, sessions]; +} + +/** + * Try to make sure we have established olm sessions for the given devices. + * + * @param devicesByUser - map from userid to list of devices to ensure sessions for + * + * @param force - If true, establish a new session even if one + * already exists. + * + * @param otkTimeout - The timeout in milliseconds when requesting + * one-time keys for establishing new olm sessions. + * + * @param failedServers - An array to fill with remote servers that + * failed to respond to one-time-key requests. + * + * @param log - A possibly customised log + * + * @returns resolves once the sessions are complete, to + * an Object mapping from userId to deviceId to + * {@link OlmSessionResult} + */ +async function ensureOlmSessionsForDevices(olmDevice, baseApis, devicesByUser, force = false, otkTimeout, failedServers, log = _logger.logger) { + const devicesWithoutSession = [ + // [userId, deviceId], ... + ]; + // map user Id → device Id → IExistingOlmSession + const result = new Map(); + // map device key → resolve session fn + const resolveSession = new Map(); + + // Mark all sessions this task intends to update as in progress. It is + // important to do this for all devices this task cares about in a single + // synchronous operation, as otherwise it is possible to have deadlocks + // where multiple tasks wait indefinitely on another task to update some set + // of common devices. + for (const devices of devicesByUser.values()) { + for (const deviceInfo of devices) { + const key = deviceInfo.getIdentityKey(); + if (key === olmDevice.deviceCurve25519Key) { + // We don't start sessions with ourself, so there's no need to + // mark it in progress. + continue; + } + if (!olmDevice.sessionsInProgress[key]) { + // pre-emptively mark the session as in-progress to avoid race + // conditions. If we find that we already have a session, then + // we'll resolve + olmDevice.sessionsInProgress[key] = new Promise(resolve => { + resolveSession.set(key, v => { + delete olmDevice.sessionsInProgress[key]; + resolve(v); + }); + }); + } + } + } + for (const [userId, devices] of devicesByUser) { + const resultDevices = new Map(); + result.set(userId, resultDevices); + for (const deviceInfo of devices) { + const deviceId = deviceInfo.deviceId; + const key = deviceInfo.getIdentityKey(); + if (key === olmDevice.deviceCurve25519Key) { + // We should never be trying to start a session with ourself. + // Apart from talking to yourself being the first sign of madness, + // olm sessions can't do this because they get confused when + // they get a message and see that the 'other side' has started a + // new chain when this side has an active sender chain. + // If you see this message being logged in the wild, we should find + // the thing that is trying to send Olm messages to itself and fix it. + log.info("Attempted to start session with ourself! Ignoring"); + // We must fill in the section in the return value though, as callers + // expect it to be there. + resultDevices.set(deviceId, { + device: deviceInfo, + sessionId: null + }); + continue; + } + const forWhom = `for ${key} (${userId}:${deviceId})`; + const sessionId = await olmDevice.getSessionIdForDevice(key, !!resolveSession.get(key), log); + const resolveSessionFn = resolveSession.get(key); + if (sessionId !== null && resolveSessionFn) { + // we found a session, but we had marked the session as + // in-progress, so resolve it now, which will unmark it and + // unblock anything that was waiting + resolveSessionFn(); + } + if (sessionId === null || force) { + if (force) { + log.info(`Forcing new Olm session ${forWhom}`); + } else { + log.info(`Making new Olm session ${forWhom}`); + } + devicesWithoutSession.push([userId, deviceId]); + } + resultDevices.set(deviceId, { + device: deviceInfo, + sessionId: sessionId + }); + } + } + if (devicesWithoutSession.length === 0) { + return result; + } + const oneTimeKeyAlgorithm = "signed_curve25519"; + let res; + let taskDetail = `one-time keys for ${devicesWithoutSession.length} devices`; + try { + log.debug(`Claiming ${taskDetail}`); + res = await baseApis.claimOneTimeKeys(devicesWithoutSession, oneTimeKeyAlgorithm, otkTimeout); + log.debug(`Claimed ${taskDetail}`); + } catch (e) { + for (const resolver of resolveSession.values()) { + resolver(); + } + log.log(`Failed to claim ${taskDetail}`, e, devicesWithoutSession); + throw e; + } + if (failedServers && "failures" in res) { + failedServers.push(...Object.keys(res.failures)); + } + const otkResult = res.one_time_keys || {}; + const promises = []; + for (const [userId, devices] of devicesByUser) { + const userRes = otkResult[userId] || {}; + for (const deviceInfo of devices) { + var _result$get, _result$get$get; + const deviceId = deviceInfo.deviceId; + const key = deviceInfo.getIdentityKey(); + if (key === olmDevice.deviceCurve25519Key) { + // We've already logged about this above. Skip here too + // otherwise we'll log saying there are no one-time keys + // which will be confusing. + continue; + } + if ((_result$get = result.get(userId)) !== null && _result$get !== void 0 && (_result$get$get = _result$get.get(deviceId)) !== null && _result$get$get !== void 0 && _result$get$get.sessionId && !force) { + // we already have a result for this device + continue; + } + const deviceRes = userRes[deviceId] || {}; + let oneTimeKey = null; + for (const keyId in deviceRes) { + if (keyId.indexOf(oneTimeKeyAlgorithm + ":") === 0) { + oneTimeKey = deviceRes[keyId]; + } + } + if (!oneTimeKey) { + var _resolveSession$get; + log.warn(`No one-time keys (alg=${oneTimeKeyAlgorithm}) ` + `for device ${userId}:${deviceId}`); + (_resolveSession$get = resolveSession.get(key)) === null || _resolveSession$get === void 0 ? void 0 : _resolveSession$get(); + continue; + } + promises.push(_verifyKeyAndStartSession(olmDevice, oneTimeKey, userId, deviceInfo).then(sid => { + var _resolveSession$get2, _result$get2; + (_resolveSession$get2 = resolveSession.get(key)) === null || _resolveSession$get2 === void 0 ? void 0 : _resolveSession$get2(sid !== null && sid !== void 0 ? sid : undefined); + const deviceInfo = (_result$get2 = result.get(userId)) === null || _result$get2 === void 0 ? void 0 : _result$get2.get(deviceId); + if (deviceInfo) deviceInfo.sessionId = sid; + }, e => { + var _resolveSession$get3; + (_resolveSession$get3 = resolveSession.get(key)) === null || _resolveSession$get3 === void 0 ? void 0 : _resolveSession$get3(); + throw e; + })); + } + } + taskDetail = `Olm sessions for ${promises.length} devices`; + log.debug(`Starting ${taskDetail}`); + await Promise.all(promises); + log.debug(`Started ${taskDetail}`); + return result; +} +async function _verifyKeyAndStartSession(olmDevice, oneTimeKey, userId, deviceInfo) { + const deviceId = deviceInfo.deviceId; + try { + await verifySignature(olmDevice, oneTimeKey, userId, deviceId, deviceInfo.getFingerprint()); + } catch (e) { + _logger.logger.error("Unable to verify signature on one-time key for device " + userId + ":" + deviceId + ":", e); + return null; + } + let sid; + try { + sid = await olmDevice.createOutboundSession(deviceInfo.getIdentityKey(), oneTimeKey.key); + } catch (e) { + // possibly a bad key + _logger.logger.error("Error starting olm session with device " + userId + ":" + deviceId + ": " + e); + return null; + } + _logger.logger.log("Started new olm sessionid " + sid + " for device " + userId + ":" + deviceId); + return sid; +} +/** + * Verify the signature on an object + * + * @param olmDevice - olm wrapper to use for verify op + * + * @param obj - object to check signature on. + * + * @param signingUserId - ID of the user whose signature should be checked + * + * @param signingDeviceId - ID of the device whose signature should be checked + * + * @param signingKey - base64-ed ed25519 public key + * + * Returns a promise which resolves (to undefined) if the the signature is good, + * or rejects with an Error if it is bad. + */ +async function verifySignature(olmDevice, obj, signingUserId, signingDeviceId, signingKey) { + const signKeyId = "ed25519:" + signingDeviceId; + const signatures = obj.signatures || {}; + const userSigs = signatures[signingUserId] || {}; + const signature = userSigs[signKeyId]; + if (!signature) { + throw Error("No signature"); + } + + // prepare the canonical json: remove unsigned and signatures, and stringify with anotherjson + const mangledObj = Object.assign({}, obj); + if ("unsigned" in mangledObj) { + delete mangledObj.unsigned; + } + delete mangledObj.signatures; + const json = _anotherJson.default.stringify(mangledObj); + olmDevice.verifySignature(signingKey, json, signature); +} + +/** + * Sign a JSON object using public key cryptography + * @param obj - Object to sign. The object will be modified to include + * the new signature + * @param key - the signing object or the private key + * seed + * @param userId - The user ID who owns the signing key + * @param pubKey - The public key (ignored if key is a seed) + * @returns the signature for the object + */ +function pkSign(obj, key, userId, pubKey) { + let createdKey = false; + if (key instanceof Uint8Array) { + const keyObj = new global.Olm.PkSigning(); + pubKey = keyObj.init_with_seed(key); + key = keyObj; + createdKey = true; + } + const sigs = obj.signatures || {}; + delete obj.signatures; + const unsigned = obj.unsigned; + if (obj.unsigned) delete obj.unsigned; + try { + const mysigs = sigs[userId] || {}; + sigs[userId] = mysigs; + return mysigs["ed25519:" + pubKey] = key.sign(_anotherJson.default.stringify(obj)); + } finally { + obj.signatures = sigs; + if (unsigned) obj.unsigned = unsigned; + if (createdKey) { + key.free(); + } + } +} + +/** + * Verify a signed JSON object + * @param obj - Object to verify + * @param pubKey - The public key to use to verify + * @param userId - The user ID who signed the object + */ +function pkVerify(obj, pubKey, userId) { + const keyId = "ed25519:" + pubKey; + if (!(obj.signatures && obj.signatures[userId] && obj.signatures[userId][keyId])) { + throw new Error("No signature"); + } + const signature = obj.signatures[userId][keyId]; + const util = new global.Olm.Utility(); + const sigs = obj.signatures; + delete obj.signatures; + const unsigned = obj.unsigned; + if (obj.unsigned) delete obj.unsigned; + try { + util.ed25519_verify(pubKey, _anotherJson.default.stringify(obj), signature); + } finally { + obj.signatures = sigs; + if (unsigned) obj.unsigned = unsigned; + util.free(); + } +} + +/** + * Check that an event was encrypted using olm. + */ +function isOlmEncrypted(event) { + if (!event.getSenderKey()) { + _logger.logger.error("Event has no sender key (not encrypted?)"); + return false; + } + if (event.getWireType() !== _event.EventType.RoomMessageEncrypted || !["m.olm.v1.curve25519-aes-sha2"].includes(event.getWireContent().algorithm)) { + _logger.logger.error("Event was not encrypted using an appropriate algorithm"); + return false; + } + return true; +} + +/** + * Encode a typed array of uint8 as base64. + * @param uint8Array - The data to encode. + * @returns The base64. + */ +function encodeBase64(uint8Array) { + return Buffer.from(uint8Array).toString("base64"); +} + +/** + * Encode a typed array of uint8 as unpadded base64. + * @param uint8Array - The data to encode. + * @returns The unpadded base64. + */ +function encodeUnpaddedBase64(uint8Array) { + return encodeBase64(uint8Array).replace(/=+$/g, ""); +} + +/** + * Decode a base64 string to a typed array of uint8. + * @param base64 - The base64 to decode. + * @returns The decoded data. + */ +function decodeBase64(base64) { + return Buffer.from(base64, "base64"); +} +//# sourceMappingURL=olmlib.js.map
\ No newline at end of file |