feat: complete Ancestor quote bot with production-ready Docker support

This commit is contained in:
unfunny
2026-09-13 14:14:38 -04:00
parent f2016da05a
commit fe7351a7dc
1145 changed files with 30890 additions and 97684 deletions

View File

@@ -1,3 +1,14 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ServerSideSecretStorageImpl = exports.SECRET_STORAGE_ALGORITHM_V1_AES = void 0;
exports.trimTrailingEquals = trimTrailingEquals;
var _client = require("./client");
var _aes = require("./crypto/aes");
var _randomstring = require("./randomstring");
var _logger = require("./logger");
/*
Copyright 2021-2023 The Matrix.org Foundation C.I.C.
@@ -20,11 +31,7 @@ limitations under the License.
* @see https://spec.matrix.org/v1.6/client-server-api/#storage
*/
import { secureRandomString } from "./randomstring.js";
import { logger } from "./logger.js";
import encryptAESSecretStorageItem from "./utils/encryptAESSecretStorageItem.js";
import decryptAESSecretStorageItem from "./utils/decryptAESSecretStorageItem.js";
export const SECRET_STORAGE_ALGORITHM_V1_AES = "m.secret_storage.v1.aes-hmac-sha2";
const SECRET_STORAGE_ALGORITHM_V1_AES = "m.secret_storage.v1.aes-hmac-sha2";
/**
* Common base interface for Secret Storage Keys.
@@ -75,23 +82,13 @@ export const SECRET_STORAGE_ALGORITHM_V1_AES = "m.secret_storage.v1.aes-hmac-sha
* Application callbacks for use with {@link SecretStorage.ServerSideSecretStorageImpl}
*/
/**
* Account Data event types which can store secret-storage-encrypted information.
*/
/**
* Account Data event content type for storing secret-storage-encrypted information.
*
* See https://spec.matrix.org/v1.13/client-server-api/#msecret_storagev1aes-hmac-sha2-1
*/
/**
* Interface provided by SecretStorage implementations
*
* Normally this will just be an {@link ServerSideSecretStorageImpl}, but for backwards
* compatibility some methods allow other implementations.
*/
exports.SECRET_STORAGE_ALGORITHM_V1_AES = SECRET_STORAGE_ALGORITHM_V1_AES;
/**
* Implementation of Server-side secret storage.
*
@@ -100,7 +97,7 @@ export const SECRET_STORAGE_ALGORITHM_V1_AES = "m.secret_storage.v1.aes-hmac-sha
*
* @see https://spec.matrix.org/v1.6/client-server-api/#storage
*/
export class ServerSideSecretStorageImpl {
class ServerSideSecretStorageImpl {
/**
* Construct a new `SecretStorage`.
*
@@ -124,21 +121,30 @@ export class ServerSideSecretStorageImpl {
async getDefaultKeyId() {
const defaultKey = await this.accountDataAdapter.getAccountDataFromServer("m.secret_storage.default_key");
if (!defaultKey) return null;
return defaultKey.key ?? null;
return defaultKey.key;
}
/**
* Implementation of {@link ServerSideSecretStorage#setDefaultKeyId}.
* Set the default key ID for encrypting secrets.
*
* @param keyId - The new default key ID
*/
async setDefaultKeyId(keyId) {
// The spec [1] says that the value of the account data entry should be an object with a `key` property.
// It doesn't specify how to delete the default key; we do it by setting the account data to an empty object.
//
// [1]: https://spec.matrix.org/v1.13/client-server-api/#key-storage
const newValue = keyId === null ? {} : {
key: keyId
};
await this.accountDataAdapter.setAccountData("m.secret_storage.default_key", newValue);
setDefaultKeyId(keyId) {
return new Promise((resolve, reject) => {
const listener = ev => {
if (ev.getType() === "m.secret_storage.default_key" && ev.getContent().key === keyId) {
this.accountDataAdapter.removeListener(_client.ClientEvent.AccountData, listener);
resolve();
}
};
this.accountDataAdapter.on(_client.ClientEvent.AccountData, listener);
this.accountDataAdapter.setAccountData("m.secret_storage.default_key", {
key: keyId
}).catch(e => {
this.accountDataAdapter.removeListener(_client.ClientEvent.AccountData, listener);
reject(e);
});
});
}
/**
@@ -154,7 +160,7 @@ export class ServerSideSecretStorageImpl {
* keyId: the ID of the key
* keyInfo: details about the key (iv, mac, passphrase)
*/
async addKey(algorithm, opts, keyId) {
async addKey(algorithm, opts = {}, keyId) {
if (algorithm !== SECRET_STORAGE_ALGORITHM_V1_AES) {
throw new Error(`Unknown key algorithm ${algorithm}`);
}
@@ -167,17 +173,19 @@ export class ServerSideSecretStorageImpl {
if (opts.passphrase) {
keyInfo.passphrase = opts.passphrase;
}
const {
iv,
mac
} = await calculateKeyCheck(opts.key);
keyInfo.iv = iv;
keyInfo.mac = mac;
if (opts.key) {
const {
iv,
mac
} = await (0, _aes.calculateKeyCheck)(opts.key);
keyInfo.iv = iv;
keyInfo.mac = mac;
}
// Create a unique key id. XXX: this is racey.
if (!keyId) {
do {
keyId = secureRandomString(32);
keyId = (0, _randomstring.randomString)(32);
} while (await this.accountDataAdapter.getAccountDataFromServer(`m.secret_storage.key.${keyId}`));
}
await this.accountDataAdapter.setAccountData(`m.secret_storage.key.${keyId}`, keyInfo);
@@ -203,7 +211,7 @@ export class ServerSideSecretStorageImpl {
if (!keyId) {
return null;
}
const keyInfo = await this.accountDataAdapter.getAccountDataFromServer(`m.secret_storage.key.${keyId}`);
const keyInfo = await this.accountDataAdapter.getAccountDataFromServer("m.secret_storage.key." + keyId);
return keyInfo ? [keyId, keyInfo] : null;
}
@@ -232,7 +240,7 @@ export class ServerSideSecretStorageImpl {
if (info.mac) {
const {
mac
} = await calculateKeyCheck(key, info.iv);
} = await (0, _aes.calculateKeyCheck)(key, info.iv);
return trimTrailingEquals(info.mac) === trimTrailingEquals(mac);
} else {
// if we have no information, we have to assume the key is right
@@ -244,14 +252,17 @@ export class ServerSideSecretStorageImpl {
}
/**
* Implementation of {@link ServerSideSecretStorage#store}.
* Store an encrypted secret on the server.
*
* Details of the encryption keys to be used must previously have been stored in account data
* (for example, via {@link ServerSideSecretStorageImpl#addKey}. {@link SecretStorageCallbacks#getSecretStorageKey} will be called to obtain a secret storage
* key to decrypt the secret.
*
* @param name - The name of the secret - i.e., the "event type" to be stored in the account data
* @param secret - The secret contents.
* @param keys - The IDs of the keys to use to encrypt the secret, or null/undefined to use the default key.
*/
async store(name, secret, keys) {
if (secret === null) {
// remove secret
await this.accountDataAdapter.setAccountData(name, {});
return;
}
const encrypted = {};
if (!keys) {
const defaultKeyId = await this.getDefaultKeyId();
@@ -265,7 +276,7 @@ export class ServerSideSecretStorageImpl {
}
for (const keyId of keys) {
// get key information from key storage
const keyInfo = await this.accountDataAdapter.getAccountDataFromServer(`m.secret_storage.key.${keyId}`);
const keyInfo = await this.accountDataAdapter.getAccountDataFromServer("m.secret_storage.key." + keyId);
if (!keyInfo) {
throw new Error("Unknown key: " + keyId);
}
@@ -278,7 +289,7 @@ export class ServerSideSecretStorageImpl {
const [, encryption] = await this.getSecretStorageKey(keys, name);
encrypted[keyId] = await encryption.encrypt(secret);
} else {
logger.warn("unknown algorithm for secret storage key " + keyId + ": " + keyInfo.algorithm);
_logger.logger.warn("unknown algorithm for secret storage key " + keyId + ": " + keyInfo.algorithm);
// do nothing if we don't understand the encryption algorithm
}
}
@@ -313,10 +324,10 @@ export class ServerSideSecretStorageImpl {
const keys = {};
for (const keyId of Object.keys(secretInfo.encrypted)) {
// get key information from key storage
const keyInfo = await this.accountDataAdapter.getAccountDataFromServer(`m.secret_storage.key.${keyId}`);
const keyInfo = await this.accountDataAdapter.getAccountDataFromServer("m.secret_storage.key." + keyId);
const encInfo = secretInfo.encrypted[keyId];
// only use keys we understand the encryption algorithm of
if (keyInfo?.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
if ((keyInfo === null || keyInfo === void 0 ? void 0 : keyInfo.algorithm) === SECRET_STORAGE_ALGORITHM_V1_AES) {
if (encInfo.iv && encInfo.ciphertext && encInfo.mac) {
keys[keyId] = keyInfo;
}
@@ -344,13 +355,13 @@ export class ServerSideSecretStorageImpl {
async isStored(name) {
// check if secret exists
const secretInfo = await this.accountDataAdapter.getAccountDataFromServer(name);
if (!secretInfo?.encrypted) return null;
if (!(secretInfo !== null && secretInfo !== void 0 && secretInfo.encrypted)) return null;
const ret = {};
// filter secret encryption keys with supported algorithm
for (const keyId of Object.keys(secretInfo.encrypted)) {
// get key information from key storage
const keyInfo = await this.accountDataAdapter.getAccountDataFromServer(`m.secret_storage.key.${keyId}`);
const keyInfo = await this.accountDataAdapter.getAccountDataFromServer("m.secret_storage.key." + keyId);
if (!keyInfo) continue;
const encInfo = secretInfo.encrypted[keyId];
@@ -383,10 +394,10 @@ export class ServerSideSecretStorageImpl {
if (keys[keyId].algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
const decryption = {
encrypt: function (secret) {
return encryptAESSecretStorageItem(secret, privateKey, name);
return (0, _aes.encryptAES)(secret, privateKey, name);
},
decrypt: function (encInfo) {
return decryptAESSecretStorageItem(encInfo, privateKey, name);
return (0, _aes.decryptAES)(encInfo, privateKey, name);
}
};
return [keyId, decryption];
@@ -402,7 +413,8 @@ export class ServerSideSecretStorageImpl {
*
* @param input - input string
*/
export function trimTrailingEquals(input) {
exports.ServerSideSecretStorageImpl = ServerSideSecretStorageImpl;
function trimTrailingEquals(input) {
// according to Sonar and CodeQL, a regex such as /=+$/ is superlinear.
// Not sure I believe it, but it's easy enough to work around.
@@ -417,20 +429,4 @@ export function trimTrailingEquals(input) {
return input;
}
}
// string of zeroes, for calculating the key check
const ZERO_STR = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
/**
* Calculate the MAC for checking the key.
* See https://spec.matrix.org/v1.11/client-server-api/#msecret_storagev1aes-hmac-sha2, steps 3 and 4.
*
* @param key - the key to use
* @param iv - The initialization vector as a base64-encoded string.
* If omitted, a random initialization vector will be created.
* @returns An object that contains, `mac` and `iv` properties.
*/
export function calculateKeyCheck(key, iv) {
return encryptAESSecretStorageItem(ZERO_STR, key, "", iv);
}
//# sourceMappingURL=secret-storage.js.map