Move web_api.js and js/modules/crypto.js to TypeScript

This commit is contained in:
Scott Nonnenberg 2020-03-31 13:03:38 -07:00
parent 71436d18e2
commit 9ab54b9b83
29 changed files with 770 additions and 427 deletions

View File

@ -19,7 +19,7 @@ const {
saveMessage,
setAttachmentDownloadJobPending,
} = require('./data');
const { stringFromBytes } = require('./crypto');
const { stringFromBytes } = require('../../ts/Crypto');
module.exports = {
start,

View File

@ -19,7 +19,7 @@ const pify = require('pify');
const rimraf = require('rimraf');
const electronRemote = require('electron').remote;
const crypto = require('./crypto');
const crypto = require('../../ts/Crypto');
const { dialog, BrowserWindow } = electronRemote;

View File

@ -14,7 +14,7 @@ const {
set,
} = require('lodash');
const { base64ToArrayBuffer, arrayBufferToBase64 } = require('./crypto');
const { base64ToArrayBuffer, arrayBufferToBase64 } = require('../../ts/Crypto');
const MessageType = require('./types/message');
const { createBatcher } = require('../../ts/util/batcher');

View File

@ -6,7 +6,7 @@ const nodeUrl = require('url');
const LinkifyIt = require('linkify-it');
const linkify = LinkifyIt();
const { concatenateBytes, getViewOfArrayBuffer } = require('./crypto');
const { concatenateBytes, getViewOfArrayBuffer } = require('../../ts/Crypto');
module.exports = {
assembleChunks,

View File

@ -17,7 +17,7 @@ const {
intsToByteHighAndLow,
splitBytes,
trimBytes,
} = require('../crypto');
} = require('../../../ts/Crypto');
const REVOKED_CERTIFICATES = [];

View File

@ -2,7 +2,7 @@
const { bindActionCreators } = require('redux');
const Backbone = require('../../ts/backbone');
const Crypto = require('./crypto');
const Crypto = require('../../ts/Crypto');
const Data = require('./data');
const Database = require('./database');
const Emojis = require('./emojis');

View File

@ -9,3 +9,5 @@ export function downloadStickerPack(
fromSync?: boolean;
}
): Promise<void>;
export function redactPackId(packId: string): string;

View File

