matrix-react-sdk
Version:
SDK for matrix.org using React
127 lines (120 loc) • 14.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.buildAndEncodePickleKey = buildAndEncodePickleKey;
exports.encryptPickleKey = encryptPickleKey;
exports.getPickleAdditionalData = getPickleAdditionalData;
var _base = require("matrix-js-sdk/src/base64");
var _logger = require("matrix-js-sdk/src/logger");
/*
Copyright 2024 New Vector Ltd.
Copyright 2020-2024 The Matrix.org Foundation C.I.C.
Copyright 2018 New Vector Ltd
Copyright 2016 Aviral Dasgupta
Copyright 2016 OpenMarket Ltd
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
// Note: we don't import the base64 utils from `matrix-js-sdk/src/matrix` because this file
// is used by Element Web's service worker, and importing `matrix` brings in ~1mb of stuff
// we don't need. Instead, we ignore the import restriction and only bring in what we actually
// need.
// Note: `base64` is not public in the js-sdk, so if it changes/breaks, that's on us. We should
// be okay with our frequent tests, locked versioning, etc though. We'll pick up problems well
// before release.
// eslint-disable-next-line no-restricted-imports
/**
* Encrypted format of a pickle key, as stored in IndexedDB.
*/
/**
* Calculates the `additionalData` for the AES-GCM key used by the pickling processes. This
* additional data is *not* encrypted, but *is* authenticated. The additional data is constructed
* from the user ID and device ID provided.
*
* The later-constructed pickle key is used to decrypt values, such as access tokens, from IndexedDB.
*
* See https://developer.mozilla.org/en-US/docs/Web/API/AesGcmParams for more information on
* `additionalData`.
*
* @param {string} userId The user ID who owns the pickle key.
* @param {string} deviceId The device ID which owns the pickle key.
* @return {Uint8Array} The additional data as a Uint8Array.
*/
function getPickleAdditionalData(userId, deviceId) {
const additionalData = new Uint8Array(userId.length + deviceId.length + 1);
for (let i = 0; i < userId.length; i++) {
additionalData[i] = userId.charCodeAt(i);
}
additionalData[userId.length] = 124; // "|"
for (let i = 0; i < deviceId.length; i++) {
additionalData[userId.length + 1 + i] = deviceId.charCodeAt(i);
}
return additionalData;
}
/**
* Encrypt the given pickle key, ready for storage in the database.
*
* @param pickleKey - The key to be encrypted.
* @param userId - The user ID the pickle key belongs to.
* @param deviceId - The device ID the pickle key belongs to.
*
* @returns Data object ready for storing in indexeddb.
*/
async function encryptPickleKey(pickleKey, userId, deviceId) {
if (!crypto?.subtle) {
return undefined;
}
const cryptoKey = await crypto.subtle.generateKey({
name: "AES-GCM",
length: 256
}, false, ["encrypt", "decrypt"]);
const iv = new Uint8Array(32);
crypto.getRandomValues(iv);
const additionalData = getPickleAdditionalData(userId, deviceId);
const encrypted = await crypto.subtle.encrypt({
name: "AES-GCM",
iv,
additionalData
}, cryptoKey, pickleKey);
return {
encrypted,
iv,
cryptoKey
};
}
/**
* Decrypts the provided data into a pickle key and base64-encodes it ready for use elsewhere.
*
* If `data` is undefined in part or in full, returns undefined.
*
* If crypto functions are not available, returns undefined regardless of input.
*
* @param data An object containing the encrypted pickle key data: encrypted payload, initialization vector (IV), and crypto key. Typically loaded from indexedDB.
* @param userId The user ID the pickle key belongs to.
* @param deviceId The device ID the pickle key belongs to.
* @returns A promise that resolves to the encoded pickle key, or undefined if the key cannot be built and encoded.
*/
async function buildAndEncodePickleKey(data, userId, deviceId) {
if (!crypto?.subtle) {
return undefined;
}
if (!data || !data.encrypted || !data.iv || !data.cryptoKey) {
return undefined;
}
try {
const additionalData = getPickleAdditionalData(userId, deviceId);
const pickleKeyBuf = await crypto.subtle.decrypt({
name: "AES-GCM",
iv: data.iv,
additionalData
}, data.cryptoKey, data.encrypted);
if (pickleKeyBuf) {
return (0, _base.encodeUnpaddedBase64)(pickleKeyBuf);
}
} catch (e) {
_logger.logger.error("Error decrypting pickle key");
}
return undefined;
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfYmFzZSIsInJlcXVpcmUiLCJfbG9nZ2VyIiwiZ2V0UGlja2xlQWRkaXRpb25hbERhdGEiLCJ1c2VySWQiLCJkZXZpY2VJZCIsImFkZGl0aW9uYWxEYXRhIiwiVWludDhBcnJheSIsImxlbmd0aCIsImkiLCJjaGFyQ29kZUF0IiwiZW5jcnlwdFBpY2tsZUtleSIsInBpY2tsZUtleSIsImNyeXB0byIsInN1YnRsZSIsInVuZGVmaW5lZCIsImNyeXB0b0tleSIsImdlbmVyYXRlS2V5IiwibmFtZSIsIml2IiwiZ2V0UmFuZG9tVmFsdWVzIiwiZW5jcnlwdGVkIiwiZW5jcnlwdCIsImJ1aWxkQW5kRW5jb2RlUGlja2xlS2V5IiwiZGF0YSIsInBpY2tsZUtleUJ1ZiIsImRlY3J5cHQiLCJlbmNvZGVVbnBhZGRlZEJhc2U2NCIsImUiLCJsb2dnZXIiLCJlcnJvciJdLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy91dGlscy90b2tlbnMvcGlja2xpbmcudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLypcbkNvcHlyaWdodCAyMDI0IE5ldyBWZWN0b3IgTHRkLlxuQ29weXJpZ2h0IDIwMjAtMjAyNCBUaGUgTWF0cml4Lm9yZyBGb3VuZGF0aW9uIEMuSS5DLlxuQ29weXJpZ2h0IDIwMTggTmV3IFZlY3RvciBMdGRcbkNvcHlyaWdodCAyMDE2IEF2aXJhbCBEYXNndXB0YVxuQ29weXJpZ2h0IDIwMTYgT3Blbk1hcmtldCBMdGRcblxuU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFHUEwtMy4wLW9ubHkgT1IgR1BMLTMuMC1vbmx5XG5QbGVhc2Ugc2VlIExJQ0VOU0UgZmlsZXMgaW4gdGhlIHJlcG9zaXRvcnkgcm9vdCBmb3IgZnVsbCBkZXRhaWxzLlxuKi9cblxuLy8gTm90ZTogd2UgZG9uJ3QgaW1wb3J0IHRoZSBiYXNlNjQgdXRpbHMgZnJvbSBgbWF0cml4LWpzLXNkay9zcmMvbWF0cml4YCBiZWNhdXNlIHRoaXMgZmlsZVxuLy8gaXMgdXNlZCBieSBFbGVtZW50IFdlYidzIHNlcnZpY2Ugd29ya2VyLCBhbmQgaW1wb3J0aW5nIGBtYXRyaXhgIGJyaW5ncyBpbiB+MW1iIG9mIHN0dWZmXG4vLyB3ZSBkb24ndCBuZWVkLiBJbnN0ZWFkLCB3ZSBpZ25vcmUgdGhlIGltcG9ydCByZXN0cmljdGlvbiBhbmQgb25seSBicmluZyBpbiB3aGF0IHdlIGFjdHVhbGx5XG4vLyBuZWVkLlxuLy8gTm90ZTogYGJhc2U2NGAgaXMgbm90IHB1YmxpYyBpbiB0aGUganMtc2RrLCBzbyBpZiBpdCBjaGFuZ2VzL2JyZWFrcywgdGhhdCdzIG9uIHVzLiBXZSBzaG91bGRcbi8vIGJlIG9rYXkgd2l0aCBvdXIgZnJlcXVlbnQgdGVzdHMsIGxvY2tlZCB2ZXJzaW9uaW5nLCBldGMgdGhvdWdoLiBXZSdsbCBwaWNrIHVwIHByb2JsZW1zIHdlbGxcbi8vIGJlZm9yZSByZWxlYXNlLlxuLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG5vLXJlc3RyaWN0ZWQtaW1wb3J0c1xuaW1wb3J0IHsgZW5jb2RlVW5wYWRkZWRCYXNlNjQgfSBmcm9tIFwibWF0cml4LWpzLXNkay9zcmMvYmFzZTY0XCI7XG5pbXBvcnQgeyBsb2dnZXIgfSBmcm9tIFwibWF0cml4LWpzLXNkay9zcmMvbG9nZ2VyXCI7XG5cbi8qKlxuICogRW5jcnlwdGVkIGZvcm1hdCBvZiBhIHBpY2tsZSBrZXksIGFzIHN0b3JlZCBpbiBJbmRleGVkREIuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgRW5jcnlwdGVkUGlja2xlS2V5IHtcbiAgICAvKiogVGhlIGVuY3J5cHRlZCBwYXlsb2FkLiAqL1xuICAgIGVuY3J5cHRlZD86IEJ1ZmZlclNvdXJjZTtcblxuICAgIC8qKiBJbml0aWFsaXNhdGlvbiB2ZWN0b3IgZm9yIHRoZSBlbmNyeXB0aW9uLiAqL1xuICAgIGl2PzogQnVmZmVyU291cmNlO1xuXG4gICAgLyoqIFRoZSBlbmNyeXB0aW9uIGtleSB3aGljaCB3YXMgdXNlZCB0byBlbmNyeXB0IHRoZSBwYXlsb2FkLiAqL1xuICAgIGNyeXB0b0tleT86IENyeXB0b0tleTtcbn1cblxuLyoqXG4gKiBDYWxjdWxhdGVzIHRoZSBgYWRkaXRpb25hbERhdGFgIGZvciB0aGUgQUVTLUdDTSBrZXkgdXNlZCBieSB0aGUgcGlja2xpbmcgcHJvY2Vzc2VzLiBUaGlzXG4gKiBhZGRpdGlvbmFsIGRhdGEgaXMgKm5vdCogZW5jcnlwdGVkLCBidXQgKmlzKiBhdXRoZW50aWNhdGVkLiBUaGUgYWRkaXRpb25hbCBkYXRhIGlzIGNvbnN0cnVjdGVkXG4gKiBmcm9tIHRoZSB1c2VyIElEIGFuZCBkZXZpY2UgSUQgcHJvdmlkZWQuXG4gKlxuICogVGhlIGxhdGVyLWNvbnN0cnVjdGVkIHBpY2tsZSBrZXkgaXMgdXNlZCB0byBkZWNyeXB0IHZhbHVlcywgc3VjaCBhcyBhY2Nlc3MgdG9rZW5zLCBmcm9tIEluZGV4ZWREQi5cbiAqXG4gKiBTZWUgaHR0cHM6Ly9kZXZlbG9wZXIubW96aWxsYS5vcmcvZW4tVVMvZG9jcy9XZWIvQVBJL0Flc0djbVBhcmFtcyBmb3IgbW9yZSBpbmZvcm1hdGlvbiBvblxuICogYGFkZGl0aW9uYWxEYXRhYC5cbiAqXG4gKiBAcGFyYW0ge3N0cmluZ30gdXNlcklkIFRoZSB1c2VyIElEIHdobyBvd25zIHRoZSBwaWNrbGUga2V5LlxuICogQHBhcmFtIHtzdHJpbmd9IGRldmljZUlkIFRoZSBkZXZpY2UgSUQgd2hpY2ggb3ducyB0aGUgcGlja2xlIGtleS5cbiAqIEByZXR1cm4ge1VpbnQ4QXJyYXl9IFRoZSBhZGRpdGlvbmFsIGRhdGEgYXMgYSBVaW50OEFycmF5LlxuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0UGlja2xlQWRkaXRpb25hbERhdGEodXNlcklkOiBzdHJpbmcsIGRldmljZUlkOiBzdHJpbmcpOiBVaW50OEFycmF5IHtcbiAgICBjb25zdCBhZGRpdGlvbmFsRGF0YSA9IG5ldyBVaW50OEFycmF5KHVzZXJJZC5sZW5ndGggKyBkZXZpY2VJZC5sZW5ndGggKyAxKTtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IHVzZXJJZC5sZW5ndGg7IGkrKykge1xuICAgICAgICBhZGRpdGlvbmFsRGF0YVtpXSA9IHVzZXJJZC5jaGFyQ29kZUF0KGkpO1xuICAgIH1cbiAgICBhZGRpdGlvbmFsRGF0YVt1c2VySWQubGVuZ3RoXSA9IDEyNDsgLy8gXCJ8XCJcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IGRldmljZUlkLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGFkZGl0aW9uYWxEYXRhW3VzZXJJZC5sZW5ndGggKyAxICsgaV0gPSBkZXZpY2VJZC5jaGFyQ29kZUF0KGkpO1xuICAgIH1cbiAgICByZXR1cm4gYWRkaXRpb25hbERhdGE7XG59XG5cbi8qKlxuICogRW5jcnlwdCB0aGUgZ2l2ZW4gcGlja2xlIGtleSwgcmVhZHkgZm9yIHN0b3JhZ2UgaW4gdGhlIGRhdGFiYXNlLlxuICpcbiAqIEBwYXJhbSBwaWNrbGVLZXkgLSBUaGUga2V5IHRvIGJlIGVuY3J5cHRlZC5cbiAqIEBwYXJhbSB1c2VySWQgLSBUaGUgdXNlciBJRCB0aGUgcGlja2xlIGtleSBiZWxvbmdzIHRvLlxuICogQHBhcmFtIGRldmljZUlkIC0gVGhlIGRldmljZSBJRCB0aGUgcGlja2xlIGtleSBiZWxvbmdzIHRvLlxuICpcbiAqIEByZXR1cm5zIERhdGEgb2JqZWN0IHJlYWR5IGZvciBzdG9yaW5nIGluIGluZGV4ZWRkYi5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGVuY3J5cHRQaWNrbGVLZXkoXG4gICAgcGlja2xlS2V5OiBVaW50OEFycmF5LFxuICAgIHVzZXJJZDogc3RyaW5nLFxuICAgIGRldmljZUlkOiBzdHJpbmcsXG4pOiBQcm9taXNlPEVuY3J5cHRlZFBpY2tsZUtleSB8IHVuZGVmaW5lZD4ge1xuICAgIGlmICghY3J5cHRvPy5zdWJ0bGUpIHtcbiAgICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICB9XG4gICAgY29uc3QgY3J5cHRvS2V5ID0gYXdhaXQgY3J5cHRvLnN1YnRsZS5nZW5lcmF0ZUtleSh7IG5hbWU6IFwiQUVTLUdDTVwiLCBsZW5ndGg6IDI1NiB9LCBmYWxzZSwgW1wiZW5jcnlwdFwiLCBcImRlY3J5cHRcIl0pO1xuICAgIGNvbnN0IGl2ID0gbmV3IFVpbnQ4QXJyYXkoMzIpO1xuICAgIGNyeXB0by5nZXRSYW5kb21WYWx1ZXMoaXYpO1xuXG4gICAgY29uc3QgYWRkaXRpb25hbERhdGEgPSBnZXRQaWNrbGVBZGRpdGlvbmFsRGF0YSh1c2VySWQsIGRldmljZUlkKTtcbiAgICBjb25zdCBlbmNyeXB0ZWQgPSBhd2FpdCBjcnlwdG8uc3VidGxlLmVuY3J5cHQoeyBuYW1lOiBcIkFFUy1HQ01cIiwgaXYsIGFkZGl0aW9uYWxEYXRhIH0sIGNyeXB0b0tleSwgcGlja2xlS2V5KTtcbiAgICByZXR1cm4geyBlbmNyeXB0ZWQsIGl2LCBjcnlwdG9LZXkgfTtcbn1cblxuLyoqXG4gKiBEZWNyeXB0cyB0aGUgcHJvdmlkZWQgZGF0YSBpbnRvIGEgcGlja2xlIGtleSBhbmQgYmFzZTY0LWVuY29kZXMgaXQgcmVhZHkgZm9yIHVzZSBlbHNld2hlcmUuXG4gKlxuICogSWYgYGRhdGFgIGlzIHVuZGVmaW5lZCBpbiBwYXJ0IG9yIGluIGZ1bGwsIHJldHVybnMgdW5kZWZpbmVkLlxuICpcbiAqICBJZiBjcnlwdG8gZnVuY3Rpb25zIGFyZSBub3QgYXZhaWxhYmxlLCByZXR1cm5zIHVuZGVmaW5lZCByZWdhcmRsZXNzIG9mIGlucHV0LlxuICpcbiAqIEBwYXJhbSBkYXRhIEFuIG9iamVjdCBjb250YWluaW5nIHRoZSBlbmNyeXB0ZWQgcGlja2xlIGtleSBkYXRhOiBlbmNyeXB0ZWQgcGF5bG9hZCwgaW5pdGlhbGl6YXRpb24gdmVjdG9yIChJViksIGFuZCBjcnlwdG8ga2V5LiBUeXBpY2FsbHkgbG9hZGVkIGZyb20gaW5kZXhlZERCLlxuICogQHBhcmFtIHVzZXJJZCBUaGUgdXNlciBJRCB0aGUgcGlja2xlIGtleSBiZWxvbmdzIHRvLlxuICogQHBhcmFtIGRldmljZUlkIFRoZSBkZXZpY2UgSUQgdGhlIHBpY2tsZSBrZXkgYmVsb25ncyB0by5cbiAqIEByZXR1cm5zIEEgcHJvbWlzZSB0aGF0IHJlc29sdmVzIHRvIHRoZSBlbmNvZGVkIHBpY2tsZSBrZXksIG9yIHVuZGVmaW5lZCBpZiB0aGUga2V5IGNhbm5vdCBiZSBidWlsdCBhbmQgZW5jb2RlZC5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGJ1aWxkQW5kRW5jb2RlUGlja2xlS2V5KFxuICAgIGRhdGE6IEVuY3J5cHRlZFBpY2tsZUtleSB8IHVuZGVmaW5lZCxcbiAgICB1c2VySWQ6IHN0cmluZyxcbiAgICBkZXZpY2VJZDogc3RyaW5nLFxuKTogUHJvbWlzZTxzdHJpbmcgfCB1bmRlZmluZWQ+IHtcbiAgICBpZiAoIWNyeXB0bz8uc3VidGxlKSB7XG4gICAgICAgIHJldHVybiB1bmRlZmluZWQ7XG4gICAgfVxuICAgIGlmICghZGF0YSB8fCAhZGF0YS5lbmNyeXB0ZWQgfHwgIWRhdGEuaXYgfHwgIWRhdGEuY3J5cHRvS2V5KSB7XG4gICAgICAgIHJldHVybiB1bmRlZmluZWQ7XG4gICAgfVxuXG4gICAgdHJ5IHtcbiAgICAgICAgY29uc3QgYWRkaXRpb25hbERhdGEgPSBnZXRQaWNrbGVBZGRpdGlvbmFsRGF0YSh1c2VySWQsIGRldmljZUlkKTtcbiAgICAgICAgY29uc3QgcGlja2xlS2V5QnVmID0gYXdhaXQgY3J5cHRvLnN1YnRsZS5kZWNyeXB0KFxuICAgICAgICAgICAgeyBuYW1lOiBcIkFFUy1HQ01cIiwgaXY6IGRhdGEuaXYsIGFkZGl0aW9uYWxEYXRhIH0sXG4gICAgICAgICAgICBkYXRhLmNyeXB0b0tleSxcbiAgICAgICAgICAgIGRhdGEuZW5jcnlwdGVkLFxuICAgICAgICApO1xuICAgICAgICBpZiAocGlja2xlS2V5QnVmKSB7XG4gICAgICAgICAgICByZXR1cm4gZW5jb2RlVW5wYWRkZWRCYXNlNjQocGlja2xlS2V5QnVmKTtcbiAgICAgICAgfVxuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgbG9nZ2VyLmVycm9yKFwiRXJyb3IgZGVjcnlwdGluZyBwaWNrbGUga2V5XCIp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