UNPKG

@signalapp/mock-server

Version:
240 lines (239 loc) 10.3 kB
"use strict"; // Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.encryptProvisionMessage = encryptProvisionMessage; exports.encryptAttachment = encryptAttachment; exports.generateServerCertificate = generateServerCertificate; exports.generateSenderCertificate = generateSenderCertificate; exports.deriveAccessKey = deriveAccessKey; exports.deriveMasterKey = deriveMasterKey; exports.deriveStorageKey = deriveStorageKey; exports.deriveStorageItemKey = deriveStorageItemKey; exports.decryptStorageManifest = decryptStorageManifest; exports.encryptStorageManifest = encryptStorageManifest; exports.decryptStorageItem = decryptStorageItem; exports.encryptStorageItem = encryptStorageItem; exports.encryptProfileName = encryptProfileName; exports.generateAccessKeyVerifier = generateAccessKeyVerifier; exports.decodePreKey = decodePreKey; exports.decodeSignedPreKey = decodeSignedPreKey; exports.decodeKyberPreKey = decodeKyberPreKey; exports.hashRemoteConfig = hashRemoteConfig; const crypto_1 = __importDefault(require("crypto")); const buffer_1 = require("buffer"); const libsignal_client_1 = require("@signalapp/libsignal-client"); const compiled_1 = require("../protos/compiled"); const constants_1 = require("./constants"); const AES_KEY_SIZE = 32; const MAC_KEY_SIZE = 32; const AESGCM_IV_SIZE = 12; const AUTH_TAG_SIZE = 16; const MASTER_KEY_SIZE = 32; function encryptProvisionMessage(data, remotePubKey) { const privateKey = libsignal_client_1.PrivateKey.generate(); const publicKey = privateKey.getPublicKey(); const agreement = privateKey.agree(remotePubKey); const secrets = (0, libsignal_client_1.hkdf)(AES_KEY_SIZE + MAC_KEY_SIZE, agreement, buffer_1.Buffer.from('TextSecure Provisioning Message'), null); const aesKey = secrets.slice(0, AES_KEY_SIZE); const macKey = secrets.slice(AES_KEY_SIZE); const iv = crypto_1.default.randomBytes(16); const cipher = crypto_1.default.createCipheriv('aes-256-cbc', aesKey, iv); const encrypted = buffer_1.Buffer.concat([cipher.update(data), cipher.final()]); const version = buffer_1.Buffer.from([1]); const ciphertext = buffer_1.Buffer.concat([version, iv, encrypted]); const mac = crypto_1.default.createHmac('sha256', macKey).update(ciphertext).digest(); const body = buffer_1.Buffer.concat([ciphertext, mac]); return { body, ephemeralKey: buffer_1.Buffer.from(publicKey.serialize()), }; } function encryptAttachment(cleartext, { aesKey, macKey, iv } = { aesKey: crypto_1.default.randomBytes(32), macKey: crypto_1.default.randomBytes(32), iv: crypto_1.default.randomBytes(16), }) { const cipher = crypto_1.default.createCipheriv('aes-256-cbc', aesKey, iv); const ciphertext = buffer_1.Buffer.concat([cipher.update(cleartext), cipher.final()]); const mac = crypto_1.default .createHmac('sha256', macKey) .update(iv) .update(ciphertext) .digest(); const key = buffer_1.Buffer.concat([aesKey, macKey]); const blob = buffer_1.Buffer.concat([iv, ciphertext, mac]); const digest = crypto_1.default.createHash('sha256').update(blob).digest(); return { key, blob, digest, size: cleartext.length, }; } function generateServerCertificate(rootKey) { const privateKey = libsignal_client_1.PrivateKey.generate(); const data = buffer_1.Buffer.from(compiled_1.signalservice.ServerCertificate.Certificate.encode({ id: constants_1.SERVER_CERTIFICATE_ID, key: privateKey.getPublicKey().serialize(), })); const signature = rootKey.sign(data); const certificate = { certificate: data, signature, }; return { privateKey, certificate, }; } function generateSenderCertificate(serverCert, sender) { const data = buffer_1.Buffer.from(compiled_1.signalservice.SenderCertificate.Certificate.encode({ senderE164: sender.number ?? null, senderUuid: sender.aci, senderDevice: sender.deviceId, expires: BigInt(sender.expires ?? constants_1.NEVER_EXPIRES), identityKey: sender.identityKey.serialize(), signer: serverCert.certificate, })); const signature = serverCert.privateKey.sign(data); const certificate = buffer_1.Buffer.from(compiled_1.signalservice.SenderCertificate.encode({ certificate: data, signature, })); return libsignal_client_1.SenderCertificate.deserialize(certificate); } function deriveAccessKey(profileKey) { const cipher = crypto_1.default.createCipheriv('aes-256-gcm', profileKey, buffer_1.Buffer.alloc(12)); return buffer_1.Buffer.concat([cipher.update(buffer_1.Buffer.alloc(16)), cipher.final()]); } function deriveMasterKey(accountEntropyPool) { return buffer_1.Buffer.from((0, libsignal_client_1.hkdf)(MASTER_KEY_SIZE, buffer_1.Buffer.from(accountEntropyPool), buffer_1.Buffer.from('20240801_SIGNAL_SVR_MASTER_KEY'), null)); } function deriveStorageKey(masterKey) { const hash = crypto_1.default.createHmac('sha256', masterKey); hash.update('Storage Service Encryption'); return hash.digest(); } function deriveStorageManifestKey(storageKey, version) { const hash = crypto_1.default.createHmac('sha256', storageKey); hash.update(`Manifest_${version.toString()}`); return hash.digest(); } const STORAGE_SERVICE_ITEM_KEY_INFO_PREFIX = '20240801_SIGNAL_STORAGE_SERVICE_ITEM_'; const STORAGE_SERVICE_ITEM_KEY_LEN = 32; function deriveStorageItemKey({ storageKey, recordIkm, key, }) { if (recordIkm === undefined) { const hash = crypto_1.default.createHmac('sha256', storageKey); hash.update(`Item_${key.toString('base64')}`); return hash.digest(); } return buffer_1.Buffer.from((0, libsignal_client_1.hkdf)(STORAGE_SERVICE_ITEM_KEY_LEN, recordIkm, buffer_1.Buffer.concat([buffer_1.Buffer.from(STORAGE_SERVICE_ITEM_KEY_INFO_PREFIX), key]), buffer_1.Buffer.alloc(0))); } function decryptAESGCM(ciphertext, key) { const iv = ciphertext.subarray(0, AESGCM_IV_SIZE); const tag = ciphertext.subarray(ciphertext.length - AUTH_TAG_SIZE); const rest = ciphertext.subarray(iv.length, ciphertext.length - tag.length); const decipher = crypto_1.default.createDecipheriv('aes-256-gcm', key, iv); decipher.setAuthTag(tag); return buffer_1.Buffer.concat([decipher.update(rest), decipher.final()]); } function encryptAESGCM(plaintext, key) { const iv = crypto_1.default.randomBytes(AESGCM_IV_SIZE); const cipher = crypto_1.default.createCipheriv('aes-256-gcm', key, iv); const ciphertext = [cipher.update(plaintext), cipher.final()]; const tag = cipher.getAuthTag(); return buffer_1.Buffer.concat([iv, ...ciphertext, tag]); } function decryptStorageManifest(storageKey, manifest) { if (!manifest.value?.length) { throw new Error('Missing manifest.value'); } const manifestKey = deriveStorageManifestKey(storageKey, manifest.version ?? 0n); const decoded = compiled_1.signalservice.ManifestRecord.decode(decryptAESGCM(buffer_1.Buffer.from(manifest.value), manifestKey)); if (decoded.version !== manifest.version) { throw new Error('manifestRecord.version != manifest.version'); } return decoded; } function encryptStorageManifest(storageKey, manifestRecord) { if (!manifestRecord.version) { throw new Error('Missing manifest.version'); } const manifestKey = deriveStorageManifestKey(storageKey, manifestRecord.version); const encrypted = encryptAESGCM(buffer_1.Buffer.from(compiled_1.signalservice.ManifestRecord.encode(manifestRecord)), manifestKey); return { version: manifestRecord.version, value: encrypted, }; } function decryptStorageItem({ storageKey, recordIkm, item, }) { if (!item.key) { throw new Error('Missing item.key'); } if (!item.value) { throw new Error('Missing item.value'); } const itemKey = deriveStorageItemKey({ storageKey, recordIkm, key: buffer_1.Buffer.from(item.key), }); return compiled_1.signalservice.StorageRecord.decode(decryptAESGCM(buffer_1.Buffer.from(item.value), itemKey)); } function encryptStorageItem({ storageKey, key, recordIkm, record, }) { const itemKey = deriveStorageItemKey({ storageKey, recordIkm, key, }); const encrypted = encryptAESGCM(buffer_1.Buffer.from(compiled_1.signalservice.StorageRecord.encode(record)), itemKey); return { key, value: encrypted, }; } function encryptProfileName(profileKey, name) { const encrypted = encryptAESGCM(buffer_1.Buffer.from(name), profileKey); return encrypted; } function generateAccessKeyVerifier(accessKey) { const zeroes = buffer_1.Buffer.alloc(32); return crypto_1.default.createHmac('sha256', accessKey).update(zeroes).digest(); } function decodePreKey({ keyId, publicKey }) { return { keyId, publicKey: libsignal_client_1.PublicKey.deserialize(buffer_1.Buffer.from(publicKey, 'base64')), }; } function decodeSignedPreKey({ keyId, publicKey, signature, }) { return { keyId, publicKey: libsignal_client_1.PublicKey.deserialize(buffer_1.Buffer.from(publicKey, 'base64')), signature: buffer_1.Buffer.from(signature, 'base64'), }; } function decodeKyberPreKey({ keyId, publicKey, signature, }) { return { keyId, publicKey: libsignal_client_1.KEMPublicKey.deserialize(buffer_1.Buffer.from(publicKey, 'base64')), signature: buffer_1.Buffer.from(signature, 'base64'), }; } function hashRemoteConfig(config) { // Not necessarily secure, but this will let us detect changes. The exact // format isn't important so long as it's deterministic. const mac = crypto_1.default.createHmac('sha256', 'remoteConfig'); return config .reduce((mac, [name, value], index) => mac .update(index.toString()) .update(name.length.toString()) .update(name) .update(value.length.toString()) .update(value), mac) .digest(); }