@ -33,7 +33,10 @@ const Queue = require('p-queue').default;
const qs = require('qs');
const { makeLookup } = require('../../ts/util/makeLookup');
const { base64ToArrayBuffer, deriveStickerPackKey } = require('./crypto');
const {
base64ToArrayBuffer,
deriveStickerPackKey,
} = require('../../ts/Crypto');
const {
addStickerPackReference,
createOrUpdateSticker,

View File

@ -2,7 +2,10 @@
const { isFunction, isNumber } = require('lodash');
const { createLastMessageUpdate } = require('../../../ts/types/Conversation');
const { arrayBufferToBase64, base64ToArrayBuffer } = require('../crypto');
const {
arrayBufferToBase64,
base64ToArrayBuffer,
} = require('../../../ts/Crypto');
async function computeHash(arraybuffer) {
const hash = await crypto.subtle.digest({ name: 'SHA-512' }, arraybuffer);

View File

@ -1321,7 +1321,7 @@ MessageReceiver.prototype.extend({
throw new Error('Failure: Ask sender to update Signal and resend.');
}
const data = await textsecure.crypto.decryptAttachment(
const paddedData = await textsecure.crypto.decryptAttachment(
encrypted,
window.Signal.Crypto.base64ToArrayBuffer(key),
window.Signal.Crypto.base64ToArrayBuffer(digest)
@ -1329,15 +1329,15 @@ MessageReceiver.prototype.extend({
if (!_.isNumber(size)) {
throw new Error(
`downloadAttachment: Size was not provided, actual size was ${data.byteLength}`
`downloadAttachment: Size was not provided, actual size was ${paddedData.byteLength}`
);
}
const typedArray = window.Signal.Crypto.getFirstBytes(data, size);
const data = window.Signal.Crypto.getFirstBytes(paddedData, size);
return {
..._.omit(attachment, 'digest', 'key'),
data: window.Signal.Crypto.typedArrayToArrayBuffer(typedArray),
data,
};
},
handleAttachment(attachment) {

View File

@ -63,6 +63,8 @@
"dependencies": {
"@journeyapps/sqlcipher": "https://github.com/scottnonnenberg-signal/node-sqlcipher.git#b10f232fac62ba7f8775c9e086bb5558fe7d948b",
"@sindresorhus/is": "0.8.0",
"@types/node-fetch": "2.5.5",
"@types/websocket": "1.0.0",
"array-move": "2.1.0",
"backbone": "1.3.3",
"blob-util": "1.3.0",

View File

@ -222,7 +222,7 @@ try {
window.nodeSetImmediate = setImmediate;
const { initialize: initializeWebAPI } = require('./js/modules/web_api');
const { initialize: initializeWebAPI } = require('./ts/WebAPI');
window.WebAPI = initializeWebAPI({
url: config.serverUrl,
@ -308,17 +308,13 @@ try {
function wrapWithPromise(fn) {
return (...args) => Promise.resolve(fn(...args));
}
function typedArrayToArrayBuffer(typedArray) {
const { buffer, byteOffset, byteLength } = typedArray;
return buffer.slice(byteOffset, byteLength + byteOffset);
}
const externalCurve = {
generateKeyPair: () => {
const { privKey, pubKey } = curve.generateKeyPair();
return {
privKey: typedArrayToArrayBuffer(privKey),
pubKey: typedArrayToArrayBuffer(pubKey),
privKey: window.Signal.Crypto.typedArrayToArrayBuffer(privKey),
pubKey: window.Signal.Crypto.typedArrayToArrayBuffer(pubKey),
};
},
createKeyPair: incomingKey => {
@ -326,8 +322,8 @@ try {
const { privKey, pubKey } = curve.createKeyPair(incomingKeyBuffer);
return {
privKey: typedArrayToArrayBuffer(privKey),
pubKey: typedArrayToArrayBuffer(pubKey),
privKey: window.Signal.Crypto.typedArrayToArrayBuffer(privKey),
pubKey: window.Signal.Crypto.typedArrayToArrayBuffer(pubKey),
};
},
calculateAgreement: (pubKey, privKey) => {
@ -336,7 +332,7 @@ try {
const buffer = curve.calculateAgreement(pubKeyBuffer, privKeyBuffer);
return typedArrayToArrayBuffer(buffer);
return window.Signal.Crypto.typedArrayToArrayBuffer(buffer);
},
verifySignature: (pubKey, message, signature) => {
const pubKeyBuffer = Buffer.from(pubKey);
@ -357,7 +353,7 @@ try {
const buffer = curve.calculateSignature(privKeyBuffer, messageBuffer);
return typedArrayToArrayBuffer(buffer);
return window.Signal.Crypto.typedArrayToArrayBuffer(buffer);
},
validatePubKeyFormat: pubKey => {
const pubKeyBuffer = Buffer.from(pubKey);

View File

@ -6,7 +6,7 @@ const { readFile } = require('fs');
const config = require('url').parse(window.location.toString(), true).query;
const { noop, uniqBy } = require('lodash');
const pMap = require('p-map');
const { deriveStickerPackKey } = require('../js/modules/crypto');
const { deriveStickerPackKey } = require('../ts/Crypto');
const { makeGetter } = require('../preload_utils');
const { dialog } = remote;
@ -29,7 +29,7 @@ const Signal = require('../js/modules/signal');
window.Signal = Signal.setup({});
const { initialize: initializeWebAPI } = require('../js/modules/web_api');
const { initialize: initializeWebAPI } = require('../ts/WebAPI');
const WebAPI = initializeWebAPI({
url: config.serverUrl,
@ -143,8 +143,7 @@ window.encryptAndUpload = async (
async function encrypt(data, key, iv) {
const { ciphertext } = await window.textsecure.crypto.encryptAttachment(
// Convert Node Buffer to ArrayBuffer
window.Signal.Crypto.concatenateBytes(data),
window.Signal.Crypto.typedArrayToArrayBuffer(data),
key,
iv
);

View File

@ -1,79 +1,48 @@
/* eslint-env browser */
/* global dcodeIO, libsignal */
// Yep, we're doing some bitwise stuff in an encryption-related file
// tslint:disable no-bitwise
/* eslint-disable camelcase, no-bitwise */
// We want some extra variables to make the decrption algorithm easier to understand
// tslint:disable no-unnecessary-local-variable
module.exports = {
arrayBufferToBase64,
typedArrayToArrayBuffer,
base64ToArrayBuffer,
bytesFromHexString,
bytesFromString,
concatenateBytes,
constantTimeEqual,
decryptAesCtr,
decryptDeviceName,
decryptAttachment,
decryptFile,
decryptSymmetric,
deriveAccessKey,
deriveStickerPackKey,
encryptAesCtr,
encryptDeviceName,
encryptAttachment,
encryptFile,
encryptSymmetric,
fromEncodedBinaryToArrayBuffer,
getAccessKeyVerifier,
getFirstBytes,
getRandomBytes,
getRandomValue,
getViewOfArrayBuffer,
getZeroes,
hexFromBytes,
highBitsToInt,
hmacSha256,
intsToByteHighAndLow,
splitBytes,
stringFromBytes,
trimBytes,
verifyAccessKey,
};
// Seems that tslint doesn't understand that crypto.subtle.importKey does return a Promise
// tslint:disable await-promise
function typedArrayToArrayBuffer(typedArray) {
export function typedArrayToArrayBuffer(typedArray: Uint8Array): ArrayBuffer {
const { buffer, byteOffset, byteLength } = typedArray;
return buffer.slice(byteOffset, byteLength + byteOffset);
// tslint:disable-next-line no-unnecessary-type-assertion
return buffer.slice(byteOffset, byteLength + byteOffset) as ArrayBuffer;
}
function arrayBufferToBase64(arrayBuffer) {
return dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('base64');
export function arrayBufferToBase64(arrayBuffer: ArrayBuffer) {
return window.dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('base64');
}
function base64ToArrayBuffer(base64string) {
return dcodeIO.ByteBuffer.wrap(base64string, 'base64').toArrayBuffer();
export function base64ToArrayBuffer(base64string: string) {
return window.dcodeIO.ByteBuffer.wrap(base64string, 'base64').toArrayBuffer();
}
function fromEncodedBinaryToArrayBuffer(key) {
return dcodeIO.ByteBuffer.wrap(key, 'binary').toArrayBuffer();
export function fromEncodedBinaryToArrayBuffer(key: string) {
return window.dcodeIO.ByteBuffer.wrap(key, 'binary').toArrayBuffer();
}
function bytesFromString(string) {
return dcodeIO.ByteBuffer.wrap(string, 'utf8').toArrayBuffer();
export function bytesFromString(string: string) {
return window.dcodeIO.ByteBuffer.wrap(string, 'utf8').toArrayBuffer();
}
function stringFromBytes(buffer) {
return dcodeIO.ByteBuffer.wrap(buffer).toString('utf8');
export function stringFromBytes(buffer: ArrayBuffer) {
return window.dcodeIO.ByteBuffer.wrap(buffer).toString('utf8');
}
function hexFromBytes(buffer) {
return dcodeIO.ByteBuffer.wrap(buffer).toString('hex');
export function hexFromBytes(buffer: ArrayBuffer) {
return window.dcodeIO.ByteBuffer.wrap(buffer).toString('hex');
}
function bytesFromHexString(string) {
return dcodeIO.ByteBuffer.wrap(string, 'hex').toArrayBuffer();
export function bytesFromHexString(string: string) {
return window.dcodeIO.ByteBuffer.wrap(string, 'hex').toArrayBuffer();
}
async function deriveStickerPackKey(packKey) {
export async function deriveStickerPackKey(packKey: ArrayBuffer) {
const salt = getZeroes(32);
const info = bytesFromString('Sticker Pack');
const [part1, part2] = await libsignal.HKDF.deriveSecrets(
const [part1, part2] = await window.libsignal.HKDF.deriveSecrets(
packKey,
salt,
info
@ -84,10 +53,13 @@ async function deriveStickerPackKey(packKey) {
// High-level Operations
async function encryptDeviceName(deviceName, identityPublic) {
export async function encryptDeviceName(
deviceName: string,
identityPublic: ArrayBuffer
) {
const plaintext = bytesFromString(deviceName);
const ephemeralKeyPair = await libsignal.KeyHelper.generateIdentityKeyPair();
const masterSecret = await libsignal.Curve.async.calculateAgreement(
const ephemeralKeyPair = await window.libsignal.KeyHelper.generateIdentityKeyPair();
const masterSecret = await window.libsignal.Curve.async.calculateAgreement(
identityPublic,
ephemeralKeyPair.privKey
);
@ -108,11 +80,19 @@ async function encryptDeviceName(deviceName, identityPublic) {
};
}
async function decryptDeviceName(
{ ephemeralPublic, syntheticIv, ciphertext } = {},
identityPrivate
export async function decryptDeviceName(
{
ephemeralPublic,
syntheticIv,
ciphertext,
}: {
ephemeralPublic: ArrayBuffer;
syntheticIv: ArrayBuffer;
ciphertext: ArrayBuffer;
},
identityPrivate: ArrayBuffer
) {
const masterSecret = await libsignal.Curve.async.calculateAgreement(
const masterSecret = await window.libsignal.Curve.async.calculateAgreement(
ephemeralPublic,
identityPrivate
);
@ -134,38 +114,58 @@ async function decryptDeviceName(
}
// Path structure: 'fa/facdf99c22945b1c9393345599a276f4b36ad7ccdc8c2467f5441b742c2d11fa'
function getAttachmentLabel(path) {
export function getAttachmentLabel(path: string) {
const filename = path.slice(3);
return base64ToArrayBuffer(filename);
}
const PUB_KEY_LENGTH = 32;
async function encryptAttachment(staticPublicKey, path, plaintext) {
export async function encryptAttachment(
staticPublicKey: ArrayBuffer,
path: string,
plaintext: ArrayBuffer
) {
const uniqueId = getAttachmentLabel(path);
return encryptFile(staticPublicKey, uniqueId, plaintext);
}
async function decryptAttachment(staticPrivateKey, path, data) {
export async function decryptAttachment(
staticPrivateKey: ArrayBuffer,
path: string,
data: ArrayBuffer
) {
const uniqueId = getAttachmentLabel(path);
return decryptFile(staticPrivateKey, uniqueId, data);
}
async function encryptFile(staticPublicKey, uniqueId, plaintext) {
const ephemeralKeyPair = await libsignal.KeyHelper.generateIdentityKeyPair();
const agreement = await libsignal.Curve.async.calculateAgreement(
export async function encryptFile(
staticPublicKey: ArrayBuffer,
uniqueId: ArrayBuffer,
plaintext: ArrayBuffer
) {
const ephemeralKeyPair = await window.libsignal.KeyHelper.generateIdentityKeyPair();
const agreement = await window.libsignal.Curve.async.calculateAgreement(
staticPublicKey,
ephemeralKeyPair.privKey
);
const key = await hmacSha256(agreement, uniqueId);
const prefix = ephemeralKeyPair.pubKey.slice(1);
return concatenateBytes(prefix, await encryptSymmetric(key, plaintext));
}
async function decryptFile(staticPrivateKey, uniqueId, data) {
export async function decryptFile(
staticPrivateKey: ArrayBuffer,
uniqueId: ArrayBuffer,
data: ArrayBuffer
) {
const ephemeralPublicKey = getFirstBytes(data, PUB_KEY_LENGTH);
const ciphertext = _getBytes(data, PUB_KEY_LENGTH, data.byteLength);
const agreement = await libsignal.Curve.async.calculateAgreement(
const agreement = await window.libsignal.Curve.async.calculateAgreement(
ephemeralPublicKey,
staticPrivateKey
);
@ -175,21 +175,24 @@ async function decryptFile(staticPrivateKey, uniqueId, data) {
return decryptSymmetric(key, ciphertext);
}
async function deriveAccessKey(profileKey) {
export async function deriveAccessKey(profileKey: ArrayBuffer) {
const iv = getZeroes(12);
const plaintext = getZeroes(16);
const accessKey = await _encrypt_aes_gcm(profileKey, iv, plaintext);
return getFirstBytes(accessKey, 16);
}
async function getAccessKeyVerifier(accessKey) {
export async function getAccessKeyVerifier(accessKey: ArrayBuffer) {
const plaintext = getZeroes(32);
const hmac = await hmacSha256(accessKey, plaintext);
return hmac;
return hmacSha256(accessKey, plaintext);
}
async function verifyAccessKey(accessKey, theirVerifier) {
export async function verifyAccessKey(
accessKey: ArrayBuffer,
theirVerifier: ArrayBuffer
) {
const ourVerifier = await getAccessKeyVerifier(accessKey);
if (constantTimeEqual(ourVerifier, theirVerifier)) {
@ -203,7 +206,10 @@ const IV_LENGTH = 16;
const MAC_LENGTH = 16;
const NONCE_LENGTH = 16;
async function encryptSymmetric(key, plaintext) {
export async function encryptSymmetric(
key: ArrayBuffer,
plaintext: ArrayBuffer
) {
const iv = getZeroes(IV_LENGTH);
const nonce = getRandomBytes(NONCE_LENGTH);
@ -220,7 +226,7 @@ async function encryptSymmetric(key, plaintext) {
return concatenateBytes(nonce, cipherText, mac);
}
async function decryptSymmetric(key, data) {
export async function decryptSymmetric(key: ArrayBuffer, data: ArrayBuffer) {
const iv = getZeroes(IV_LENGTH);
const nonce = getFirstBytes(data, NONCE_LENGTH);
@ -247,23 +253,25 @@ async function decryptSymmetric(key, data) {
return _decrypt_aes256_CBC_PKCSPadding(cipherKey, iv, cipherText);
}
function constantTimeEqual(left, right) {
export function constantTimeEqual(left: ArrayBuffer, right: ArrayBuffer) {
if (left.byteLength !== right.byteLength) {
return false;
}
let result = 0;
const ta1 = new Uint8Array(left);
const ta2 = new Uint8Array(right);
for (let i = 0, max = left.byteLength; i < max; i += 1) {
const max = left.byteLength;
for (let i = 0; i < max; i += 1) {
// eslint-disable-next-line no-bitwise
result |= ta1[i] ^ ta2[i];
}
return result === 0;
}
// Encryption
async function hmacSha256(key, plaintext) {
export async function hmacSha256(key: ArrayBuffer, plaintext: ArrayBuffer) {
const algorithm = {
name: 'HMAC',
hash: 'SHA-256',
@ -273,7 +281,7 @@ async function hmacSha256(key, plaintext) {
const cryptoKey = await window.crypto.subtle.importKey(
'raw',
key,
algorithm,
algorithm as any,
extractable,
['sign']
);
@ -281,7 +289,11 @@ async function hmacSha256(key, plaintext) {
return window.crypto.subtle.sign(algorithm, cryptoKey, plaintext);
}
async function _encrypt_aes256_CBC_PKCSPadding(key, iv, plaintext) {
export async function _encrypt_aes256_CBC_PKCSPadding(
key: ArrayBuffer,
iv: ArrayBuffer,
plaintext: ArrayBuffer
) {
const algorithm = {
name: 'AES-CBC',
iv,
@ -291,7 +303,7 @@ async function _encrypt_aes256_CBC_PKCSPadding(key, iv, plaintext) {
const cryptoKey = await window.crypto.subtle.importKey(
'raw',
key,
algorithm,
algorithm as any,
extractable,
['encrypt']
);
@ -299,7 +311,11 @@ async function _encrypt_aes256_CBC_PKCSPadding(key, iv, plaintext) {
return window.crypto.subtle.encrypt(algorithm, cryptoKey, plaintext);
}
async function _decrypt_aes256_CBC_PKCSPadding(key, iv, plaintext) {
export async function _decrypt_aes256_CBC_PKCSPadding(
key: ArrayBuffer,
iv: ArrayBuffer,
plaintext: ArrayBuffer
) {
const algorithm = {
name: 'AES-CBC',
iv,
@ -309,14 +325,19 @@ async function _decrypt_aes256_CBC_PKCSPadding(key, iv, plaintext) {
const cryptoKey = await window.crypto.subtle.importKey(
'raw',
key,
algorithm,
algorithm as any,
extractable,
['decrypt']
);
return window.crypto.subtle.decrypt(algorithm, cryptoKey, plaintext);
}
async function encryptAesCtr(key, plaintext, counter) {
export async function encryptAesCtr(
key: ArrayBuffer,
plaintext: ArrayBuffer,
counter: ArrayBuffer
) {
const extractable = false;
const algorithm = {
name: 'AES-CTR',
@ -327,7 +348,7 @@ async function encryptAesCtr(key, plaintext, counter) {
const cryptoKey = await crypto.subtle.importKey(
'raw',
key,
algorithm,
algorithm as any,
extractable,
['encrypt']
);
@ -341,7 +362,11 @@ async function encryptAesCtr(key, plaintext, counter) {
return ciphertext;
}
async function decryptAesCtr(key, ciphertext, counter) {
export async function decryptAesCtr(
key: ArrayBuffer,
ciphertext: ArrayBuffer,
counter: ArrayBuffer
) {
const extractable = false;
const algorithm = {
name: 'AES-CTR',
@ -352,7 +377,7 @@ async function decryptAesCtr(key, ciphertext, counter) {
const cryptoKey = await crypto.subtle.importKey(
'raw',
key,
algorithm,
algorithm as any,
extractable,
['decrypt']
);
@ -361,10 +386,15 @@ async function decryptAesCtr(key, ciphertext, counter) {
cryptoKey,
ciphertext
);
return plaintext;
}
async function _encrypt_aes_gcm(key, iv, plaintext) {
export async function _encrypt_aes_gcm(
key: ArrayBuffer,
iv: ArrayBuffer,
plaintext: ArrayBuffer
) {
const algorithm = {
name: 'AES-GCM',
iv,
@ -374,32 +404,35 @@ async function _encrypt_aes_gcm(key, iv, plaintext) {
const cryptoKey = await crypto.subtle.importKey(
'raw',
key,
algorithm,
algorithm as any,
extractable,
['encrypt']
);
return crypto.subtle.encrypt(algorithm, cryptoKey, plaintext);
}
// Utility
function getRandomBytes(n) {
export function getRandomBytes(n: number) {
const bytes = new Uint8Array(n);
window.crypto.getRandomValues(bytes);
return bytes;
return typedArrayToArrayBuffer(bytes);
}
function getRandomValue(low, high) {
export function getRandomValue(low: number, high: number): number {
const diff = high - low;
const bytes = new Uint32Array(1);
window.crypto.getRandomValues(bytes);
// Because high and low are inclusive
const mod = diff + 1;
return (bytes[0] % mod) + low;
}
function getZeroes(n) {
export function getZeroes(n: number) {
const result = new Uint8Array(n);
const value = 0;
@ -407,28 +440,36 @@ function getZeroes(n) {
const endExclusive = n;
result.fill(value, startIndex, endExclusive);
return result;
return typedArrayToArrayBuffer(result);
}
function highBitsToInt(byte) {
export function highBitsToInt(byte: number): number {
return (byte & 0xff) >> 4;
}
function intsToByteHighAndLow(highValue, lowValue) {
export function intsToByteHighAndLow(
highValue: number,
lowValue: number
): number {
return ((highValue << 4) | lowValue) & 0xff;
}
function trimBytes(buffer, length) {
export function trimBytes(buffer: ArrayBuffer, length: number) {
return getFirstBytes(buffer, length);
}
function getViewOfArrayBuffer(buffer, start, finish) {
export function getViewOfArrayBuffer(
buffer: ArrayBuffer,
start: number,
finish: number
) {
const source = new Uint8Array(buffer);
const result = source.slice(start, finish);
return result.buffer;
}
function concatenateBytes(...elements) {
export function concatenateBytes(...elements: Array<ArrayBuffer | Uint8Array>) {
const length = elements.reduce(
(total, element) => total + element.byteLength,
0
@ -437,7 +478,8 @@ function concatenateBytes(...elements) {
const result = new Uint8Array(length);
let position = 0;
for (let i = 0, max = elements.length; i < max; i += 1) {
const max = elements.length;
for (let i = 0; i < max; i += 1) {
const element = new Uint8Array(elements[i]);
result.set(element, position);
position += element.byteLength;
@ -446,10 +488,13 @@ function concatenateBytes(...elements) {
throw new Error('problem concatenating!');
}
return result.buffer;
return typedArrayToArrayBuffer(result);
}
function splitBytes(buffer, ...lengths) {
export function splitBytes(
buffer: ArrayBuffer,
...lengths: Array<number>
): Array<ArrayBuffer> {
const total = lengths.reduce((acc, length) => acc + length, 0);
if (total !== buffer.byteLength) {
@ -462,27 +507,34 @@ function splitBytes(buffer, ...lengths) {
const results = [];
let position = 0;
for (let i = 0, max = lengths.length; i < max; i += 1) {
const max = lengths.length;
for (let i = 0; i < max; i += 1) {
const length = lengths[i];
const result = new Uint8Array(length);
const section = source.slice(position, position + length);
result.set(section);
position += result.byteLength;
results.push(result);
results.push(typedArrayToArrayBuffer(result));
}
return results;
}
function getFirstBytes(data, n) {
export function getFirstBytes(data: ArrayBuffer, n: number) {
const source = new Uint8Array(data);
return source.subarray(0, n);
return typedArrayToArrayBuffer(source.subarray(0, n));
}
// Internal-only
function _getBytes(data, start, n) {
export function _getBytes(
data: ArrayBuffer | Uint8Array,
start: number,
n: number
) {
const source = new Uint8Array(data);
return source.subarray(start, start + n);
return typedArrayToArrayBuffer(source.subarray(start, start + n));
}

File diff suppressed because it is too large Load Diff

7
ts/proxy-agent.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
declare module 'proxy-agent' {
import { Agent } from 'http';
export default class ProxyAgent extends Agent {
constructor(url: string);
}
}

View File

@ -11,17 +11,8 @@ type NetworkActions = {
const REFRESH_INTERVAL = 5000;
interface ShimmedWindow extends Window {
log: {
info: (...args: any) => void;
};
}
const unknownWindow = window as unknown;
const shimmedWindow = unknownWindow as ShimmedWindow;
export function initializeNetworkObserver(networkActions: NetworkActions) {
const { log } = shimmedWindow;
const { log } = window;
log.info(`Initializing network observer every ${REFRESH_INTERVAL}ms`);
const refresh = () => {

View File

@ -1,4 +1,3 @@
export function trigger(name: string, param1?: any, param2?: any) {
// @ts-ignore
window.Whisper.events.trigger(name, param1, param2);
}

View File

@ -1,12 +1,5 @@
interface ShimmedWindow extends Window {
getSocketStatus: () => number;
}
const unknownWindow = window as unknown;
const shimmedWindow = unknownWindow as ShimmedWindow;
export function getSocketStatus() {
const { getSocketStatus: getMessageReceiverStatus } = shimmedWindow;
const { getSocketStatus: getMessageReceiverStatus } = window;
return getMessageReceiverStatus();
}

View File

@ -1,9 +1,7 @@
export async function put(key: string, value: any) {
// @ts-ignore
return window.storage.put(key, value);
export function put(key: string, value: any) {
window.storage.put(key, value);
}
export async function remove(key: string) {
// @ts-ignore
return window.storage.remove(key);
export function remove(key: string) {
window.storage.remove(key);
}

View File

@ -1,52 +1,9 @@
type LoggerType = (...args: Array<any>) => void;
type TextSecureType = {
storage: {
user: {
getNumber: () => string;
};
get: (item: string) => any;
};
messaging: {
sendStickerPackSync: (
operations: Array<{
packId: string;
packKey: string;
installed: boolean;
}>,
options: Object
) => Promise<void>;
};
};
type ConversationControllerType = {
prepareForSend: (
id: string,
options: Object
) => {
wrap: (promise: Promise<any>) => Promise<void>;
sendOptions: Object;
};
};
interface ShimmedWindow extends Window {
log: {
error: LoggerType;
info: LoggerType;
};
textsecure: TextSecureType;
ConversationController: ConversationControllerType;
}
const unknownWindow = window as unknown;
const shimmedWindow = unknownWindow as ShimmedWindow;
export function sendStickerPackSync(
packId: string,
packKey: string,
installed: boolean
) {
const { ConversationController, textsecure, log } = shimmedWindow;
const { ConversationController, textsecure, log } = window;
const ourNumber = textsecure.storage.user.getNumber();
const { wrap, sendOptions } = ConversationController.prepareForSend(
ourNumber,

View File

@ -11,7 +11,7 @@ export type ItemsStateType = {
type ItemPutAction = {
type: 'items/PUT';
payload: Promise<void>;
payload: null;
};
type ItemPutExternalAction = {
@ -24,7 +24,7 @@ type ItemPutExternalAction = {
type ItemRemoveAction = {
type: 'items/REMOVE';
payload: Promise<void>;
payload: null;
};
type ItemRemoveExternalAction = {
@ -54,9 +54,11 @@ export const actions = {
};
function putItem(key: string, value: any): ItemPutAction {
storageShim.put(key, value);
return {
type: 'items/PUT',
payload: storageShim.put(key, value),
payload: null,
};
}
@ -71,9 +73,11 @@ function putItemExternal(key: string, value: any): ItemPutExternalAction {
}
function removeItem(key: string): ItemRemoveAction {
storageShim.remove(key);
return {
type: 'items/REMOVE',
payload: storageShim.remove(key),
payload: null,
};
}

View File

@ -1,21 +1,9 @@
interface ShimmedWindow extends Window {
getExpiration: () => string;
log: {
info: (...args: any) => void;
error: (...args: any) => void;
};
}
const unknownWindow = window as unknown;
const shimmedWindow = unknownWindow as ShimmedWindow;
// @ts-ignore
const env = window.getEnvironment();
const NINETY_ONE_DAYS = 86400 * 91 * 1000;
export function hasExpired() {
const { getExpiration, log } = shimmedWindow;
const { getExpiration, log } = window;
let buildExpiration = 0;

View File

@ -255,7 +255,7 @@
"rule": "jQuery-load(",
"path": "js/modules/stickers.js",
"line": "async function load() {",
"lineNumber": 74,
"lineNumber": 77,
"reasonCategory": "falseMatch",
"updated": "2019-04-26T17:48:30.675Z"
},
@ -11613,7 +11613,7 @@
"rule": "jQuery-wrap(",
"path": "ts/shims/textsecure.js",
"line": " wrap(textsecure.messaging.sendStickerPackSync([",
"lineNumber": 13,
"lineNumber": 11,
"reasonCategory": "falseMatch",
"updated": "2020-02-07T19:52:28.522Z"
},
@ -11621,7 +11621,7 @@
"rule": "jQuery-wrap(",
"path": "ts/shims/textsecure.ts",
"line": " wrap(",
"lineNumber": 64,
"lineNumber": 21,
"reasonCategory": "falseMatch",
"updated": "2020-02-07T19:52:28.522Z"
}

View File

@ -54,9 +54,10 @@ const excludedFiles = [
// High-traffic files in our project
'^js/models/messages.js',
'^js/modules/crypto.js',
'^js/views/conversation_view.js',
'^js/background.js',
'^ts/Crypto.js',
'^ts/Crypto.ts',
// Generated files
'^js/components.js',

View File

@ -5,23 +5,19 @@ export function markEverDone() {
export function markDone() {
markEverDone();
// @ts-ignore
window.storage.put('chromiumRegistrationDone', '');
}
export function remove() {
// @ts-ignore
window.storage.remove('chromiumRegistrationDone');
}
export function isDone() {
// @ts-ignore
// tslint:disable-next-line no-backbone-get-set-outside-model
return window.storage.get('chromiumRegistrationDone') === '';
}
export function everDone() {
// @ts-ignore
// tslint:disable-next-line no-backbone-get-set-outside-model
return window.storage.get('chromiumRegistrationDoneEver') === '' || isDone();
}

98
ts/window.d.ts vendored Normal file
View File

@ -0,0 +1,98 @@
// Captures the globals put in place by preload.js, background.js and others
declare global {
interface Window {
dcodeIO: DCodeIOType;
getExpiration: () => string;
getEnvironment: () => string;
getSocketStatus: () => number;
libsignal: LibSignalType;
log: {
info: LoggerType;
warn: LoggerType;
error: LoggerType;
};
storage: {
put: (key: string, value: any) => void;
remove: (key: string) => void;
get: (key: string) => any;
};
textsecure: TextSecureType;
ConversationController: ConversationControllerType;
Whisper: WhisperType;
}
}
export type ConversationControllerType = {
prepareForSend: (
id: string,
options: Object
) => {
wrap: (promise: Promise<any>) => Promise<void>;
sendOptions: Object;
};
};
export type DCodeIOType = {
ByteBuffer: {
wrap: (
value: any,
type?: string
) => {
toString: (type: string) => string;
toArrayBuffer: () => ArrayBuffer;
};
};
};
export type LibSignalType = {
KeyHelper: {
generateIdentityKeyPair: () => Promise<{
privKey: ArrayBuffer;
pubKey: ArrayBuffer;
}>;
};
Curve: {
async: {
calculateAgreement: (
publicKey: ArrayBuffer,
privateKey: ArrayBuffer
) => Promise<ArrayBuffer>;
};
};
HKDF: {
deriveSecrets: (
packKey: ArrayBuffer,
salt: ArrayBuffer,
info: ArrayBuffer
) => Promise<Array<ArrayBuffer>>;
};
};
export type LoggerType = (...args: Array<any>) => void;
export type TextSecureType = {
storage: {
user: {
getNumber: () => string;
};
get: (key: string) => any;
};
messaging: {
sendStickerPackSync: (
operations: Array<{
packId: string;
packKey: string;
installed: boolean;
}>,
options: Object
) => Promise<void>;
};
};
export type WhisperType = {
events: {
trigger: (name: string, param1: any, param2: any) => void;
};
};

View File

@ -7,6 +7,7 @@
"align": false,
"newline-per-chained-call": false,
"array-type": [true, "generic"],
"number-literal-format": false,
// Preferred by Prettier:
"arrow-parens": [true, "ban-single-arg-parens"],

View File

@ -2066,6 +2066,14 @@
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.0.0.tgz#a3014921991066193f6c8e47290d4d598dfd19e6"
integrity sha512-ZS0vBV7Jn5Z/Q4T3VXauEKMDCV8nWOtJJg90OsDylkYJiQwcWtKuLzohWzrthBkerUF7DLMmJcwOPEP0i/AOXw==
"@types/node-fetch@2.5.5":
version "2.5.5"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.5.tgz#cd264e20a81f4600a6c52864d38e7fef72485e92"
integrity sha512-IWwjsyYjGw+em3xTvWVQi5MgYKbRs0du57klfTaZkv/B24AEQ/p/IopNeqIYNy3EsfHOpg8ieQSDomPcsYMHpA==
dependencies:
"@types/node" "*"
form-data "^3.0.0"
"@types/node@*":
version "11.12.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-11.12.2.tgz#d7f302e74b10e9801d52852137f652d9ee235da8"
@ -2317,6 +2325,13 @@
"@types/webpack-sources" "*"
source-map "^0.6.0"
"@types/websocket@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/websocket/-/websocket-1.0.0.tgz#828c794b0a50949ad061aa311af1009934197e4b"
integrity sha512-MLr8hDM8y7vvdAdnoDEP5LotRoYJj7wgT6mWzCUQH/gHqzS4qcnOT/K4dhC0WimWIUiA3Arj9QAJGGKNRiRZKA==
dependencies:
"@types/node" "*"
"@types/yargs-parser@*":
version "15.0.0"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
@ -4757,6 +4772,13 @@ combined-stream@^1.0.5, combined-stream@~1.0.5:
dependencies:
delayed-stream "~1.0.0"
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
comma-separated-tokens@^1.0.0:
version "1.0.7"
resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.7.tgz#419cd7fb3258b1ed838dc0953167a25e152f5b59"
@ -7380,6 +7402,15 @@ form-data@2.3.2, form-data@~2.3.2:
combined-stream "1.0.6"
mime-types "^2.1.12"
form-data@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682"
integrity sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
form-data@~2.1.1:
version "2.1.4"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1"