Uint8Array migration
This commit is contained in:
parent
daf75190b8
commit
4ef0bf96cc
137 changed files with 2202 additions and 3170 deletions
|
@ -2,19 +2,17 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { randomBytes } from 'crypto';
|
||||
import { basename, extname, join, normalize, relative } from 'path';
|
||||
import { basename, join, normalize, relative } from 'path';
|
||||
import { app, dialog, shell, remote } from 'electron';
|
||||
|
||||
import fastGlob from 'fast-glob';
|
||||
import glob from 'glob';
|
||||
import pify from 'pify';
|
||||
import fse from 'fs-extra';
|
||||
import { map, isArrayBuffer, isString } from 'lodash';
|
||||
import { map, isTypedArray, isString } from 'lodash';
|
||||
import normalizePath from 'normalize-path';
|
||||
import sanitizeFilename from 'sanitize-filename';
|
||||
import getGuid from 'uuid/v4';
|
||||
|
||||
import { typedArrayToArrayBuffer } from '../ts/Crypto';
|
||||
import { isPathInside } from '../ts/util/isPathInside';
|
||||
import { isWindows } from '../ts/OS';
|
||||
import { writeWindowsZoneIdentifier } from '../ts/util/windowsZoneIdentifier';
|
||||
|
@ -123,12 +121,12 @@ export const clearTempPath = (userDataPath: string): Promise<void> => {
|
|||
|
||||
export const createReader = (
|
||||
root: string
|
||||
): ((relativePath: string) => Promise<ArrayBuffer>) => {
|
||||
): ((relativePath: string) => Promise<Uint8Array>) => {
|
||||
if (!isString(root)) {
|
||||
throw new TypeError("'root' must be a path");
|
||||
}
|
||||
|
||||
return async (relativePath: string): Promise<ArrayBuffer> => {
|
||||
return async (relativePath: string): Promise<Uint8Array> => {
|
||||
if (!isString(relativePath)) {
|
||||
throw new TypeError("'relativePath' must be a string");
|
||||
}
|
||||
|
@ -138,8 +136,7 @@ export const createReader = (
|
|||
if (!isPathInside(normalized, root)) {
|
||||
throw new Error('Invalid relative path');
|
||||
}
|
||||
const buffer = await fse.readFile(normalized);
|
||||
return typedArrayToArrayBuffer(buffer);
|
||||
return fse.readFile(normalized);
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -203,48 +200,9 @@ export const copyIntoAttachmentsDirectory = (
|
|||
};
|
||||
};
|
||||
|
||||
export const writeToDownloads = async ({
|
||||
data,
|
||||
name,
|
||||
}: {
|
||||
data: ArrayBuffer;
|
||||
name: string;
|
||||
}): Promise<{ fullPath: string; name: string }> => {
|
||||
const appToUse = getApp();
|
||||
const downloadsPath =
|
||||
appToUse.getPath('downloads') || appToUse.getPath('home');
|
||||
const sanitized = sanitizeFilename(name);
|
||||
|
||||
const extension = extname(sanitized);
|
||||
const fileBasename = basename(sanitized, extension);
|
||||
const getCandidateName = (count: number) =>
|
||||
`${fileBasename} (${count})${extension}`;
|
||||
|
||||
const existingFiles = await fse.readdir(downloadsPath);
|
||||
let candidateName = sanitized;
|
||||
let count = 0;
|
||||
while (existingFiles.includes(candidateName)) {
|
||||
count += 1;
|
||||
candidateName = getCandidateName(count);
|
||||
}
|
||||
|
||||
const target = join(downloadsPath, candidateName);
|
||||
const normalized = normalize(target);
|
||||
if (!isPathInside(normalized, downloadsPath)) {
|
||||
throw new Error('Invalid filename!');
|
||||
}
|
||||
|
||||
await writeWithAttributes(normalized, data);
|
||||
|
||||
return {
|
||||
fullPath: normalized,
|
||||
name: candidateName,
|
||||
};
|
||||
};
|
||||
|
||||
async function writeWithAttributes(
|
||||
target: string,
|
||||
data: ArrayBuffer
|
||||
data: Uint8Array
|
||||
): Promise<void> {
|
||||
await fse.writeFile(target, Buffer.from(data));
|
||||
|
||||
|
@ -292,7 +250,7 @@ export const saveAttachmentToDisk = async ({
|
|||
data,
|
||||
name,
|
||||
}: {
|
||||
data: ArrayBuffer;
|
||||
data: Uint8Array;
|
||||
name: string;
|
||||
}): Promise<null | { fullPath: string; name: string }> => {
|
||||
const dialogToUse = dialog || remote.dialog;
|
||||
|
@ -327,20 +285,20 @@ export const openFileInFolder = async (target: string): Promise<void> => {
|
|||
|
||||
export const createWriterForNew = (
|
||||
root: string
|
||||
): ((arrayBuffer: ArrayBuffer) => Promise<string>) => {
|
||||
): ((bytes: Uint8Array) => Promise<string>) => {
|
||||
if (!isString(root)) {
|
||||
throw new TypeError("'root' must be a path");
|
||||
}
|
||||
|
||||
return async (arrayBuffer: ArrayBuffer) => {
|
||||
if (!isArrayBuffer(arrayBuffer)) {
|
||||
throw new TypeError("'arrayBuffer' must be an array buffer");
|
||||
return async (bytes: Uint8Array) => {
|
||||
if (!isTypedArray(bytes)) {
|
||||
throw new TypeError("'bytes' must be a typed array");
|
||||
}
|
||||
|
||||
const name = createName();
|
||||
const relativePath = getRelativePath(name);
|
||||
return createWriterForExisting(root)({
|
||||
data: arrayBuffer,
|
||||
data: bytes,
|
||||
path: relativePath,
|
||||
});
|
||||
};
|
||||
|
@ -348,27 +306,27 @@ export const createWriterForNew = (
|
|||
|
||||
export const createWriterForExisting = (
|
||||
root: string
|
||||
): ((options: { data: ArrayBuffer; path: string }) => Promise<string>) => {
|
||||
): ((options: { data: Uint8Array; path: string }) => Promise<string>) => {
|
||||
if (!isString(root)) {
|
||||
throw new TypeError("'root' must be a path");
|
||||
}
|
||||
|
||||
return async ({
|
||||
data: arrayBuffer,
|
||||
data: bytes,
|
||||
path: relativePath,
|
||||
}: {
|
||||
data: ArrayBuffer;
|
||||
data: Uint8Array;
|
||||
path: string;
|
||||
}): Promise<string> => {
|
||||
if (!isString(relativePath)) {
|
||||
throw new TypeError("'relativePath' must be a path");
|
||||
}
|
||||
|
||||
if (!isArrayBuffer(arrayBuffer)) {
|
||||
if (!isTypedArray(bytes)) {
|
||||
throw new TypeError("'arrayBuffer' must be an array buffer");
|
||||
}
|
||||
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
const buffer = Buffer.from(bytes);
|
||||
const absolutePath = join(root, relativePath);
|
||||
const normalized = normalize(absolutePath);
|
||||
if (!isPathInside(normalized, root)) {
|
||||
|
|
|
@ -130,13 +130,8 @@ const searchSelectors = require('../../ts/state/selectors/search');
|
|||
|
||||
// Types
|
||||
const AttachmentType = require('../../ts/types/Attachment');
|
||||
const VisualAttachment = require('./types/visual_attachment');
|
||||
const EmbeddedContact = require('../../ts/types/EmbeddedContact');
|
||||
const Conversation = require('./types/conversation');
|
||||
const Errors = require('../../ts/types/errors');
|
||||
const VisualAttachment = require('../../ts/types/VisualAttachment');
|
||||
const MessageType = require('./types/message');
|
||||
const MIME = require('../../ts/types/MIME');
|
||||
const SettingsType = require('../../ts/types/Settings');
|
||||
const { UUID } = require('../../ts/types/UUID');
|
||||
const { Address } = require('../../ts/types/Address');
|
||||
const { QualifiedAddress } = require('../../ts/types/QualifiedAddress');
|
||||
|
@ -417,14 +412,9 @@ exports.setup = (options = {}) => {
|
|||
};
|
||||
|
||||
const Types = {
|
||||
Attachment: AttachmentType,
|
||||
EmbeddedContact,
|
||||
Conversation,
|
||||
Errors,
|
||||
Message: MessageType,
|
||||
MIME,
|
||||
Settings: SettingsType,
|
||||
VisualAttachment,
|
||||
|
||||
// Mostly for debugging
|
||||
UUID,
|
||||
Address,
|
||||
QualifiedAddress,
|
||||
|
|
|
@ -1,167 +0,0 @@
|
|||
// Copyright 2018-2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
/* global window */
|
||||
|
||||
const { isFunction, isNumber } = require('lodash');
|
||||
const {
|
||||
arrayBufferToBase64,
|
||||
base64ToArrayBuffer,
|
||||
computeHash,
|
||||
} = require('../../../ts/Crypto');
|
||||
|
||||
function buildAvatarUpdater({ field }) {
|
||||
return async (conversation, data, options = {}) => {
|
||||
if (!conversation) {
|
||||
return conversation;
|
||||
}
|
||||
|
||||
const avatar = conversation[field];
|
||||
const {
|
||||
deleteAttachmentData,
|
||||
doesAttachmentExist,
|
||||
writeNewAttachmentData,
|
||||
} = options;
|
||||
if (!isFunction(deleteAttachmentData)) {
|
||||
throw new Error(
|
||||
'Conversation.buildAvatarUpdater: deleteAttachmentData must be a function'
|
||||
);
|
||||
}
|
||||
if (!isFunction(doesAttachmentExist)) {
|
||||
throw new Error(
|
||||
'Conversation.buildAvatarUpdater: deleteAttachmentData must be a function'
|
||||
);
|
||||
}
|
||||
if (!isFunction(writeNewAttachmentData)) {
|
||||
throw new Error(
|
||||
'Conversation.buildAvatarUpdater: writeNewAttachmentData must be a function'
|
||||
);
|
||||
}
|
||||
|
||||
const newHash = await computeHash(data);
|
||||
|
||||
if (!avatar || !avatar.hash) {
|
||||
return {
|
||||
...conversation,
|
||||
[field]: {
|
||||
hash: newHash,
|
||||
path: await writeNewAttachmentData(data),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const { hash, path } = avatar;
|
||||
const exists = await doesAttachmentExist(path);
|
||||
if (!exists) {
|
||||
window.SignalWindow.log.warn(
|
||||
`Conversation.buildAvatarUpdater: attachment ${path} did not exist`
|
||||
);
|
||||
}
|
||||
|
||||
if (exists && hash === newHash) {
|
||||
return conversation;
|
||||
}
|
||||
|
||||
await deleteAttachmentData(path);
|
||||
|
||||
return {
|
||||
...conversation,
|
||||
[field]: {
|
||||
hash: newHash,
|
||||
path: await writeNewAttachmentData(data),
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const maybeUpdateAvatar = buildAvatarUpdater({ field: 'avatar' });
|
||||
const maybeUpdateProfileAvatar = buildAvatarUpdater({
|
||||
field: 'profileAvatar',
|
||||
});
|
||||
|
||||
async function upgradeToVersion2(conversation, options) {
|
||||
if (conversation.version >= 2) {
|
||||
return conversation;
|
||||
}
|
||||
|
||||
const { writeNewAttachmentData } = options;
|
||||
if (!isFunction(writeNewAttachmentData)) {
|
||||
throw new Error(
|
||||
'Conversation.upgradeToVersion2: writeNewAttachmentData must be a function'
|
||||
);
|
||||
}
|
||||
|
||||
let { avatar, profileAvatar, profileKey } = conversation;
|
||||
|
||||
if (avatar && avatar.data) {
|
||||
avatar = {
|
||||
hash: await computeHash(avatar.data),
|
||||
path: await writeNewAttachmentData(avatar.data),
|
||||
};
|
||||
}
|
||||
|
||||
if (profileAvatar && profileAvatar.data) {
|
||||
profileAvatar = {
|
||||
hash: await computeHash(profileAvatar.data),
|
||||
path: await writeNewAttachmentData(profileAvatar.data),
|
||||
};
|
||||
}
|
||||
|
||||
if (profileKey && profileKey.byteLength) {
|
||||
profileKey = arrayBufferToBase64(profileKey);
|
||||
}
|
||||
|
||||
return {
|
||||
...conversation,
|
||||
version: 2,
|
||||
avatar,
|
||||
profileAvatar,
|
||||
profileKey,
|
||||
};
|
||||
}
|
||||
|
||||
async function migrateConversation(conversation, options = {}) {
|
||||
if (!conversation) {
|
||||
return conversation;
|
||||
}
|
||||
if (!isNumber(conversation.version)) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
conversation.version = 1;
|
||||
}
|
||||
|
||||
return upgradeToVersion2(conversation, options);
|
||||
}
|
||||
|
||||
async function deleteExternalFiles(conversation, options = {}) {
|
||||
if (!conversation) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { deleteAttachmentData } = options;
|
||||
if (!isFunction(deleteAttachmentData)) {
|
||||
throw new Error(
|
||||
'Conversation.buildAvatarUpdater: deleteAttachmentData must be a function'
|
||||
);
|
||||
}
|
||||
|
||||
const { avatar, profileAvatar } = conversation;
|
||||
|
||||
if (avatar && avatar.path) {
|
||||
await deleteAttachmentData(avatar.path);
|
||||
}
|
||||
|
||||
if (profileAvatar && profileAvatar.path) {
|
||||
await deleteAttachmentData(profileAvatar.path);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
arrayBufferToBase64,
|
||||
base64ToArrayBuffer,
|
||||
computeHash,
|
||||
|
||||
deleteExternalFiles,
|
||||
maybeUpdateAvatar,
|
||||
maybeUpdateProfileAvatar,
|
||||
migrateConversation,
|
||||
};
|
|
@ -6,7 +6,7 @@ const { isFunction, isObject, isString, omit } = require('lodash');
|
|||
const Contact = require('../../../ts/types/EmbeddedContact');
|
||||
const Attachment = require('../../../ts/types/Attachment');
|
||||
const Errors = require('../../../ts/types/errors');
|
||||
const SchemaVersion = require('./schema_version');
|
||||
const SchemaVersion = require('../../../ts/types/SchemaVersion');
|
||||
const {
|
||||
initializeAttachmentMetadata,
|
||||
} = require('../../../ts/types/message/initializeAttachmentMetadata');
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
// Copyright 2018-2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
const { isNumber } = require('lodash');
|
||||
|
||||
exports.isValid = value => isNumber(value) && value >= 0;
|
|
@ -16,9 +16,6 @@ module.exports = {
|
|||
},
|
||||
globals: {
|
||||
assert: true,
|
||||
assertEqualArrayBuffers: true,
|
||||
getString: true,
|
||||
hexToArrayBuffer: true,
|
||||
stringToArrayBuffer: true,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -46,18 +46,6 @@ mocha.reporter(SauceReporter);
|
|||
/*
|
||||
* global helpers for tests
|
||||
*/
|
||||
window.assertEqualArrayBuffers = (ab1, ab2) => {
|
||||
assert.deepEqual(new Uint8Array(ab1), new Uint8Array(ab2));
|
||||
};
|
||||
|
||||
window.hexToArrayBuffer = str => {
|
||||
const ret = new ArrayBuffer(str.length / 2);
|
||||
const array = new Uint8Array(ret);
|
||||
for (let i = 0; i < str.length / 2; i += 1) {
|
||||
array[i] = parseInt(str.substr(i * 2, 2), 16);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
window.Whisper = window.Whisper || {};
|
||||
window.Whisper.events = {
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
// Copyright 2015-2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
describe('Helpers', () => {
|
||||
describe('ArrayBuffer->String conversion', () => {
|
||||
it('works', () => {
|
||||
const b = new ArrayBuffer(3);
|
||||
const a = new Uint8Array(b);
|
||||
a[0] = 0;
|
||||
a[1] = 255;
|
||||
a[2] = 128;
|
||||
assert.equal(window.textsecure.utils.getString(b), '\x00\xff\x80');
|
||||
});
|
||||
});
|
||||
|
||||
describe('stringToArrayBuffer', () => {
|
||||
it('returns ArrayBuffer when passed string', () => {
|
||||
const anArrayBuffer = new ArrayBuffer(1);
|
||||
const typedArray = new Uint8Array(anArrayBuffer);
|
||||
typedArray[0] = 'a'.charCodeAt(0);
|
||||
assertEqualArrayBuffers(
|
||||
window.textsecure.utils.stringToArrayBuffer('a'),
|
||||
anArrayBuffer
|
||||
);
|
||||
});
|
||||
it('throws an error when passed a non string', () => {
|
||||
const notStringable = [{}, undefined, null, new ArrayBuffer()];
|
||||
notStringable.forEach(notString => {
|
||||
assert.throw(() => {
|
||||
window.textsecure.utils.stringToArrayBuffer(notString);
|
||||
}, Error);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -31,7 +31,6 @@
|
|||
data-cover
|
||||
></script>
|
||||
|
||||
<script type="text/javascript" src="helpers_test.js"></script>
|
||||
<script type="text/javascript" src="task_with_timeout_test.js"></script>
|
||||
<script type="text/javascript" src="account_manager_test.js"></script>
|
||||
<script type="text/javascript" src="sendmessage_test.js"></script>
|
||||
|
|
|
@ -367,7 +367,6 @@ try {
|
|||
|
||||
window.Backbone = require('backbone');
|
||||
window.textsecure = require('./ts/textsecure').default;
|
||||
window.synchronousCrypto = require('./ts/util/synchronousCrypto');
|
||||
|
||||
window.WebAPI = window.textsecure.WebAPI.initialize({
|
||||
url: config.serverUrl,
|
||||
|
|
|
@ -9,7 +9,6 @@ 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 client = require('@signalapp/signal-client');
|
||||
|
||||
// It is important to call this as early as possible
|
||||
require('../ts/windows/context');
|
||||
|
@ -55,20 +54,6 @@ const Signal = require('../js/modules/signal');
|
|||
window.Signal = Signal.setup({});
|
||||
window.textsecure = require('../ts/textsecure').default;
|
||||
|
||||
window.libsignal = window.libsignal || {};
|
||||
window.libsignal.HKDF = {};
|
||||
window.libsignal.HKDF.deriveSecrets = (input, salt, info) => {
|
||||
const hkdf = client.HKDF.new(3);
|
||||
const output = hkdf.deriveSecrets(
|
||||
3 * 32,
|
||||
Buffer.from(input),
|
||||
Buffer.from(info),
|
||||
Buffer.from(salt)
|
||||
);
|
||||
return [output.slice(0, 32), output.slice(32, 64), output.slice(64, 96)];
|
||||
};
|
||||
window.synchronousCrypto = require('../ts/util/synchronousCrypto');
|
||||
|
||||
const { initialize: initializeWebAPI } = require('../ts/textsecure/WebAPI');
|
||||
const {
|
||||
getAnimatedPngDataIfExists,
|
||||
|
@ -206,7 +191,7 @@ window.encryptAndUpload = async (
|
|||
const { value: password } = passwordItem;
|
||||
|
||||
const packKey = window.Signal.Crypto.getRandomBytes(32);
|
||||
const encryptionKey = await deriveStickerPackKey(packKey);
|
||||
const encryptionKey = deriveStickerPackKey(packKey);
|
||||
const iv = window.Signal.Crypto.getRandomBytes(16);
|
||||
|
||||
const server = WebAPI.connect({
|
||||
|
@ -265,9 +250,7 @@ window.encryptAndUpload = async (
|
|||
|
||||
async function encrypt(data, key, iv) {
|
||||
const { ciphertext } = await window.textsecure.crypto.encryptAttachment(
|
||||
data instanceof ArrayBuffer
|
||||
? data
|
||||
: window.Signal.Crypto.typedArrayToArrayBuffer(data),
|
||||
data,
|
||||
key,
|
||||
iv
|
||||
);
|
||||
|
|
|
@ -11,10 +11,7 @@ module.exports = {
|
|||
|
||||
globals: {
|
||||
assert: true,
|
||||
assertEqualArrayBuffers: true,
|
||||
getString: true,
|
||||
hexToArrayBuffer: true,
|
||||
stringToArrayBuffer: true,
|
||||
},
|
||||
|
||||
parserOptions: {
|
||||
|
|
|
@ -51,18 +51,6 @@ Whisper.Database.id = 'test';
|
|||
/*
|
||||
* global helpers for tests
|
||||
*/
|
||||
window.assertEqualArrayBuffers = (ab1, ab2) => {
|
||||
assert.deepEqual(new Uint8Array(ab1), new Uint8Array(ab2));
|
||||
};
|
||||
|
||||
window.hexToArrayBuffer = str => {
|
||||
const ret = new ArrayBuffer(str.length / 2);
|
||||
const array = new Uint8Array(ret);
|
||||
for (let i = 0; i < str.length / 2; i += 1) {
|
||||
array[i] = parseInt(str.substr(i * 2, 2), 16);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
function deleteIndexedDB() {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
|
|
@ -9,7 +9,7 @@ const { assert } = require('chai');
|
|||
const { app } = require('electron');
|
||||
|
||||
const Attachments = require('../../app/attachments');
|
||||
const { stringToArrayBuffer } = require('../../ts/util/stringToArrayBuffer');
|
||||
const Bytes = require('../../ts/Bytes');
|
||||
|
||||
const PREFIX_LENGTH = 2;
|
||||
const NUM_SEPARATORS = 1;
|
||||
|
@ -28,7 +28,7 @@ describe('Attachments', () => {
|
|||
});
|
||||
|
||||
it('should write file to disk and return path', async () => {
|
||||
const input = stringToArrayBuffer('test string');
|
||||
const input = Bytes.fromString('test string');
|
||||
const tempDirectory = path.join(
|
||||
tempRootDirectory,
|
||||
'Attachments_createWriterForNew'
|
||||
|
@ -57,7 +57,7 @@ describe('Attachments', () => {
|
|||
});
|
||||
|
||||
it('should write file to disk on given path and return path', async () => {
|
||||
const input = stringToArrayBuffer('test string');
|
||||
const input = Bytes.fromString('test string');
|
||||
const tempDirectory = path.join(
|
||||
tempRootDirectory,
|
||||
'Attachments_createWriterForExisting'
|
||||
|
@ -82,7 +82,7 @@ describe('Attachments', () => {
|
|||
});
|
||||
|
||||
it('throws if relative path goes higher than root', async () => {
|
||||
const input = stringToArrayBuffer('test string');
|
||||
const input = Bytes.fromString('test string');
|
||||
const tempDirectory = path.join(
|
||||
tempRootDirectory,
|
||||
'Attachments_createWriterForExisting'
|
||||
|
@ -124,7 +124,7 @@ describe('Attachments', () => {
|
|||
Attachments.createName()
|
||||
);
|
||||
const fullPath = path.join(tempDirectory, relativePath);
|
||||
const input = stringToArrayBuffer('test string');
|
||||
const input = Bytes.fromString('test string');
|
||||
|
||||
const inputBuffer = Buffer.from(input);
|
||||
await fse.ensureFile(fullPath);
|
||||
|
@ -260,7 +260,7 @@ describe('Attachments', () => {
|
|||
Attachments.createName()
|
||||
);
|
||||
const fullPath = path.join(tempDirectory, relativePath);
|
||||
const input = stringToArrayBuffer('test string');
|
||||
const input = Bytes.fromString('test string');
|
||||
|
||||
const inputBuffer = Buffer.from(input);
|
||||
await fse.ensureFile(fullPath);
|
||||
|
|
|
@ -6,7 +6,7 @@ const sinon = require('sinon');
|
|||
|
||||
const Message = require('../../../js/modules/types/message');
|
||||
const { SignalService } = require('../../../ts/protobuf');
|
||||
const { stringToArrayBuffer } = require('../../../ts/util/stringToArrayBuffer');
|
||||
const Bytes = require('../../../ts/Bytes');
|
||||
|
||||
describe('Message', () => {
|
||||
const logger = {
|
||||
|
@ -60,7 +60,7 @@ describe('Message', () => {
|
|||
attachments: [
|
||||
{
|
||||
path: 'ab/abcdefghi',
|
||||
data: stringToArrayBuffer('It’s easy if you try'),
|
||||
data: Bytes.fromString('It’s easy if you try'),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -78,9 +78,9 @@ describe('Message', () => {
|
|||
|
||||
const writeExistingAttachmentData = attachment => {
|
||||
assert.equal(attachment.path, 'ab/abcdefghi');
|
||||
assert.deepEqual(
|
||||
attachment.data,
|
||||
stringToArrayBuffer('It’s easy if you try')
|
||||
assert.strictEqual(
|
||||
Bytes.toString(attachment.data),
|
||||
'It’s easy if you try'
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -101,7 +101,7 @@ describe('Message', () => {
|
|||
{
|
||||
thumbnail: {
|
||||
path: 'ab/abcdefghi',
|
||||
data: stringToArrayBuffer('It’s easy if you try'),
|
||||
data: Bytes.fromString('It’s easy if you try'),
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -126,9 +126,9 @@ describe('Message', () => {
|
|||
|
||||
const writeExistingAttachmentData = attachment => {
|
||||
assert.equal(attachment.path, 'ab/abcdefghi');
|
||||
assert.deepEqual(
|
||||
attachment.data,
|
||||
stringToArrayBuffer('It’s easy if you try')
|
||||
assert.strictEqual(
|
||||
Bytes.toString(attachment.data),
|
||||
'It’s easy if you try'
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -151,7 +151,7 @@ describe('Message', () => {
|
|||
isProfile: false,
|
||||
avatar: {
|
||||
path: 'ab/abcdefghi',
|
||||
data: stringToArrayBuffer('It’s easy if you try'),
|
||||
data: Bytes.fromString('It’s easy if you try'),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -177,9 +177,9 @@ describe('Message', () => {
|
|||
|
||||
const writeExistingAttachmentData = attachment => {
|
||||
assert.equal(attachment.path, 'ab/abcdefghi');
|
||||
assert.deepEqual(
|
||||
attachment.data,
|
||||
stringToArrayBuffer('It’s easy if you try')
|
||||
assert.strictEqual(
|
||||
Bytes.toString(attachment.data),
|
||||
'It’s easy if you try'
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -268,7 +268,7 @@ describe('Message', () => {
|
|||
{
|
||||
contentType: 'audio/aac',
|
||||
flags: SignalService.AttachmentPointer.Flags.VOICE_MESSAGE,
|
||||
data: stringToArrayBuffer('It’s easy if you try'),
|
||||
data: Bytes.fromString('It’s easy if you try'),
|
||||
fileName: 'test\u202Dfig.exe',
|
||||
size: 1111,
|
||||
},
|
||||
|
@ -292,12 +292,13 @@ describe('Message', () => {
|
|||
contact: [],
|
||||
};
|
||||
|
||||
const expectedAttachmentData = stringToArrayBuffer(
|
||||
'It’s easy if you try'
|
||||
);
|
||||
const expectedAttachmentData = 'It’s easy if you try';
|
||||
const context = {
|
||||
writeNewAttachmentData: async attachmentData => {
|
||||
assert.deepEqual(attachmentData, expectedAttachmentData);
|
||||
assert.strictEqual(
|
||||
Bytes.toString(attachmentData),
|
||||
expectedAttachmentData
|
||||
);
|
||||
return 'abc/abcdefg';
|
||||
},
|
||||
getRegionCode: () => 'US',
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
// Copyright 2018-2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
require('mocha-testcheck').install();
|
||||
const { assert } = require('chai');
|
||||
|
||||
const SchemaVersion = require('../../../js/modules/types/schema_version');
|
||||
|
||||
describe('SchemaVersion', () => {
|
||||
describe('isValid', () => {
|
||||
check.it('should return true for positive integers', gen.posInt, input => {
|
||||
assert.isTrue(SchemaVersion.isValid(input));
|
||||
});
|
||||
|
||||
check.it(
|
||||
'should return false for any other value',
|
||||
gen.primitive.suchThat(value => typeof value !== 'number' || value < 0),
|
||||
input => {
|
||||
assert.isFalse(SchemaVersion.isValid(input));
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
|
@ -245,7 +245,7 @@ export class ConversationController {
|
|||
|
||||
try {
|
||||
if (isGroupV1(conversation.attributes)) {
|
||||
await maybeDeriveGroupV2Id(conversation);
|
||||
maybeDeriveGroupV2Id(conversation);
|
||||
}
|
||||
await saveConversation(conversation.attributes);
|
||||
} catch (error) {
|
||||
|
@ -577,8 +577,7 @@ export class ConversationController {
|
|||
|
||||
let groupV2Id: undefined | string;
|
||||
if (isGroupV1(conversation.attributes)) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await maybeDeriveGroupV2Id(conversation);
|
||||
maybeDeriveGroupV2Id(conversation);
|
||||
groupV2Id = conversation.get('derivedGroupV2Id');
|
||||
assert(
|
||||
groupV2Id,
|
||||
|
@ -836,7 +835,7 @@ export class ConversationController {
|
|||
// Hydrate contactCollection, now that initial fetch is complete
|
||||
conversation.fetchContacts();
|
||||
|
||||
const isChanged = await maybeDeriveGroupV2Id(conversation);
|
||||
const isChanged = maybeDeriveGroupV2Id(conversation);
|
||||
if (isChanged) {
|
||||
updateConversation(conversation.attributes);
|
||||
}
|
||||
|
|
865
ts/Crypto.ts
865
ts/Crypto.ts
File diff suppressed because it is too large
Load diff
86
ts/Curve.ts
86
ts/Curve.ts
|
@ -3,7 +3,8 @@
|
|||
|
||||
import * as client from '@signalapp/signal-client';
|
||||
|
||||
import { constantTimeEqual, typedArrayToArrayBuffer } from './Crypto';
|
||||
import * as Bytes from './Bytes';
|
||||
import { constantTimeEqual } from './Crypto';
|
||||
import {
|
||||
KeyPairType,
|
||||
CompatPreKeyType,
|
||||
|
@ -26,9 +27,9 @@ export function generateSignedPreKey(
|
|||
}
|
||||
|
||||
if (
|
||||
!(identityKeyPair.privKey instanceof ArrayBuffer) ||
|
||||
!(identityKeyPair.privKey instanceof Uint8Array) ||
|
||||
identityKeyPair.privKey.byteLength !== 32 ||
|
||||
!(identityKeyPair.pubKey instanceof ArrayBuffer) ||
|
||||
!(identityKeyPair.pubKey instanceof Uint8Array) ||
|
||||
identityKeyPair.pubKey.byteLength !== 33
|
||||
) {
|
||||
throw new TypeError(
|
||||
|
@ -63,24 +64,13 @@ export function generateKeyPair(): KeyPairType {
|
|||
const pubKey = privKey.getPublicKey();
|
||||
|
||||
return {
|
||||
privKey: typedArrayToArrayBuffer(privKey.serialize()),
|
||||
pubKey: typedArrayToArrayBuffer(pubKey.serialize()),
|
||||
privKey: privKey.serialize(),
|
||||
pubKey: pubKey.serialize(),
|
||||
};
|
||||
}
|
||||
|
||||
export function copyArrayBuffer(source: ArrayBuffer): ArrayBuffer {
|
||||
const sourceArray = new Uint8Array(source);
|
||||
|
||||
const target = new ArrayBuffer(source.byteLength);
|
||||
const targetArray = new Uint8Array(target);
|
||||
|
||||
targetArray.set(sourceArray, 0);
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
export function createKeyPair(incomingKey: ArrayBuffer): KeyPairType {
|
||||
const copy = copyArrayBuffer(incomingKey);
|
||||
export function createKeyPair(incomingKey: Uint8Array): KeyPairType {
|
||||
const copy = new Uint8Array(incomingKey);
|
||||
clampPrivateKey(copy);
|
||||
if (!constantTimeEqual(copy, incomingKey)) {
|
||||
log.warn('createKeyPair: incoming private key was not clamped!');
|
||||
|
@ -96,32 +86,31 @@ export function createKeyPair(incomingKey: ArrayBuffer): KeyPairType {
|
|||
const pubKey = privKey.getPublicKey();
|
||||
|
||||
return {
|
||||
privKey: typedArrayToArrayBuffer(privKey.serialize()),
|
||||
pubKey: typedArrayToArrayBuffer(pubKey.serialize()),
|
||||
privKey: privKey.serialize(),
|
||||
pubKey: pubKey.serialize(),
|
||||
};
|
||||
}
|
||||
|
||||
export function calculateAgreement(
|
||||
pubKey: ArrayBuffer,
|
||||
privKey: ArrayBuffer
|
||||
): ArrayBuffer {
|
||||
pubKey: Uint8Array,
|
||||
privKey: Uint8Array
|
||||
): Uint8Array {
|
||||
const privKeyBuffer = Buffer.from(privKey);
|
||||
|
||||
const pubKeyObj = client.PublicKey.deserialize(
|
||||
Buffer.concat([
|
||||
Buffer.from([0x05]),
|
||||
Buffer.from(validatePubKeyFormat(pubKey)),
|
||||
])
|
||||
Buffer.from(
|
||||
Bytes.concatenate([new Uint8Array([0x05]), validatePubKeyFormat(pubKey)])
|
||||
)
|
||||
);
|
||||
const privKeyObj = client.PrivateKey.deserialize(privKeyBuffer);
|
||||
const sharedSecret = privKeyObj.agree(pubKeyObj);
|
||||
return typedArrayToArrayBuffer(sharedSecret);
|
||||
return sharedSecret;
|
||||
}
|
||||
|
||||
export function verifySignature(
|
||||
pubKey: ArrayBuffer,
|
||||
message: ArrayBuffer,
|
||||
signature: ArrayBuffer
|
||||
pubKey: Uint8Array,
|
||||
message: Uint8Array,
|
||||
signature: Uint8Array
|
||||
): boolean {
|
||||
const pubKeyBuffer = Buffer.from(pubKey);
|
||||
const messageBuffer = Buffer.from(message);
|
||||
|
@ -134,22 +123,21 @@ export function verifySignature(
|
|||
}
|
||||
|
||||
export function calculateSignature(
|
||||
privKey: ArrayBuffer,
|
||||
plaintext: ArrayBuffer
|
||||
): ArrayBuffer {
|
||||
privKey: Uint8Array,
|
||||
plaintext: Uint8Array
|
||||
): Uint8Array {
|
||||
const privKeyBuffer = Buffer.from(privKey);
|
||||
const plaintextBuffer = Buffer.from(plaintext);
|
||||
|
||||
const privKeyObj = client.PrivateKey.deserialize(privKeyBuffer);
|
||||
const signature = privKeyObj.sign(plaintextBuffer);
|
||||
return typedArrayToArrayBuffer(signature);
|
||||
return signature;
|
||||
}
|
||||
|
||||
export function validatePubKeyFormat(pubKey: ArrayBuffer): ArrayBuffer {
|
||||
function validatePubKeyFormat(pubKey: Uint8Array): Uint8Array {
|
||||
if (
|
||||
pubKey === undefined ||
|
||||
((pubKey.byteLength !== 33 || new Uint8Array(pubKey)[0] !== 5) &&
|
||||
pubKey.byteLength !== 32)
|
||||
((pubKey.byteLength !== 33 || pubKey[0] !== 5) && pubKey.byteLength !== 32)
|
||||
) {
|
||||
throw new Error('Invalid public key');
|
||||
}
|
||||
|
@ -160,18 +148,16 @@ export function validatePubKeyFormat(pubKey: ArrayBuffer): ArrayBuffer {
|
|||
return pubKey;
|
||||
}
|
||||
|
||||
export function setPublicKeyTypeByte(publicKey: ArrayBuffer): void {
|
||||
const byteArray = new Uint8Array(publicKey);
|
||||
byteArray[0] = 5;
|
||||
export function setPublicKeyTypeByte(publicKey: Uint8Array): void {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
publicKey[0] = 5;
|
||||
}
|
||||
|
||||
export function clampPrivateKey(privateKey: ArrayBuffer): void {
|
||||
const byteArray = new Uint8Array(privateKey);
|
||||
|
||||
// eslint-disable-next-line no-bitwise
|
||||
byteArray[0] &= 248;
|
||||
// eslint-disable-next-line no-bitwise
|
||||
byteArray[31] &= 127;
|
||||
// eslint-disable-next-line no-bitwise
|
||||
byteArray[31] |= 64;
|
||||
export function clampPrivateKey(privateKey: Uint8Array): void {
|
||||
// eslint-disable-next-line no-bitwise, no-param-reassign
|
||||
privateKey[0] &= 248;
|
||||
// eslint-disable-next-line no-bitwise, no-param-reassign
|
||||
privateKey[31] &= 127;
|
||||
// eslint-disable-next-line no-bitwise, no-param-reassign
|
||||
privateKey[31] |= 64;
|
||||
}
|
||||
|
|
|
@ -27,8 +27,6 @@ import { Address } from './types/Address';
|
|||
import { QualifiedAddress } from './types/QualifiedAddress';
|
||||
import type { UUID } from './types/UUID';
|
||||
|
||||
import { typedArrayToArrayBuffer } from './Crypto';
|
||||
|
||||
import { Zone } from './util/Zone';
|
||||
|
||||
function encodeAddress(address: ProtocolAddress): Address {
|
||||
|
@ -148,7 +146,7 @@ export class IdentityKeys extends IdentityKeyStore {
|
|||
|
||||
async saveIdentity(name: ProtocolAddress, key: PublicKey): Promise<boolean> {
|
||||
const encodedAddress = encodeAddress(name);
|
||||
const publicKey = typedArrayToArrayBuffer(key.serialize());
|
||||
const publicKey = key.serialize();
|
||||
|
||||
// Pass `zone` to let `saveIdentity` archive sibling sessions when identity
|
||||
// key changes.
|
||||
|
@ -166,7 +164,7 @@ export class IdentityKeys extends IdentityKeyStore {
|
|||
direction: Direction
|
||||
): Promise<boolean> {
|
||||
const encodedAddress = encodeAddress(name);
|
||||
const publicKey = typedArrayToArrayBuffer(key.serialize());
|
||||
const publicKey = key.serialize();
|
||||
|
||||
return window.textsecure.storage.protocol.isTrustedIdentity(
|
||||
encodedAddress,
|
||||
|
|
|
@ -17,12 +17,8 @@ import {
|
|||
SignedPreKeyRecord,
|
||||
} from '@signalapp/signal-client';
|
||||
|
||||
import {
|
||||
constantTimeEqual,
|
||||
fromEncodedBinaryToArrayBuffer,
|
||||
typedArrayToArrayBuffer,
|
||||
base64ToArrayBuffer,
|
||||
} from './Crypto';
|
||||
import * as Bytes from './Bytes';
|
||||
import { constantTimeEqual } from './Crypto';
|
||||
import { assert, strictAssert } from './util/assert';
|
||||
import { handleMessageSend } from './util/handleMessageSend';
|
||||
import { isNotNil } from './util/isNotNil';
|
||||
|
@ -30,7 +26,7 @@ import { Zone } from './util/Zone';
|
|||
import { isMoreRecentThan } from './util/timestamp';
|
||||
import {
|
||||
sessionRecordToProtobuf,
|
||||
sessionStructureToArrayBuffer,
|
||||
sessionStructureToBytes,
|
||||
} from './util/sessionTranslation';
|
||||
import {
|
||||
DeviceType,
|
||||
|
@ -81,7 +77,7 @@ function validateVerifiedStatus(status: number): boolean {
|
|||
|
||||
const identityKeySchema = z.object({
|
||||
id: z.string(),
|
||||
publicKey: z.instanceof(ArrayBuffer),
|
||||
publicKey: z.instanceof(Uint8Array),
|
||||
firstUse: z.boolean(),
|
||||
timestamp: z.number().refine((value: number) => value % 1 === 0 && value > 0),
|
||||
verified: z.number().refine(validateVerifiedStatus),
|
||||
|
@ -171,13 +167,13 @@ export function hydrateSignedPreKey(
|
|||
export function freezeSession(session: SessionRecord): string {
|
||||
return session.serialize().toString('base64');
|
||||
}
|
||||
export function freezePublicKey(publicKey: PublicKey): ArrayBuffer {
|
||||
return typedArrayToArrayBuffer(publicKey.serialize());
|
||||
export function freezePublicKey(publicKey: PublicKey): Uint8Array {
|
||||
return publicKey.serialize();
|
||||
}
|
||||
export function freezePreKey(preKey: PreKeyRecord): KeyPairType {
|
||||
const keyPair = {
|
||||
pubKey: typedArrayToArrayBuffer(preKey.publicKey().serialize()),
|
||||
privKey: typedArrayToArrayBuffer(preKey.privateKey().serialize()),
|
||||
pubKey: preKey.publicKey().serialize(),
|
||||
privKey: preKey.privateKey().serialize(),
|
||||
};
|
||||
return keyPair;
|
||||
}
|
||||
|
@ -185,8 +181,8 @@ export function freezeSignedPreKey(
|
|||
signedPreKey: SignedPreKeyRecord
|
||||
): KeyPairType {
|
||||
const keyPair = {
|
||||
pubKey: typedArrayToArrayBuffer(signedPreKey.publicKey().serialize()),
|
||||
privKey: typedArrayToArrayBuffer(signedPreKey.privateKey().serialize()),
|
||||
pubKey: signedPreKey.publicKey().serialize(),
|
||||
privKey: signedPreKey.privateKey().serialize(),
|
||||
};
|
||||
return keyPair;
|
||||
}
|
||||
|
@ -260,8 +256,8 @@ export class SignalProtocolStore extends EventsMixin {
|
|||
for (const key of Object.keys(map.value)) {
|
||||
const { privKey, pubKey } = map.value[key];
|
||||
this.ourIdentityKeys.set(new UUID(key).toString(), {
|
||||
privKey: base64ToArrayBuffer(privKey),
|
||||
pubKey: base64ToArrayBuffer(pubKey),
|
||||
privKey: Bytes.fromBase64(privKey),
|
||||
pubKey: Bytes.fromBase64(pubKey),
|
||||
});
|
||||
}
|
||||
})(),
|
||||
|
@ -607,7 +603,7 @@ export class SignalProtocolStore extends EventsMixin {
|
|||
return entry.item;
|
||||
}
|
||||
|
||||
const item = SenderKeyRecord.deserialize(entry.fromDB.data);
|
||||
const item = SenderKeyRecord.deserialize(Buffer.from(entry.fromDB.data));
|
||||
this.senderKeys.set(id, {
|
||||
hydrated: true,
|
||||
item,
|
||||
|
@ -960,7 +956,7 @@ export class SignalProtocolStore extends EventsMixin {
|
|||
localUserData
|
||||
);
|
||||
const record = SessionRecord.deserialize(
|
||||
Buffer.from(sessionStructureToArrayBuffer(sessionProto))
|
||||
Buffer.from(sessionStructureToBytes(sessionProto))
|
||||
);
|
||||
|
||||
await this.storeSession(QualifiedAddress.parse(session.id), record, {
|
||||
|
@ -1425,7 +1421,7 @@ export class SignalProtocolStore extends EventsMixin {
|
|||
|
||||
async isTrustedIdentity(
|
||||
encodedAddress: Address,
|
||||
publicKey: ArrayBuffer,
|
||||
publicKey: Uint8Array,
|
||||
direction: number
|
||||
): Promise<boolean> {
|
||||
if (!this.identityKeys) {
|
||||
|
@ -1463,7 +1459,7 @@ export class SignalProtocolStore extends EventsMixin {
|
|||
}
|
||||
|
||||
isTrustedForSending(
|
||||
publicKey: ArrayBuffer,
|
||||
publicKey: Uint8Array,
|
||||
identityRecord?: IdentityKeyType
|
||||
): boolean {
|
||||
if (!identityRecord) {
|
||||
|
@ -1493,7 +1489,7 @@ export class SignalProtocolStore extends EventsMixin {
|
|||
return true;
|
||||
}
|
||||
|
||||
async loadIdentityKey(uuid: UUID): Promise<ArrayBuffer | undefined> {
|
||||
async loadIdentityKey(uuid: UUID): Promise<Uint8Array | undefined> {
|
||||
if (uuid === null || uuid === undefined) {
|
||||
throw new Error('loadIdentityKey: uuid was undefined/null');
|
||||
}
|
||||
|
@ -1522,7 +1518,7 @@ export class SignalProtocolStore extends EventsMixin {
|
|||
|
||||
async saveIdentity(
|
||||
encodedAddress: Address,
|
||||
publicKey: ArrayBuffer,
|
||||
publicKey: Uint8Array,
|
||||
nonblockingApproval = false,
|
||||
{ zone }: SessionTransactionOptions = {}
|
||||
): Promise<boolean> {
|
||||
|
@ -1533,9 +1529,9 @@ export class SignalProtocolStore extends EventsMixin {
|
|||
if (encodedAddress === null || encodedAddress === undefined) {
|
||||
throw new Error('saveIdentity: encodedAddress was undefined/null');
|
||||
}
|
||||
if (!(publicKey instanceof ArrayBuffer)) {
|
||||
if (!(publicKey instanceof Uint8Array)) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
publicKey = fromEncodedBinaryToArrayBuffer(publicKey);
|
||||
publicKey = Bytes.fromBinary(publicKey);
|
||||
}
|
||||
if (typeof nonblockingApproval !== 'boolean') {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
|
@ -1668,7 +1664,7 @@ export class SignalProtocolStore extends EventsMixin {
|
|||
async setVerified(
|
||||
uuid: UUID,
|
||||
verifiedStatus: number,
|
||||
publicKey?: ArrayBuffer
|
||||
publicKey?: Uint8Array
|
||||
): Promise<void> {
|
||||
if (uuid === null || uuid === undefined) {
|
||||
throw new Error('setVerified: uuid was undefined/null');
|
||||
|
@ -1676,7 +1672,7 @@ export class SignalProtocolStore extends EventsMixin {
|
|||
if (!validateVerifiedStatus(verifiedStatus)) {
|
||||
throw new Error('setVerified: Invalid verified status');
|
||||
}
|
||||
if (arguments.length > 2 && !(publicKey instanceof ArrayBuffer)) {
|
||||
if (arguments.length > 2 && !(publicKey instanceof Uint8Array)) {
|
||||
throw new Error('setVerified: Invalid public key');
|
||||
}
|
||||
|
||||
|
@ -1719,7 +1715,7 @@ export class SignalProtocolStore extends EventsMixin {
|
|||
processContactSyncVerificationState(
|
||||
uuid: UUID,
|
||||
verifiedStatus: number,
|
||||
publicKey: ArrayBuffer
|
||||
publicKey: Uint8Array
|
||||
): Promise<boolean> {
|
||||
if (verifiedStatus === VerifiedStatus.UNVERIFIED) {
|
||||
return this.processUnverifiedMessage(uuid, verifiedStatus, publicKey);
|
||||
|
@ -1733,12 +1729,12 @@ export class SignalProtocolStore extends EventsMixin {
|
|||
async processUnverifiedMessage(
|
||||
uuid: UUID,
|
||||
verifiedStatus: number,
|
||||
publicKey?: ArrayBuffer
|
||||
publicKey?: Uint8Array
|
||||
): Promise<boolean> {
|
||||
if (uuid === null || uuid === undefined) {
|
||||
throw new Error('processUnverifiedMessage: uuid was undefined/null');
|
||||
}
|
||||
if (publicKey !== undefined && !(publicKey instanceof ArrayBuffer)) {
|
||||
if (publicKey !== undefined && !(publicKey instanceof Uint8Array)) {
|
||||
throw new Error('processUnverifiedMessage: Invalid public key');
|
||||
}
|
||||
|
||||
|
@ -1796,7 +1792,7 @@ export class SignalProtocolStore extends EventsMixin {
|
|||
async processVerifiedMessage(
|
||||
uuid: UUID,
|
||||
verifiedStatus: number,
|
||||
publicKey?: ArrayBuffer
|
||||
publicKey?: Uint8Array
|
||||
): Promise<boolean> {
|
||||
if (uuid === null || uuid === undefined) {
|
||||
throw new Error('processVerifiedMessage: uuid was undefined/null');
|
||||
|
@ -1804,7 +1800,7 @@ export class SignalProtocolStore extends EventsMixin {
|
|||
if (!validateVerifiedStatus(verifiedStatus)) {
|
||||
throw new Error('processVerifiedMessage: Invalid verified status');
|
||||
}
|
||||
if (publicKey !== undefined && !(publicKey instanceof ArrayBuffer)) {
|
||||
if (publicKey !== undefined && !(publicKey instanceof Uint8Array)) {
|
||||
throw new Error('processVerifiedMessage: Invalid public key');
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ import {
|
|||
ConversationAttributesType,
|
||||
} from './model-types.d';
|
||||
import * as Bytes from './Bytes';
|
||||
import { typedArrayToArrayBuffer } from './Crypto';
|
||||
import { WhatIsThis, DeliveryReceiptBatcherItemType } from './window.d';
|
||||
import { getTitleBarVisibility, TitleBarVisibility } from './types/Settings';
|
||||
import { SocketStatus } from './types/SocketStatus';
|
||||
|
@ -93,7 +92,9 @@ import {
|
|||
} from './messages/MessageSendState';
|
||||
import * as AttachmentDownloads from './messageModifiers/AttachmentDownloads';
|
||||
import * as preferredReactions from './state/ducks/preferredReactions';
|
||||
import * as Conversation from './types/Conversation';
|
||||
import * as Stickers from './types/Stickers';
|
||||
import * as Errors from './types/errors';
|
||||
import { SignalService as Proto } from './protobuf';
|
||||
import { onRetryRequest, onDecryptionError } from './util/handleRetry';
|
||||
import { themeChanged } from './shims/themeChanged';
|
||||
|
@ -497,7 +498,7 @@ export async function startApp(): Promise<void> {
|
|||
removeDatabase: removeIndexedDB,
|
||||
doesDatabaseExist,
|
||||
} = window.Signal.IndexedDB;
|
||||
const { Errors, Message } = window.Signal.Types;
|
||||
const { Message } = window.Signal.Types;
|
||||
const {
|
||||
upgradeMessageSchema,
|
||||
writeNewAttachmentData,
|
||||
|
@ -2559,7 +2560,7 @@ export async function startApp(): Promise<void> {
|
|||
// special case for syncing details about ourselves
|
||||
if (details.profileKey) {
|
||||
log.info('Got sync message with our own profile key');
|
||||
ourProfileKeyService.set(typedArrayToArrayBuffer(details.profileKey));
|
||||
ourProfileKeyService.set(details.profileKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2607,7 +2608,7 @@ export async function startApp(): Promise<void> {
|
|||
// Update the conversation avatar only if new avatar exists and hash differs
|
||||
const { avatar } = details;
|
||||
if (avatar && avatar.data) {
|
||||
const newAttributes = await window.Signal.Types.Conversation.maybeUpdateAvatar(
|
||||
const newAttributes = await Conversation.maybeUpdateAvatar(
|
||||
conversation.attributes,
|
||||
avatar.data,
|
||||
{
|
||||
|
@ -2650,9 +2651,7 @@ export async function startApp(): Promise<void> {
|
|||
state: dropNull(verified.state),
|
||||
destination: dropNull(verified.destination),
|
||||
destinationUuid: dropNull(verified.destinationUuid),
|
||||
identityKey: verified.identityKey
|
||||
? typedArrayToArrayBuffer(verified.identityKey)
|
||||
: undefined,
|
||||
identityKey: dropNull(verified.identityKey),
|
||||
viaContactSync: true,
|
||||
},
|
||||
noop
|
||||
|
@ -2720,7 +2719,7 @@ export async function startApp(): Promise<void> {
|
|||
// Update the conversation avatar only if new avatar exists and hash differs
|
||||
const { avatar } = details;
|
||||
if (avatar && avatar.data) {
|
||||
const newAttributes = await window.Signal.Types.Conversation.maybeUpdateAvatar(
|
||||
const newAttributes = await Conversation.maybeUpdateAvatar(
|
||||
conversation.attributes,
|
||||
avatar.data,
|
||||
{
|
||||
|
@ -3478,9 +3477,7 @@ export async function startApp(): Promise<void> {
|
|||
|
||||
if (storageServiceKey) {
|
||||
log.info('onKeysSync: received keys');
|
||||
const storageServiceKeyBase64 = window.Signal.Crypto.arrayBufferToBase64(
|
||||
storageServiceKey
|
||||
);
|
||||
const storageServiceKeyBase64 = Bytes.toBase64(storageServiceKey);
|
||||
window.storage.put('storageKey', storageServiceKeyBase64);
|
||||
|
||||
await window.Signal.Services.runStorageServiceSyncJob();
|
||||
|
|
|
@ -17,7 +17,7 @@ import { AvatarTextEditor } from './AvatarTextEditor';
|
|||
import { AvatarUploadButton } from './AvatarUploadButton';
|
||||
import { BetterAvatar } from './BetterAvatar';
|
||||
import { LocalizerType } from '../types/Util';
|
||||
import { avatarDataToArrayBuffer } from '../util/avatarDataToArrayBuffer';
|
||||
import { avatarDataToBytes } from '../util/avatarDataToBytes';
|
||||
import { createAvatarData } from '../util/createAvatarData';
|
||||
import { isSameAvatarData } from '../util/isSameAvatarData';
|
||||
import { missingCaseError } from '../util/missingCaseError';
|
||||
|
@ -25,14 +25,14 @@ import { missingCaseError } from '../util/missingCaseError';
|
|||
export type PropsType = {
|
||||
avatarColor?: AvatarColorType;
|
||||
avatarPath?: string;
|
||||
avatarValue?: ArrayBuffer;
|
||||
avatarValue?: Uint8Array;
|
||||
conversationId?: string;
|
||||
conversationTitle?: string;
|
||||
deleteAvatarFromDisk: DeleteAvatarFromDiskActionType;
|
||||
i18n: LocalizerType;
|
||||
isGroup?: boolean;
|
||||
onCancel: () => unknown;
|
||||
onSave: (buffer: ArrayBuffer | undefined) => unknown;
|
||||
onSave: (buffer: Uint8Array | undefined) => unknown;
|
||||
userAvatarData: ReadonlyArray<AvatarDataType>;
|
||||
replaceAvatar: ReplaceAvatarActionType;
|
||||
saveAvatarToDisk: SaveAvatarToDiskActionType;
|
||||
|
@ -62,10 +62,10 @@ export const AvatarEditor = ({
|
|||
const [provisionalSelectedAvatar, setProvisionalSelectedAvatar] = useState<
|
||||
AvatarDataType | undefined
|
||||
>();
|
||||
const [avatarPreview, setAvatarPreview] = useState<ArrayBuffer | undefined>(
|
||||
const [avatarPreview, setAvatarPreview] = useState<Uint8Array | undefined>(
|
||||
avatarValue
|
||||
);
|
||||
const [initialAvatar, setInitialAvatar] = useState<ArrayBuffer | undefined>(
|
||||
const [initialAvatar, setInitialAvatar] = useState<Uint8Array | undefined>(
|
||||
avatarValue
|
||||
);
|
||||
const [localAvatarData, setLocalAvatarData] = useState<Array<AvatarDataType>>(
|
||||
|
@ -84,7 +84,7 @@ export const AvatarEditor = ({
|
|||
|
||||
const selectedAvatar = getSelectedAvatar(provisionalSelectedAvatar);
|
||||
|
||||
// Caching the ArrayBuffer produced into avatarData as buffer because
|
||||
// Caching the Uint8Array produced into avatarData as buffer because
|
||||
// that function is a little expensive to run and so we don't flicker the UI.
|
||||
useEffect(() => {
|
||||
let shouldCancel = false;
|
||||
|
@ -95,7 +95,7 @@ export const AvatarEditor = ({
|
|||
if (avatarData.buffer) {
|
||||
return avatarData;
|
||||
}
|
||||
const buffer = await avatarDataToArrayBuffer(avatarData);
|
||||
const buffer = await avatarDataToBytes(avatarData);
|
||||
return {
|
||||
...avatarData,
|
||||
buffer,
|
||||
|
|
|
@ -9,7 +9,7 @@ import { AvatarDataType } from '../types/Avatar';
|
|||
import { AvatarModalButtons } from './AvatarModalButtons';
|
||||
import { AvatarPreview } from './AvatarPreview';
|
||||
import { LocalizerType } from '../types/Util';
|
||||
import { avatarDataToArrayBuffer } from '../util/avatarDataToArrayBuffer';
|
||||
import { avatarDataToBytes } from '../util/avatarDataToBytes';
|
||||
|
||||
export type PropsType = {
|
||||
avatarData: AvatarDataType;
|
||||
|
@ -22,7 +22,7 @@ export const AvatarIconEditor = ({
|
|||
i18n,
|
||||
onClose,
|
||||
}: PropsType): JSX.Element => {
|
||||
const [avatarBuffer, setAvatarBuffer] = useState<ArrayBuffer | undefined>();
|
||||
const [avatarBuffer, setAvatarBuffer] = useState<Uint8Array | undefined>();
|
||||
const [avatarData, setAvatarData] = useState<AvatarDataType>(
|
||||
initialAvatarData
|
||||
);
|
||||
|
@ -41,7 +41,7 @@ export const AvatarIconEditor = ({
|
|||
let shouldCancel = false;
|
||||
|
||||
async function loadAvatar() {
|
||||
const buffer = await avatarDataToArrayBuffer(avatarData);
|
||||
const buffer = await avatarDataToBytes(avatarData);
|
||||
if (!shouldCancel) {
|
||||
setAvatarBuffer(buffer);
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ const TEST_IMAGE = new Uint8Array(
|
|||
'89504e470d0a1a0a0000000d4948445200000008000000080103000000fec12cc800000006504c5445ff00ff00ff000c82e9800000001849444154085b633061a8638863a867f8c720c760c12000001a4302f4d81dd9870000000049454e44ae426082',
|
||||
2
|
||||
).map(bytePair => parseInt(bytePair.join(''), 16))
|
||||
).buffer;
|
||||
);
|
||||
|
||||
const createProps = (overrideProps: Partial<PropsType> = {}): PropsType => ({
|
||||
avatarColor: overrideProps.avatarColor,
|
||||
|
|
|
@ -9,17 +9,17 @@ import { LocalizerType } from '../types/Util';
|
|||
import { Spinner } from './Spinner';
|
||||
import { AvatarColors, AvatarColorType } from '../types/Colors';
|
||||
import { getInitials } from '../util/getInitials';
|
||||
import { imagePathToArrayBuffer } from '../util/imagePathToArrayBuffer';
|
||||
import { imagePathToBytes } from '../util/imagePathToBytes';
|
||||
|
||||
export type PropsType = {
|
||||
avatarColor?: AvatarColorType;
|
||||
avatarPath?: string;
|
||||
avatarValue?: ArrayBuffer;
|
||||
avatarValue?: Uint8Array;
|
||||
conversationTitle?: string;
|
||||
i18n: LocalizerType;
|
||||
isEditable?: boolean;
|
||||
isGroup?: boolean;
|
||||
onAvatarLoaded?: (avatarBuffer: ArrayBuffer) => unknown;
|
||||
onAvatarLoaded?: (avatarBuffer: Uint8Array) => unknown;
|
||||
onClear?: () => unknown;
|
||||
onClick?: () => unknown;
|
||||
style?: CSSProperties;
|
||||
|
@ -48,7 +48,7 @@ export const AvatarPreview = ({
|
|||
avatarValue ? undefined : avatarPath
|
||||
);
|
||||
|
||||
const [avatarPreview, setAvatarPreview] = useState<ArrayBuffer | undefined>();
|
||||
const [avatarPreview, setAvatarPreview] = useState<Uint8Array | undefined>();
|
||||
|
||||
// Loads the initial avatarPath if one is provided.
|
||||
useEffect(() => {
|
||||
|
@ -61,7 +61,7 @@ export const AvatarPreview = ({
|
|||
|
||||
(async () => {
|
||||
try {
|
||||
const buffer = await imagePathToArrayBuffer(startingAvatarPath);
|
||||
const buffer = await imagePathToBytes(startingAvatarPath);
|
||||
if (shouldCancel) {
|
||||
return;
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ export const AvatarPreview = ({
|
|||
}
|
||||
}, [avatarValue]);
|
||||
|
||||
// Creates the object URL to render the ArrayBuffer image
|
||||
// Creates the object URL to render the Uint8Array image
|
||||
const [objectUrl, setObjectUrl] = useState<undefined | string>();
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -19,7 +19,7 @@ import { AvatarDataType } from '../types/Avatar';
|
|||
import { AvatarModalButtons } from './AvatarModalButtons';
|
||||
import { BetterAvatarBubble } from './BetterAvatarBubble';
|
||||
import { LocalizerType } from '../types/Util';
|
||||
import { avatarDataToArrayBuffer } from '../util/avatarDataToArrayBuffer';
|
||||
import { avatarDataToBytes } from '../util/avatarDataToBytes';
|
||||
import { createAvatarData } from '../util/createAvatarData';
|
||||
import {
|
||||
getFittedFontSize,
|
||||
|
@ -27,7 +27,7 @@ import {
|
|||
} from '../util/avatarTextSizeCalculator';
|
||||
|
||||
type DoneHandleType = (
|
||||
avatarBuffer: ArrayBuffer,
|
||||
avatarBuffer: Uint8Array,
|
||||
avatarData: AvatarDataType
|
||||
) => unknown;
|
||||
|
||||
|
@ -111,7 +111,7 @@ export const AvatarTextEditor = ({
|
|||
text: inputText,
|
||||
});
|
||||
|
||||
const buffer = await avatarDataToArrayBuffer(newAvatarData);
|
||||
const buffer = await avatarDataToBytes(newAvatarData);
|
||||
|
||||
onDoneRef.current(buffer, newAvatarData);
|
||||
}, [inputText, selectedColor]);
|
||||
|
|
|
@ -10,7 +10,7 @@ import { processImageFile } from '../util/processImageFile';
|
|||
export type PropsType = {
|
||||
className: string;
|
||||
i18n: LocalizerType;
|
||||
onChange: (avatar: ArrayBuffer) => unknown;
|
||||
onChange: (avatar: Uint8Array) => unknown;
|
||||
};
|
||||
|
||||
export const AvatarUploadButton = ({
|
||||
|
@ -30,7 +30,7 @@ export const AvatarUploadButton = ({
|
|||
let shouldCancel = false;
|
||||
|
||||
(async () => {
|
||||
let newAvatar: ArrayBuffer;
|
||||
let newAvatar: Uint8Array;
|
||||
try {
|
||||
newAvatar = await processImageFile(processingFile);
|
||||
} catch (err) {
|
||||
|
|
|
@ -7,7 +7,7 @@ import { AvatarDataType } from '../types/Avatar';
|
|||
import { BetterAvatarBubble } from './BetterAvatarBubble';
|
||||
import { LocalizerType } from '../types/Util';
|
||||
import { Spinner } from './Spinner';
|
||||
import { avatarDataToArrayBuffer } from '../util/avatarDataToArrayBuffer';
|
||||
import { avatarDataToBytes } from '../util/avatarDataToBytes';
|
||||
|
||||
type AvatarSize = 48 | 80;
|
||||
|
||||
|
@ -15,7 +15,7 @@ export type PropsType = {
|
|||
avatarData: AvatarDataType;
|
||||
i18n: LocalizerType;
|
||||
isSelected?: boolean;
|
||||
onClick: (avatarBuffer: ArrayBuffer | undefined) => unknown;
|
||||
onClick: (avatarBuffer: Uint8Array | undefined) => unknown;
|
||||
onDelete: () => unknown;
|
||||
size?: AvatarSize;
|
||||
};
|
||||
|
@ -28,7 +28,7 @@ export const BetterAvatar = ({
|
|||
onDelete,
|
||||
size = 48,
|
||||
}: PropsType): JSX.Element => {
|
||||
const [avatarBuffer, setAvatarBuffer] = useState<ArrayBuffer | undefined>(
|
||||
const [avatarBuffer, setAvatarBuffer] = useState<Uint8Array | undefined>(
|
||||
avatarData.buffer
|
||||
);
|
||||
const [avatarURL, setAvatarURL] = useState<string | undefined>(undefined);
|
||||
|
@ -37,7 +37,7 @@ export const BetterAvatar = ({
|
|||
let shouldCancel = false;
|
||||
|
||||
async function makeAvatar() {
|
||||
const buffer = await avatarDataToArrayBuffer(avatarData);
|
||||
const buffer = await avatarDataToBytes(avatarData);
|
||||
if (!shouldCancel) {
|
||||
setAvatarBuffer(buffer);
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ export const BetterAvatar = ({
|
|||
};
|
||||
}, [avatarBuffer, avatarData]);
|
||||
|
||||
// Convert avatar's ArrayBuffer to a URL object
|
||||
// Convert avatar's Uint8Array to a URL object
|
||||
useEffect(() => {
|
||||
if (avatarBuffer) {
|
||||
const url = URL.createObjectURL(new Blob([avatarBuffer]));
|
||||
|
|
|
@ -102,7 +102,7 @@ export type PropsType = {
|
|||
switchToAssociatedView?: boolean;
|
||||
}) => void;
|
||||
setComposeSearchTerm: (composeSearchTerm: string) => void;
|
||||
setComposeGroupAvatar: (_: undefined | ArrayBuffer) => void;
|
||||
setComposeGroupAvatar: (_: undefined | Uint8Array) => void;
|
||||
setComposeGroupName: (_: string) => void;
|
||||
setComposeGroupExpireTimer: (_: number) => void;
|
||||
showArchivedConversations: () => void;
|
||||
|
|
|
@ -37,7 +37,7 @@ type PropsExternalType = {
|
|||
onEditStateChanged: (editState: EditState) => unknown;
|
||||
onProfileChanged: (
|
||||
profileData: ProfileDataType,
|
||||
avatarBuffer?: ArrayBuffer
|
||||
avatarBuffer?: Uint8Array
|
||||
) => unknown;
|
||||
};
|
||||
|
||||
|
@ -126,7 +126,7 @@ export const ProfileEditor = ({
|
|||
aboutText,
|
||||
});
|
||||
|
||||
const [avatarBuffer, setAvatarBuffer] = useState<ArrayBuffer | undefined>(
|
||||
const [avatarBuffer, setAvatarBuffer] = useState<Uint8Array | undefined>(
|
||||
undefined
|
||||
);
|
||||
const [stagedProfile, setStagedProfile] = useState<ProfileDataType>({
|
||||
|
@ -153,7 +153,7 @@ export const ProfileEditor = ({
|
|||
);
|
||||
|
||||
const handleAvatarChanged = useCallback(
|
||||
(avatar: ArrayBuffer | undefined) => {
|
||||
(avatar: Uint8Array | undefined) => {
|
||||
setAvatarBuffer(avatar);
|
||||
setEditState(EditState.None);
|
||||
onProfileChanged(stagedProfile, avatar);
|
||||
|
|
|
@ -18,7 +18,7 @@ export type PropsDataType = {
|
|||
type PropsType = {
|
||||
myProfileChanged: (
|
||||
profileData: ProfileDataType,
|
||||
avatarBuffer?: ArrayBuffer
|
||||
avatarBuffer?: Uint8Array
|
||||
) => unknown;
|
||||
toggleProfileEditor: () => unknown;
|
||||
toggleProfileEditorHasError: () => unknown;
|
||||
|
|
|
@ -72,7 +72,7 @@ export type StateProps = {
|
|||
showConversationNotificationsSettings: () => void;
|
||||
updateGroupAttributes: (
|
||||
_: Readonly<{
|
||||
avatar?: undefined | ArrayBuffer;
|
||||
avatar?: undefined | Uint8Array;
|
||||
description?: string;
|
||||
title?: string;
|
||||
}>
|
||||
|
@ -169,7 +169,7 @@ export const ConversationDetails: React.ComponentType<Props> = ({
|
|||
}
|
||||
makeRequest={async (
|
||||
options: Readonly<{
|
||||
avatar?: undefined | ArrayBuffer;
|
||||
avatar?: undefined | Uint8Array;
|
||||
description?: string;
|
||||
title?: string;
|
||||
}>
|
||||
|
|
|
@ -34,7 +34,7 @@ type PropsType = {
|
|||
initiallyFocusDescription: boolean;
|
||||
makeRequest: (
|
||||
_: Readonly<{
|
||||
avatar?: undefined | ArrayBuffer;
|
||||
avatar?: undefined | Uint8Array;
|
||||
description?: string;
|
||||
title?: undefined | string;
|
||||
}>
|
||||
|
@ -73,7 +73,7 @@ export const EditConversationAttributesModal: FunctionComponent<PropsType> = ({
|
|||
const startingAvatarPathRef = useRef<undefined | string>(externalAvatarPath);
|
||||
|
||||
const [editingAvatar, setEditingAvatar] = useState(false);
|
||||
const [avatar, setAvatar] = useState<undefined | ArrayBuffer>();
|
||||
const [avatar, setAvatar] = useState<undefined | Uint8Array>();
|
||||
const [rawTitle, setRawTitle] = useState(externalTitle);
|
||||
const [rawGroupDescription, setRawGroupDescription] = useState(
|
||||
externalGroupDescription
|
||||
|
@ -111,7 +111,7 @@ export const EditConversationAttributesModal: FunctionComponent<PropsType> = ({
|
|||
event.preventDefault();
|
||||
|
||||
const request: {
|
||||
avatar?: undefined | ArrayBuffer;
|
||||
avatar?: undefined | Uint8Array;
|
||||
description?: string;
|
||||
title?: string;
|
||||
} = {};
|
||||
|
|
|
@ -56,7 +56,7 @@ export abstract class LeftPaneHelper<T> {
|
|||
composeSaveAvatarToDisk: SaveAvatarToDiskActionType;
|
||||
createGroup: () => unknown;
|
||||
i18n: LocalizerType;
|
||||
setComposeGroupAvatar: (_: undefined | ArrayBuffer) => unknown;
|
||||
setComposeGroupAvatar: (_: undefined | Uint8Array) => unknown;
|
||||
setComposeGroupName: (_: string) => unknown;
|
||||
setComposeGroupExpireTimer: (_: number) => void;
|
||||
onChangeComposeSearchTerm: (
|
||||
|
|
|
@ -24,7 +24,7 @@ import {
|
|||
import { AvatarColors } from '../../types/Colors';
|
||||
|
||||
export type LeftPaneSetGroupMetadataPropsType = {
|
||||
groupAvatar: undefined | ArrayBuffer;
|
||||
groupAvatar: undefined | Uint8Array;
|
||||
groupName: string;
|
||||
groupExpireTimer: number;
|
||||
hasError: boolean;
|
||||
|
@ -37,7 +37,7 @@ export type LeftPaneSetGroupMetadataPropsType = {
|
|||
/* eslint-disable class-methods-use-this */
|
||||
|
||||
export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGroupMetadataPropsType> {
|
||||
private readonly groupAvatar: undefined | ArrayBuffer;
|
||||
private readonly groupAvatar: undefined | Uint8Array;
|
||||
|
||||
private readonly groupName: string;
|
||||
|
||||
|
@ -127,7 +127,7 @@ export class LeftPaneSetGroupMetadataHelper extends LeftPaneHelper<LeftPaneSetGr
|
|||
composeSaveAvatarToDisk: SaveAvatarToDiskActionType;
|
||||
createGroup: () => unknown;
|
||||
i18n: LocalizerType;
|
||||
setComposeGroupAvatar: (_: undefined | ArrayBuffer) => unknown;
|
||||
setComposeGroupAvatar: (_: undefined | Uint8Array) => unknown;
|
||||
setComposeGroupExpireTimer: (_: number) => void;
|
||||
setComposeGroupName: (_: string) => unknown;
|
||||
toggleComposeEditingAvatar: () => unknown;
|
||||
|
|
129
ts/context/Crypto.ts
Normal file
129
ts/context/Crypto.ts
Normal file
|
@ -0,0 +1,129 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
/* eslint-disable class-methods-use-this */
|
||||
|
||||
import { Buffer } from 'buffer';
|
||||
import crypto, { Decipher } from 'crypto';
|
||||
|
||||
import { strictAssert } from '../util/assert';
|
||||
import { HashType, CipherType } from '../types/Crypto';
|
||||
|
||||
const AUTH_TAG_SIZE = 16;
|
||||
|
||||
export class Crypto {
|
||||
public sign(key: Uint8Array, data: Uint8Array): Uint8Array {
|
||||
return crypto
|
||||
.createHmac('sha256', Buffer.from(key))
|
||||
.update(Buffer.from(data))
|
||||
.digest();
|
||||
}
|
||||
|
||||
public hash(type: HashType, data: Uint8Array): Uint8Array {
|
||||
return crypto.createHash(type).update(Buffer.from(data)).digest();
|
||||
}
|
||||
|
||||
public encrypt(
|
||||
cipherType: CipherType,
|
||||
{
|
||||
key,
|
||||
plaintext,
|
||||
iv,
|
||||
aad,
|
||||
}: Readonly<{
|
||||
key: Uint8Array;
|
||||
plaintext: Uint8Array;
|
||||
iv: Uint8Array;
|
||||
aad?: Uint8Array;
|
||||
}>
|
||||
): Uint8Array {
|
||||
if (cipherType === CipherType.AES256GCM) {
|
||||
const gcm = crypto.createCipheriv(
|
||||
cipherType,
|
||||
Buffer.from(key),
|
||||
Buffer.from(iv)
|
||||
);
|
||||
|
||||
if (aad) {
|
||||
gcm.setAAD(aad);
|
||||
}
|
||||
|
||||
const first = gcm.update(Buffer.from(plaintext));
|
||||
const last = gcm.final();
|
||||
const tag = gcm.getAuthTag();
|
||||
strictAssert(tag.length === AUTH_TAG_SIZE, 'Invalid auth tag size');
|
||||
|
||||
return Buffer.concat([first, last, tag]);
|
||||
}
|
||||
|
||||
strictAssert(aad === undefined, `AAD is not supported for: ${cipherType}`);
|
||||
const cipher = crypto.createCipheriv(
|
||||
cipherType,
|
||||
Buffer.from(key),
|
||||
Buffer.from(iv)
|
||||
);
|
||||
return Buffer.concat([
|
||||
cipher.update(Buffer.from(plaintext)),
|
||||
cipher.final(),
|
||||
]);
|
||||
}
|
||||
|
||||
public decrypt(
|
||||
cipherType: CipherType,
|
||||
{
|
||||
key,
|
||||
ciphertext,
|
||||
iv,
|
||||
aad,
|
||||
}: Readonly<{
|
||||
key: Uint8Array;
|
||||
ciphertext: Uint8Array;
|
||||
iv: Uint8Array;
|
||||
aad?: Uint8Array;
|
||||
}>
|
||||
): Uint8Array {
|
||||
let decipher: Decipher;
|
||||
let input = Buffer.from(ciphertext);
|
||||
if (cipherType === CipherType.AES256GCM) {
|
||||
const gcm = crypto.createDecipheriv(
|
||||
cipherType,
|
||||
Buffer.from(key),
|
||||
Buffer.from(iv)
|
||||
);
|
||||
|
||||
if (input.length < AUTH_TAG_SIZE) {
|
||||
throw new Error('Invalid GCM ciphertext');
|
||||
}
|
||||
|
||||
const tag = input.slice(input.length - AUTH_TAG_SIZE);
|
||||
input = input.slice(0, input.length - AUTH_TAG_SIZE);
|
||||
|
||||
gcm.setAuthTag(tag);
|
||||
|
||||
if (aad) {
|
||||
gcm.setAAD(aad);
|
||||
}
|
||||
|
||||
decipher = gcm;
|
||||
} else {
|
||||
strictAssert(
|
||||
aad === undefined,
|
||||
`AAD is not supported for: ${cipherType}`
|
||||
);
|
||||
decipher = crypto.createDecipheriv(
|
||||
cipherType,
|
||||
Buffer.from(key),
|
||||
Buffer.from(iv)
|
||||
);
|
||||
}
|
||||
return Buffer.concat([decipher.update(input), decipher.final()]);
|
||||
}
|
||||
|
||||
public getRandomBytes(size: number): Uint8Array {
|
||||
return crypto.randomBytes(size);
|
||||
}
|
||||
|
||||
public constantTimeEqual(left: Uint8Array, right: Uint8Array): boolean {
|
||||
return crypto.timingSafeEqual(Buffer.from(left), Buffer.from(right));
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { Bytes } from './Bytes';
|
||||
import { Crypto } from './Crypto';
|
||||
import {
|
||||
createNativeThemeListener,
|
||||
MinimalIPC,
|
||||
|
@ -10,6 +11,8 @@ import {
|
|||
export class Context {
|
||||
public readonly bytes = new Bytes();
|
||||
|
||||
public readonly crypto = new Crypto();
|
||||
|
||||
public readonly nativeThemeListener;
|
||||
|
||||
constructor(ipc: MinimalIPC) {
|
||||
|
|
73
ts/groups.ts
73
ts/groups.ts
|
@ -53,9 +53,7 @@ import {
|
|||
import {
|
||||
computeHash,
|
||||
deriveMasterKeyFromGroupV1,
|
||||
fromEncodedBinaryToArrayBuffer,
|
||||
getRandomBytes,
|
||||
typedArrayToArrayBuffer,
|
||||
} from './Crypto';
|
||||
import {
|
||||
GroupCredentialsType,
|
||||
|
@ -224,9 +222,6 @@ export type GroupFields = {
|
|||
readonly publicParams: Uint8Array;
|
||||
};
|
||||
|
||||
// TODO: remove once we move away from ArrayBuffers
|
||||
const FIXMEU8 = Uint8Array;
|
||||
|
||||
const MAX_CACHED_GROUP_FIELDS = 100;
|
||||
|
||||
const groupFieldsCache = new LRU<string, GroupFields>({
|
||||
|
@ -256,7 +251,7 @@ type UpdatesResultType = {
|
|||
};
|
||||
|
||||
type UploadedAvatarType = {
|
||||
data: ArrayBuffer;
|
||||
data: Uint8Array;
|
||||
hash: string;
|
||||
key: string;
|
||||
};
|
||||
|
@ -277,7 +272,7 @@ const GROUP_INVITE_LINK_PASSWORD_LENGTH = 16;
|
|||
|
||||
// Group Links
|
||||
|
||||
export function generateGroupInviteLinkPassword(): ArrayBuffer {
|
||||
export function generateGroupInviteLinkPassword(): Uint8Array {
|
||||
return getRandomBytes(GROUP_INVITE_LINK_PASSWORD_LENGTH);
|
||||
}
|
||||
|
||||
|
@ -366,24 +361,24 @@ async function uploadAvatar(
|
|||
logId: string;
|
||||
publicParams: string;
|
||||
secretParams: string;
|
||||
} & ({ path: string } | { data: ArrayBuffer })
|
||||
} & ({ path: string } | { data: Uint8Array })
|
||||
): Promise<UploadedAvatarType> {
|
||||
const { logId, publicParams, secretParams } = options;
|
||||
|
||||
try {
|
||||
const clientZkGroupCipher = getClientZkGroupCipher(secretParams);
|
||||
|
||||
let data: ArrayBuffer;
|
||||
let data: Uint8Array;
|
||||
if ('data' in options) {
|
||||
({ data } = options);
|
||||
} else {
|
||||
data = await window.Signal.Migrations.readAttachmentData(options.path);
|
||||
}
|
||||
|
||||
const hash = await computeHash(data);
|
||||
const hash = computeHash(data);
|
||||
|
||||
const blobPlaintext = Proto.GroupAttributeBlob.encode({
|
||||
avatar: new FIXMEU8(data),
|
||||
avatar: data,
|
||||
}).finish();
|
||||
const ciphertext = encryptGroupBlob(clientZkGroupCipher, blobPlaintext);
|
||||
|
||||
|
@ -731,7 +726,7 @@ export async function buildUpdateAttributesChange(
|
|||
'id' | 'revision' | 'publicParams' | 'secretParams'
|
||||
>,
|
||||
attributes: Readonly<{
|
||||
avatar?: undefined | ArrayBuffer;
|
||||
avatar?: undefined | Uint8Array;
|
||||
description?: string;
|
||||
title?: string;
|
||||
}>
|
||||
|
@ -1309,7 +1304,7 @@ export async function modifyGroupV2({
|
|||
window.Signal.Util.sendToGroup({
|
||||
groupSendOptions: {
|
||||
groupV2: conversation.getGroupV2Info({
|
||||
groupChange: typedArrayToArrayBuffer(groupChangeBuffer),
|
||||
groupChange: groupChangeBuffer,
|
||||
includePendingMembers: true,
|
||||
extraConversationsForSend,
|
||||
}),
|
||||
|
@ -1497,7 +1492,7 @@ export async function createGroupV2({
|
|||
avatars,
|
||||
}: Readonly<{
|
||||
name: string;
|
||||
avatar: undefined | ArrayBuffer;
|
||||
avatar: undefined | Uint8Array;
|
||||
expireTimer: undefined | number;
|
||||
conversationIds: Array<string>;
|
||||
avatars?: Array<AvatarDataType>;
|
||||
|
@ -1514,7 +1509,7 @@ export async function createGroupV2({
|
|||
const ACCESS_ENUM = Proto.AccessControl.AccessRequired;
|
||||
const MEMBER_ROLE_ENUM = Proto.Member.Role;
|
||||
|
||||
const masterKeyBuffer = new FIXMEU8(getRandomBytes(32));
|
||||
const masterKeyBuffer = getRandomBytes(32);
|
||||
const fields = deriveGroupFields(masterKeyBuffer);
|
||||
|
||||
const groupId = Bytes.toBase64(fields.id);
|
||||
|
@ -1763,10 +1758,8 @@ export async function hasV1GroupBeenMigrated(
|
|||
throw new Error(`checkForGV2Existence/${logId}: No groupId!`);
|
||||
}
|
||||
|
||||
const idBuffer = fromEncodedBinaryToArrayBuffer(groupId);
|
||||
const masterKeyBuffer = new FIXMEU8(
|
||||
await deriveMasterKeyFromGroupV1(idBuffer)
|
||||
);
|
||||
const idBuffer = Bytes.fromBinary(groupId);
|
||||
const masterKeyBuffer = deriveMasterKeyFromGroupV1(idBuffer);
|
||||
const fields = deriveGroupFields(masterKeyBuffer);
|
||||
|
||||
try {
|
||||
|
@ -1783,9 +1776,7 @@ export async function hasV1GroupBeenMigrated(
|
|||
}
|
||||
}
|
||||
|
||||
export async function maybeDeriveGroupV2Id(
|
||||
conversation: ConversationModel
|
||||
): Promise<boolean> {
|
||||
export function maybeDeriveGroupV2Id(conversation: ConversationModel): boolean {
|
||||
const isGroupV1 = getIsGroupV1(conversation.attributes);
|
||||
const groupV1Id = conversation.get('groupId');
|
||||
const derived = conversation.get('derivedGroupV2Id');
|
||||
|
@ -1794,10 +1785,8 @@ export async function maybeDeriveGroupV2Id(
|
|||
return false;
|
||||
}
|
||||
|
||||
const v1IdBuffer = fromEncodedBinaryToArrayBuffer(groupV1Id);
|
||||
const masterKeyBuffer = new FIXMEU8(
|
||||
await deriveMasterKeyFromGroupV1(v1IdBuffer)
|
||||
);
|
||||
const v1IdBuffer = Bytes.fromBinary(groupV1Id);
|
||||
const masterKeyBuffer = deriveMasterKeyFromGroupV1(v1IdBuffer);
|
||||
const fields = deriveGroupFields(masterKeyBuffer);
|
||||
const derivedGroupV2Id = Bytes.toBase64(fields.id);
|
||||
|
||||
|
@ -2050,10 +2039,8 @@ export async function initiateMigrationToGroupV2(
|
|||
);
|
||||
}
|
||||
|
||||
const groupV1IdBuffer = fromEncodedBinaryToArrayBuffer(previousGroupV1Id);
|
||||
const masterKeyBuffer = new FIXMEU8(
|
||||
await deriveMasterKeyFromGroupV1(groupV1IdBuffer)
|
||||
);
|
||||
const groupV1IdBuffer = Bytes.fromBinary(previousGroupV1Id);
|
||||
const masterKeyBuffer = deriveMasterKeyFromGroupV1(groupV1IdBuffer);
|
||||
const fields = deriveGroupFields(masterKeyBuffer);
|
||||
|
||||
const groupId = Bytes.toBase64(fields.id);
|
||||
|
@ -2219,9 +2206,7 @@ export async function initiateMigrationToGroupV2(
|
|||
const logId = conversation.idForLogging();
|
||||
const timestamp = Date.now();
|
||||
|
||||
const ourProfileKey:
|
||||
| ArrayBuffer
|
||||
| undefined = await ourProfileKeyService.get();
|
||||
const ourProfileKey = await ourProfileKeyService.get();
|
||||
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
const sendOptions = await getSendOptions(conversation.attributes);
|
||||
|
@ -2408,10 +2393,8 @@ export async function joinGroupV2ViaLinkAndMigrate({
|
|||
}
|
||||
|
||||
// Derive GroupV2 fields
|
||||
const groupV1IdBuffer = fromEncodedBinaryToArrayBuffer(previousGroupV1Id);
|
||||
const masterKeyBuffer = new FIXMEU8(
|
||||
await deriveMasterKeyFromGroupV1(groupV1IdBuffer)
|
||||
);
|
||||
const groupV1IdBuffer = Bytes.fromBinary(previousGroupV1Id);
|
||||
const masterKeyBuffer = deriveMasterKeyFromGroupV1(groupV1IdBuffer);
|
||||
const fields = deriveGroupFields(masterKeyBuffer);
|
||||
|
||||
const groupId = Bytes.toBase64(fields.id);
|
||||
|
@ -2506,10 +2489,8 @@ export async function respondToGroupV2Migration({
|
|||
conversation.hasMember(ourConversationId);
|
||||
|
||||
// Derive GroupV2 fields
|
||||
const groupV1IdBuffer = fromEncodedBinaryToArrayBuffer(previousGroupV1Id);
|
||||
const masterKeyBuffer = new FIXMEU8(
|
||||
await deriveMasterKeyFromGroupV1(groupV1IdBuffer)
|
||||
);
|
||||
const groupV1IdBuffer = Bytes.fromBinary(previousGroupV1Id);
|
||||
const masterKeyBuffer = deriveMasterKeyFromGroupV1(groupV1IdBuffer);
|
||||
const fields = deriveGroupFields(masterKeyBuffer);
|
||||
|
||||
const groupId = Bytes.toBase64(fields.id);
|
||||
|
@ -3421,7 +3402,7 @@ async function integrateGroupChange({
|
|||
|
||||
if (groupChange) {
|
||||
groupChangeActions = Proto.GroupChange.Actions.decode(
|
||||
groupChange.actions || new FIXMEU8(0)
|
||||
groupChange.actions || new Uint8Array(0)
|
||||
);
|
||||
|
||||
if (
|
||||
|
@ -4541,7 +4522,7 @@ async function applyGroupChange({
|
|||
export async function decryptGroupAvatar(
|
||||
avatarKey: string,
|
||||
secretParamsBase64: string
|
||||
): Promise<ArrayBuffer> {
|
||||
): Promise<Uint8Array> {
|
||||
const sender = window.textsecure.messaging;
|
||||
if (!sender) {
|
||||
throw new Error(
|
||||
|
@ -4549,7 +4530,7 @@ export async function decryptGroupAvatar(
|
|||
);
|
||||
}
|
||||
|
||||
const ciphertext = new FIXMEU8(await sender.getGroupAvatar(avatarKey));
|
||||
const ciphertext = await sender.getGroupAvatar(avatarKey);
|
||||
const clientZkGroupCipher = getClientZkGroupCipher(secretParamsBase64);
|
||||
const plaintext = decryptGroupBlob(clientZkGroupCipher, ciphertext);
|
||||
const blob = Proto.GroupAttributeBlob.decode(plaintext);
|
||||
|
@ -4559,7 +4540,7 @@ export async function decryptGroupAvatar(
|
|||
);
|
||||
}
|
||||
|
||||
return typedArrayToArrayBuffer(blob.avatar);
|
||||
return blob.avatar;
|
||||
}
|
||||
|
||||
// Ovewriting result.avatar as part of functionality
|
||||
|
@ -4583,7 +4564,7 @@ export async function applyNewAvatar(
|
|||
}
|
||||
|
||||
const data = await decryptGroupAvatar(newAvatar, result.secretParams);
|
||||
const hash = await computeHash(data);
|
||||
const hash = computeHash(data);
|
||||
|
||||
if (result.avatar && result.avatar.path && result.avatar.hash !== hash) {
|
||||
await window.Signal.Migrations.deleteAttachmentData(result.avatar.path);
|
||||
|
|
|
@ -465,7 +465,7 @@ async function getMessageSendData({
|
|||
mentions: undefined | BodyRangesType;
|
||||
messageTimestamp: number;
|
||||
preview: Array<PreviewType>;
|
||||
profileKey: undefined | ArrayBuffer;
|
||||
profileKey: undefined | Uint8Array;
|
||||
quote: WhatIsThis;
|
||||
sticker: WhatIsThis;
|
||||
}> {
|
||||
|
|
|
@ -64,7 +64,7 @@ export type LinkPreviewMetadata = {
|
|||
};
|
||||
|
||||
export type LinkPreviewImage = {
|
||||
data: ArrayBuffer;
|
||||
data: Uint8Array;
|
||||
contentType: MIMEType;
|
||||
};
|
||||
|
||||
|
@ -290,7 +290,7 @@ const getHtmlDocument = async (
|
|||
let result: HTMLDocument = emptyHtmlDocument();
|
||||
|
||||
const maxHtmlBytesToLoad = Math.min(contentLength, MAX_HTML_BYTES_TO_LOAD);
|
||||
const buffer = new Uint8Array(new ArrayBuffer(maxHtmlBytesToLoad));
|
||||
const buffer = new Uint8Array(maxHtmlBytesToLoad);
|
||||
let bytesLoadedSoFar = 0;
|
||||
|
||||
try {
|
||||
|
@ -578,9 +578,9 @@ export async function fetchLinkPreviewImage(
|
|||
return null;
|
||||
}
|
||||
|
||||
let data: ArrayBuffer;
|
||||
let data: Uint8Array;
|
||||
try {
|
||||
data = await response.arrayBuffer();
|
||||
data = await response.buffer();
|
||||
} catch (err) {
|
||||
log.warn('fetchLinkPreviewImage: failed to read body; bailing');
|
||||
return null;
|
||||
|
|
|
@ -7,7 +7,7 @@ import { v4 as getGuid } from 'uuid';
|
|||
import dataInterface from '../sql/Client';
|
||||
import * as durations from '../util/durations';
|
||||
import { downloadAttachment } from '../util/downloadAttachment';
|
||||
import { stringFromBytes } from '../Crypto';
|
||||
import * as Bytes from '../Bytes';
|
||||
import {
|
||||
AttachmentDownloadJobType,
|
||||
AttachmentDownloadJobTypeType,
|
||||
|
@ -319,7 +319,7 @@ async function _addAttachmentToMessage(
|
|||
attachment
|
||||
);
|
||||
message.set({
|
||||
body: attachment.error ? message.get('body') : stringFromBytes(data),
|
||||
body: attachment.error ? message.get('body') : Bytes.toString(data),
|
||||
bodyPending: false,
|
||||
});
|
||||
} finally {
|
||||
|
|
4
ts/model-types.d.ts
vendored
4
ts/model-types.d.ts
vendored
|
@ -94,7 +94,7 @@ export type MessageAttributesType = {
|
|||
bodyRanges?: BodyRangesType;
|
||||
callHistoryDetails?: CallHistoryDetailsFromDiskType;
|
||||
changedId?: string;
|
||||
dataMessage?: ArrayBuffer | null;
|
||||
dataMessage?: Uint8Array | null;
|
||||
decrypted_at?: number;
|
||||
deletedForEveryone?: boolean;
|
||||
deletedForEveryoneTimestamp?: number;
|
||||
|
@ -355,7 +355,7 @@ export type GroupV2PendingAdminApprovalType = {
|
|||
};
|
||||
|
||||
export type VerificationOptions = {
|
||||
key?: null | ArrayBuffer;
|
||||
key?: null | Uint8Array;
|
||||
viaContactSync?: boolean;
|
||||
viaStorageServiceSync?: boolean;
|
||||
viaSyncMessage?: boolean;
|
||||
|
|
|
@ -16,6 +16,8 @@ import {
|
|||
} from '../model-types.d';
|
||||
import { AttachmentType, isGIF } from '../types/Attachment';
|
||||
import { CallMode, CallHistoryDetailsType } from '../types/Calling';
|
||||
import * as EmbeddedContact from '../types/EmbeddedContact';
|
||||
import * as Conversation from '../types/Conversation';
|
||||
import * as Stickers from '../types/Stickers';
|
||||
import { CapabilityError } from '../types/errors';
|
||||
import type {
|
||||
|
@ -40,16 +42,10 @@ import { sniffImageMimeType } from '../util/sniffImageMimeType';
|
|||
import { isValidE164 } from '../util/isValidE164';
|
||||
import { MIMEType, IMAGE_JPEG, IMAGE_GIF, IMAGE_WEBP } from '../types/MIME';
|
||||
import { UUID } from '../types/UUID';
|
||||
import {
|
||||
arrayBufferToBase64,
|
||||
base64ToArrayBuffer,
|
||||
deriveAccessKey,
|
||||
fromEncodedBinaryToArrayBuffer,
|
||||
stringFromBytes,
|
||||
} from '../Crypto';
|
||||
import { deriveAccessKey, decryptProfileName, decryptProfile } from '../Crypto';
|
||||
import * as Bytes from '../Bytes';
|
||||
import { BodyRangesType } from '../types/Util';
|
||||
import { getTextWithMentions } from '../util';
|
||||
import { getTextWithMentions } from '../util/getTextWithMentions';
|
||||
import { migrateColor } from '../util/migrateColor';
|
||||
import { isNotNil } from '../util/isNotNil';
|
||||
import { dropNull } from '../util/dropNull';
|
||||
|
@ -98,14 +94,11 @@ import { getAvatarData } from '../util/getAvatarData';
|
|||
import { createIdenticon } from '../util/createIdenticon';
|
||||
import * as log from '../logging/log';
|
||||
|
||||
// TODO: remove once we move away from ArrayBuffers
|
||||
const FIXMEU8 = Uint8Array;
|
||||
|
||||
/* eslint-disable more/no-then */
|
||||
window.Whisper = window.Whisper || {};
|
||||
|
||||
const { Services, Util } = window.Signal;
|
||||
const { EmbeddedContact, Message } = window.Signal.Types;
|
||||
const { Message } = window.Signal.Types;
|
||||
const {
|
||||
deleteAttachmentData,
|
||||
doesAttachmentExist,
|
||||
|
@ -1125,7 +1118,7 @@ export class ConversationModel extends window.Backbone
|
|||
includePendingMembers,
|
||||
extraConversationsForSend,
|
||||
}: {
|
||||
groupChange?: ArrayBuffer;
|
||||
groupChange?: Uint8Array;
|
||||
includePendingMembers?: boolean;
|
||||
extraConversationsForSend?: Array<string>;
|
||||
} = {}): GroupV2InfoType | undefined {
|
||||
|
@ -1143,7 +1136,7 @@ export class ConversationModel extends window.Backbone
|
|||
includePendingMembers,
|
||||
extraConversationsForSend,
|
||||
}),
|
||||
groupChange: groupChange ? new FIXMEU8(groupChange) : undefined,
|
||||
groupChange,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1165,7 +1158,7 @@ export class ConversationModel extends window.Backbone
|
|||
};
|
||||
}
|
||||
|
||||
getGroupIdBuffer(): ArrayBuffer | undefined {
|
||||
getGroupIdBuffer(): Uint8Array | undefined {
|
||||
const groupIdString = this.get('groupId');
|
||||
|
||||
if (!groupIdString) {
|
||||
|
@ -1173,10 +1166,10 @@ export class ConversationModel extends window.Backbone
|
|||
}
|
||||
|
||||
if (isGroupV1(this.attributes)) {
|
||||
return fromEncodedBinaryToArrayBuffer(groupIdString);
|
||||
return Bytes.fromBinary(groupIdString);
|
||||
}
|
||||
if (isGroupV2(this.attributes)) {
|
||||
return base64ToArrayBuffer(groupIdString);
|
||||
return Bytes.fromBase64(groupIdString);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
@ -1253,12 +1246,9 @@ export class ConversationModel extends window.Backbone
|
|||
}
|
||||
|
||||
async cleanup(): Promise<void> {
|
||||
await window.Signal.Types.Conversation.deleteExternalFiles(
|
||||
this.attributes,
|
||||
{
|
||||
deleteAttachmentData,
|
||||
}
|
||||
);
|
||||
await Conversation.deleteExternalFiles(this.attributes, {
|
||||
deleteAttachmentData,
|
||||
});
|
||||
}
|
||||
|
||||
async onNewMessage(message: MessageModel): Promise<void> {
|
||||
|
@ -1838,7 +1828,7 @@ export class ConversationModel extends window.Backbone
|
|||
if (!error.response) {
|
||||
throw error;
|
||||
} else {
|
||||
const errorDetails = stringFromBytes(error.response);
|
||||
const errorDetails = Bytes.toString(error.response);
|
||||
if (errorDetails !== ALREADY_REQUESTED_TO_JOIN) {
|
||||
throw error;
|
||||
} else {
|
||||
|
@ -1914,7 +1904,7 @@ export class ConversationModel extends window.Backbone
|
|||
|
||||
async updateGroupAttributesV2(
|
||||
attributes: Readonly<{
|
||||
avatar?: undefined | ArrayBuffer;
|
||||
avatar?: undefined | Uint8Array;
|
||||
description?: string;
|
||||
title?: string;
|
||||
}>
|
||||
|
@ -3340,7 +3330,7 @@ export class ConversationModel extends window.Backbone
|
|||
const sendOptions = await getSendOptions(this.attributes);
|
||||
|
||||
const promise = (async () => {
|
||||
let profileKey: ArrayBuffer | undefined;
|
||||
let profileKey: Uint8Array | undefined;
|
||||
if (this.get('profileSharing')) {
|
||||
profileKey = await ourProfileKeyService.get();
|
||||
}
|
||||
|
@ -3474,7 +3464,7 @@ export class ConversationModel extends window.Backbone
|
|||
throw new Error('Cannot send reaction while offline!');
|
||||
}
|
||||
|
||||
let profileKey: ArrayBuffer | undefined;
|
||||
let profileKey: Uint8Array | undefined;
|
||||
if (this.get('profileSharing')) {
|
||||
profileKey = await ourProfileKeyService.get();
|
||||
}
|
||||
|
@ -3905,7 +3895,7 @@ export class ConversationModel extends window.Backbone
|
|||
return;
|
||||
}
|
||||
|
||||
const groupInviteLinkPassword = arrayBufferToBase64(
|
||||
const groupInviteLinkPassword = Bytes.toBase64(
|
||||
window.Signal.Groups.generateGroupInviteLinkPassword()
|
||||
);
|
||||
|
||||
|
@ -3932,9 +3922,7 @@ export class ConversationModel extends window.Backbone
|
|||
value && !this.get('groupInviteLinkPassword');
|
||||
const groupInviteLinkPassword =
|
||||
this.get('groupInviteLinkPassword') ||
|
||||
arrayBufferToBase64(
|
||||
window.Signal.Groups.generateGroupInviteLinkPassword()
|
||||
);
|
||||
Bytes.toBase64(window.Signal.Groups.generateGroupInviteLinkPassword());
|
||||
|
||||
log.info('toggleGroupLink for conversation', this.idForLogging(), value);
|
||||
|
||||
|
@ -4410,24 +4398,21 @@ export class ConversationModel extends window.Backbone
|
|||
if (!encryptedName) {
|
||||
return;
|
||||
}
|
||||
// isn't this already an ArrayBuffer?
|
||||
// isn't this already an Uint8Array?
|
||||
const key = (this.get('profileKey') as unknown) as string;
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
|
||||
// decode
|
||||
const keyBuffer = base64ToArrayBuffer(key);
|
||||
const keyBuffer = Bytes.fromBase64(key);
|
||||
|
||||
// decrypt
|
||||
const { given, family } = await window.textsecure.crypto.decryptProfileName(
|
||||
encryptedName,
|
||||
keyBuffer
|
||||
);
|
||||
const { given, family } = decryptProfileName(encryptedName, keyBuffer);
|
||||
|
||||
// encode
|
||||
const profileName = given ? stringFromBytes(given) : undefined;
|
||||
const profileFamilyName = family ? stringFromBytes(family) : undefined;
|
||||
const profileName = given ? Bytes.toString(given) : undefined;
|
||||
const profileFamilyName = family ? Bytes.toString(family) : undefined;
|
||||
|
||||
// set then check for changes
|
||||
const oldName = this.getProfileName();
|
||||
|
@ -4467,22 +4452,19 @@ export class ConversationModel extends window.Backbone
|
|||
}
|
||||
|
||||
const avatar = await window.textsecure.messaging.getAvatar(avatarPath);
|
||||
// isn't this already an ArrayBuffer?
|
||||
// isn't this already an Uint8Array?
|
||||
const key = (this.get('profileKey') as unknown) as string;
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
const keyBuffer = base64ToArrayBuffer(key);
|
||||
const keyBuffer = Bytes.fromBase64(key);
|
||||
|
||||
// decrypt
|
||||
const decrypted = await window.textsecure.crypto.decryptProfile(
|
||||
avatar,
|
||||
keyBuffer
|
||||
);
|
||||
const decrypted = decryptProfile(avatar, keyBuffer);
|
||||
|
||||
// update the conversation avatar only if hash differs
|
||||
if (decrypted) {
|
||||
const newAttributes = await window.Signal.Types.Conversation.maybeUpdateProfileAvatar(
|
||||
const newAttributes = await Conversation.maybeUpdateProfileAvatar(
|
||||
this.attributes,
|
||||
decrypted,
|
||||
{
|
||||
|
@ -4540,9 +4522,9 @@ export class ConversationModel extends window.Backbone
|
|||
return;
|
||||
}
|
||||
|
||||
const profileKeyBuffer = base64ToArrayBuffer(profileKey);
|
||||
const accessKeyBuffer = await deriveAccessKey(profileKeyBuffer);
|
||||
const accessKey = arrayBufferToBase64(accessKeyBuffer);
|
||||
const profileKeyBuffer = Bytes.fromBase64(profileKey);
|
||||
const accessKeyBuffer = deriveAccessKey(profileKeyBuffer);
|
||||
const accessKey = Bytes.toBase64(accessKeyBuffer);
|
||||
this.set({ accessKey });
|
||||
}
|
||||
|
||||
|
|
|
@ -41,8 +41,12 @@ import {
|
|||
getStickerPackStatus,
|
||||
} from '../types/Stickers';
|
||||
import * as Stickers from '../types/Stickers';
|
||||
import * as Errors from '../types/errors';
|
||||
import * as EmbeddedContact from '../types/EmbeddedContact';
|
||||
import { AttachmentType, isImage, isVideo } from '../types/Attachment';
|
||||
import * as Attachment from '../types/Attachment';
|
||||
import { IMAGE_WEBP, stringToMIMEType } from '../types/MIME';
|
||||
import * as MIME from '../types/MIME';
|
||||
import { ReadStatus } from '../messages/MessageReadStatus';
|
||||
import {
|
||||
SendActionType,
|
||||
|
@ -115,6 +119,8 @@ import { normalMessageSendJobQueue } from '../jobs/normalMessageSendJobQueue';
|
|||
import { notificationService } from '../services/notifications';
|
||||
import type { PreviewType as OutgoingPreviewType } from '../textsecure/SendMessage';
|
||||
import * as log from '../logging/log';
|
||||
import * as Bytes from '../Bytes';
|
||||
import { computeHash } from '../Crypto';
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
/* eslint-disable more/no-then */
|
||||
|
@ -128,13 +134,7 @@ declare const _: typeof window._;
|
|||
|
||||
window.Whisper = window.Whisper || {};
|
||||
|
||||
const {
|
||||
Message: TypedMessage,
|
||||
Attachment,
|
||||
MIME,
|
||||
EmbeddedContact,
|
||||
Errors,
|
||||
} = window.Signal.Types;
|
||||
const { Message: TypedMessage } = window.Signal.Types;
|
||||
const {
|
||||
deleteExternalMessageFiles,
|
||||
upgradeMessageSchema,
|
||||
|
@ -142,7 +142,6 @@ const {
|
|||
const { getTextWithMentions, GoogleChrome } = window.Signal.Util;
|
||||
|
||||
const { addStickerPackReference, getMessageBySender } = window.Signal.Data;
|
||||
const { bytesFromString } = window.Signal.Crypto;
|
||||
|
||||
export function isQuoteAMatch(
|
||||
message: MessageModel | null | undefined,
|
||||
|
@ -1560,7 +1559,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
}
|
||||
|
||||
async sendSyncMessageOnly(
|
||||
dataMessage: ArrayBuffer,
|
||||
dataMessage: Uint8Array,
|
||||
saveErrors?: (errors: Array<Error>) => void
|
||||
): Promise<void> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
@ -2691,9 +2690,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
downloadedAvatar
|
||||
);
|
||||
|
||||
hash = await window.Signal.Types.Conversation.computeHash(
|
||||
loadedAttachment.data
|
||||
);
|
||||
hash = computeHash(loadedAttachment.data);
|
||||
}
|
||||
} catch (err) {
|
||||
log.info('handleDataMessage: group avatar download failed');
|
||||
|
@ -2719,7 +2716,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
|
||||
let avatar = null;
|
||||
if (downloadedAvatar && avatarAttachment !== null) {
|
||||
const onDiskAttachment = await window.Signal.Types.Attachment.migrateDataToFileSystem(
|
||||
const onDiskAttachment = await Attachment.migrateDataToFileSystem(
|
||||
downloadedAvatar,
|
||||
{
|
||||
writeNewAttachmentData:
|
||||
|
@ -3328,7 +3325,7 @@ window.Whisper.Message.getLongMessageAttachment = ({
|
|||
};
|
||||
}
|
||||
|
||||
const data = bytesFromString(body);
|
||||
const data = Bytes.fromString(body);
|
||||
const attachment = {
|
||||
contentType: MIME.LONG_MESSAGE,
|
||||
fileName: `long-message-${now}.txt`,
|
||||
|
|
|
@ -56,11 +56,7 @@ import { LocalizerType } from '../types/Util';
|
|||
import { UUID } from '../types/UUID';
|
||||
import { ConversationModel } from '../models/conversations';
|
||||
import * as Bytes from '../Bytes';
|
||||
import {
|
||||
uuidToArrayBuffer,
|
||||
arrayBufferToUuid,
|
||||
typedArrayToArrayBuffer,
|
||||
} from '../Crypto';
|
||||
import { uuidToBytes, bytesToUuid } from '../Crypto';
|
||||
import { dropNull, shallowDropNull } from '../util/dropNull';
|
||||
import { getOwn } from '../util/getOwn';
|
||||
import { isNormalNumber } from '../util/isNormalNumber';
|
||||
|
@ -270,7 +266,7 @@ export class CallingClass {
|
|||
return;
|
||||
}
|
||||
|
||||
RingRTC.setSelfUuid(Buffer.from(uuidToArrayBuffer(ourUuid)));
|
||||
RingRTC.setSelfUuid(Buffer.from(uuidToBytes(ourUuid)));
|
||||
}
|
||||
|
||||
async startCallingLobby(
|
||||
|
@ -480,7 +476,7 @@ export class CallingClass {
|
|||
return getMembershipList(conversationId).map(
|
||||
member =>
|
||||
new GroupMemberInfo(
|
||||
Buffer.from(uuidToArrayBuffer(member.uuid)),
|
||||
Buffer.from(uuidToBytes(member.uuid)),
|
||||
Buffer.from(member.uuidCiphertext)
|
||||
)
|
||||
);
|
||||
|
@ -772,18 +768,16 @@ export class CallingClass {
|
|||
): GroupCallPeekInfoType {
|
||||
return {
|
||||
uuids: peekInfo.joinedMembers.map(uuidBuffer => {
|
||||
let uuid = arrayBufferToUuid(typedArrayToArrayBuffer(uuidBuffer));
|
||||
let uuid = bytesToUuid(uuidBuffer);
|
||||
if (!uuid) {
|
||||
log.error(
|
||||
'Calling.formatGroupCallPeekInfoForRedux: could not convert peek UUID ArrayBuffer to string; using fallback UUID'
|
||||
'Calling.formatGroupCallPeekInfoForRedux: could not convert peek UUID Uint8Array to string; using fallback UUID'
|
||||
);
|
||||
uuid = '00000000-0000-0000-0000-000000000000';
|
||||
}
|
||||
return uuid;
|
||||
}),
|
||||
creatorUuid:
|
||||
peekInfo.creator &&
|
||||
arrayBufferToUuid(typedArrayToArrayBuffer(peekInfo.creator)),
|
||||
creatorUuid: peekInfo.creator && bytesToUuid(peekInfo.creator),
|
||||
eraId: peekInfo.eraId,
|
||||
maxDevices: peekInfo.maxDevices ?? Infinity,
|
||||
deviceCount: peekInfo.deviceCount,
|
||||
|
@ -820,12 +814,10 @@ export class CallingClass {
|
|||
? this.formatGroupCallPeekInfoForRedux(peekInfo)
|
||||
: undefined,
|
||||
remoteParticipants: remoteDeviceStates.map(remoteDeviceState => {
|
||||
let uuid = arrayBufferToUuid(
|
||||
typedArrayToArrayBuffer(remoteDeviceState.userId)
|
||||
);
|
||||
let uuid = bytesToUuid(remoteDeviceState.userId);
|
||||
if (!uuid) {
|
||||
log.error(
|
||||
'Calling.formatGroupCallForRedux: could not convert remote participant UUID ArrayBuffer to string; using fallback UUID'
|
||||
'Calling.formatGroupCallForRedux: could not convert remote participant UUID Uint8Array to string; using fallback UUID'
|
||||
);
|
||||
uuid = '00000000-0000-0000-0000-000000000000';
|
||||
}
|
||||
|
@ -1441,7 +1433,7 @@ export class CallingClass {
|
|||
}
|
||||
|
||||
const sourceUuid = envelope.sourceUuid
|
||||
? uuidToArrayBuffer(envelope.sourceUuid)
|
||||
? uuidToBytes(envelope.sourceUuid)
|
||||
: null;
|
||||
|
||||
const messageAgeSec = envelope.messageAgeSec ? envelope.messageAgeSec : 0;
|
||||
|
@ -1531,7 +1523,7 @@ export class CallingClass {
|
|||
data: Uint8Array,
|
||||
urgency: CallMessageUrgency
|
||||
): Promise<boolean> {
|
||||
const userId = arrayBufferToUuid(typedArrayToArrayBuffer(recipient));
|
||||
const userId = bytesToUuid(recipient);
|
||||
if (!userId) {
|
||||
log.error('handleSendCallMessage(): bad recipient UUID');
|
||||
return false;
|
||||
|
@ -1594,7 +1586,7 @@ export class CallingClass {
|
|||
|
||||
const groupId = groupIdBytes.toString('base64');
|
||||
|
||||
const ringerUuid = arrayBufferToUuid(typedArrayToArrayBuffer(ringerBytes));
|
||||
const ringerUuid = bytesToUuid(ringerBytes);
|
||||
if (!ringerUuid) {
|
||||
log.error('handleGroupCallRingUpdate(): ringerUuid was invalid');
|
||||
return;
|
||||
|
@ -1862,7 +1854,7 @@ export class CallingClass {
|
|||
url,
|
||||
httpMethod,
|
||||
headers,
|
||||
body ? typedArrayToArrayBuffer(body) : undefined
|
||||
body
|
||||
);
|
||||
} catch (err) {
|
||||
if (err.code !== -1) {
|
||||
|
@ -1925,7 +1917,10 @@ export class CallingClass {
|
|||
const isContactUnknown = !conversation.isFromOrAddedByTrustedContact();
|
||||
|
||||
return {
|
||||
iceServer,
|
||||
iceServer: {
|
||||
...iceServer,
|
||||
urls: iceServer.urls.slice(),
|
||||
},
|
||||
hideIp: shouldRelayCalls || isContactUnknown,
|
||||
bandwidthMode: BandwidthMode.Normal,
|
||||
};
|
||||
|
@ -2005,9 +2000,7 @@ export class CallingClass {
|
|||
if (!peekInfo || !peekInfo.eraId || !peekInfo.creator) {
|
||||
return;
|
||||
}
|
||||
const creatorUuid = arrayBufferToUuid(
|
||||
typedArrayToArrayBuffer(peekInfo.creator)
|
||||
);
|
||||
const creatorUuid = bytesToUuid(peekInfo.creator);
|
||||
if (!creatorUuid) {
|
||||
log.error('updateCallHistoryForGroupCall(): bad creator UUID');
|
||||
return;
|
||||
|
|
|
@ -7,7 +7,7 @@ import * as log from '../logging/log';
|
|||
import { StorageInterface } from '../types/Storage.d';
|
||||
|
||||
export class OurProfileKeyService {
|
||||
private getPromise: undefined | Promise<undefined | ArrayBuffer>;
|
||||
private getPromise: undefined | Promise<undefined | Uint8Array>;
|
||||
|
||||
private promisesBlockingGet: Array<Promise<unknown>> = [];
|
||||
|
||||
|
@ -26,7 +26,7 @@ export class OurProfileKeyService {
|
|||
this.storage = storage;
|
||||
}
|
||||
|
||||
get(): Promise<undefined | ArrayBuffer> {
|
||||
get(): Promise<undefined | Uint8Array> {
|
||||
if (this.getPromise) {
|
||||
log.info(
|
||||
'Our profile key service: was already fetching. Piggybacking off of that'
|
||||
|
@ -38,7 +38,7 @@ export class OurProfileKeyService {
|
|||
return this.getPromise;
|
||||
}
|
||||
|
||||
async set(newValue: undefined | ArrayBuffer): Promise<void> {
|
||||
async set(newValue: undefined | Uint8Array): Promise<void> {
|
||||
log.info('Our profile key service: updating profile key');
|
||||
assert(this.storage, 'OurProfileKeyService was not initialized');
|
||||
if (newValue) {
|
||||
|
@ -52,7 +52,7 @@ export class OurProfileKeyService {
|
|||
this.promisesBlockingGet.push(promise);
|
||||
}
|
||||
|
||||
private async doGet(): Promise<undefined | ArrayBuffer> {
|
||||
private async doGet(): Promise<undefined | Uint8Array> {
|
||||
log.info(
|
||||
`Our profile key service: waiting for ${this.promisesBlockingGet.length} promises before fetching`
|
||||
);
|
||||
|
@ -66,13 +66,13 @@ export class OurProfileKeyService {
|
|||
|
||||
log.info('Our profile key service: fetching profile key from storage');
|
||||
const result = this.storage.get('profileKey');
|
||||
if (result === undefined || result instanceof ArrayBuffer) {
|
||||
if (result === undefined || result instanceof Uint8Array) {
|
||||
return result;
|
||||
}
|
||||
|
||||
assert(
|
||||
false,
|
||||
'Profile key in storage was defined, but not an ArrayBuffer. Returning undefined'
|
||||
'Profile key in storage was defined, but not an Uint8Array. Returning undefined'
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import {
|
|||
SerializedCertificateType,
|
||||
} from '../textsecure/OutgoingMessage';
|
||||
import * as Bytes from '../Bytes';
|
||||
import { typedArrayToArrayBuffer } from '../Crypto';
|
||||
import { assert } from '../util/assert';
|
||||
import { missingCaseError } from '../util/missingCaseError';
|
||||
import { normalizeNumber } from '../util/normalizeNumber';
|
||||
|
@ -177,7 +176,7 @@ export class SenderCertificateService {
|
|||
|
||||
const serializedCertificate = {
|
||||
expires: expires - CLOCK_SKEW_THRESHOLD,
|
||||
serialized: typedArrayToArrayBuffer(certificate),
|
||||
serialized: certificate,
|
||||
};
|
||||
|
||||
await storage.put(modeToStorageKey(mode), serializedCertificate);
|
||||
|
|
|
@ -4,15 +4,14 @@
|
|||
import { debounce, isNumber } from 'lodash';
|
||||
import pMap from 'p-map';
|
||||
|
||||
import Crypto from '../textsecure/Crypto';
|
||||
import dataInterface from '../sql/Client';
|
||||
import * as Bytes from '../Bytes';
|
||||
import {
|
||||
arrayBufferToBase64,
|
||||
base64ToArrayBuffer,
|
||||
getRandomBytes,
|
||||
deriveStorageItemKey,
|
||||
deriveStorageManifestKey,
|
||||
typedArrayToArrayBuffer,
|
||||
encryptProfile,
|
||||
decryptProfile,
|
||||
} from '../Crypto';
|
||||
import {
|
||||
mergeAccountRecord,
|
||||
|
@ -44,9 +43,6 @@ import * as log from '../logging/log';
|
|||
|
||||
type IManifestRecordIdentifier = Proto.ManifestRecord.IIdentifier;
|
||||
|
||||
// TODO: remove once we move away from ArrayBuffers
|
||||
const FIXMEU8 = Uint8Array;
|
||||
|
||||
const {
|
||||
eraseStorageServiceStateFromConversations,
|
||||
updateConversation,
|
||||
|
@ -94,32 +90,32 @@ async function encryptRecord(
|
|||
const storageItem = new Proto.StorageItem();
|
||||
|
||||
const storageKeyBuffer = storageID
|
||||
? base64ToArrayBuffer(String(storageID))
|
||||
? Bytes.fromBase64(String(storageID))
|
||||
: generateStorageID();
|
||||
|
||||
const storageKeyBase64 = window.storage.get('storageKey');
|
||||
if (!storageKeyBase64) {
|
||||
throw new Error('No storage key');
|
||||
}
|
||||
const storageKey = base64ToArrayBuffer(storageKeyBase64);
|
||||
const storageItemKey = await deriveStorageItemKey(
|
||||
const storageKey = Bytes.fromBase64(storageKeyBase64);
|
||||
const storageItemKey = deriveStorageItemKey(
|
||||
storageKey,
|
||||
arrayBufferToBase64(storageKeyBuffer)
|
||||
Bytes.toBase64(storageKeyBuffer)
|
||||
);
|
||||
|
||||
const encryptedRecord = await Crypto.encryptProfile(
|
||||
typedArrayToArrayBuffer(Proto.StorageRecord.encode(storageRecord).finish()),
|
||||
const encryptedRecord = encryptProfile(
|
||||
Proto.StorageRecord.encode(storageRecord).finish(),
|
||||
storageItemKey
|
||||
);
|
||||
|
||||
storageItem.key = new FIXMEU8(storageKeyBuffer);
|
||||
storageItem.value = new FIXMEU8(encryptedRecord);
|
||||
storageItem.key = storageKeyBuffer;
|
||||
storageItem.value = encryptedRecord;
|
||||
|
||||
return storageItem;
|
||||
}
|
||||
|
||||
function generateStorageID(): ArrayBuffer {
|
||||
return Crypto.getRandomBytes(16);
|
||||
function generateStorageID(): Uint8Array {
|
||||
return getRandomBytes(16);
|
||||
}
|
||||
|
||||
type GeneratedManifestType = {
|
||||
|
@ -127,7 +123,7 @@ type GeneratedManifestType = {
|
|||
conversation: ConversationModel;
|
||||
storageID: string | undefined;
|
||||
}>;
|
||||
deleteKeys: Array<ArrayBuffer>;
|
||||
deleteKeys: Array<Uint8Array>;
|
||||
newItems: Set<Proto.IStorageItem>;
|
||||
storageManifest: Proto.IStorageManifest;
|
||||
};
|
||||
|
@ -149,7 +145,7 @@ async function generateManifest(
|
|||
|
||||
const conversationsToUpdate = [];
|
||||
const insertKeys: Array<string> = [];
|
||||
const deleteKeys: Array<ArrayBuffer> = [];
|
||||
const deleteKeys: Array<Uint8Array> = [];
|
||||
const manifestRecordKeys: Set<IManifestRecordIdentifier> = new Set();
|
||||
const newItems: Set<Proto.IStorageItem> = new Set();
|
||||
|
||||
|
@ -202,7 +198,7 @@ async function generateManifest(
|
|||
!currentStorageID;
|
||||
|
||||
const storageID = isNewItem
|
||||
? arrayBufferToBase64(generateStorageID())
|
||||
? Bytes.toBase64(generateStorageID())
|
||||
: currentStorageID;
|
||||
|
||||
let storageItem;
|
||||
|
@ -243,7 +239,7 @@ async function generateManifest(
|
|||
'storageService.generateManifest: deleting key',
|
||||
redactStorageID(oldStorageID)
|
||||
);
|
||||
deleteKeys.push(base64ToArrayBuffer(oldStorageID));
|
||||
deleteKeys.push(Bytes.fromBase64(oldStorageID));
|
||||
}
|
||||
|
||||
conversationsToUpdate.push({
|
||||
|
@ -323,7 +319,7 @@ async function generateManifest(
|
|||
|
||||
// Ensure all deletes are not present in the manifest
|
||||
const hasDeleteKey = deleteKeys.find(
|
||||
key => arrayBufferToBase64(key) === storageID
|
||||
key => Bytes.toBase64(key) === storageID
|
||||
);
|
||||
if (hasDeleteKey) {
|
||||
log.info(
|
||||
|
@ -400,7 +396,7 @@ async function generateManifest(
|
|||
|
||||
if (deleteKeys.length !== pendingDeletes.size) {
|
||||
const localDeletes = deleteKeys.map(key =>
|
||||
redactStorageID(arrayBufferToBase64(key))
|
||||
redactStorageID(Bytes.toBase64(key))
|
||||
);
|
||||
const remoteDeletes: Array<string> = [];
|
||||
pendingDeletes.forEach(id => remoteDeletes.push(redactStorageID(id)));
|
||||
|
@ -417,7 +413,7 @@ async function generateManifest(
|
|||
throw new Error('invalid write insert items length do not match');
|
||||
}
|
||||
deleteKeys.forEach(key => {
|
||||
const storageID = arrayBufferToBase64(key);
|
||||
const storageID = Bytes.toBase64(key);
|
||||
if (!pendingDeletes.has(storageID)) {
|
||||
throw new Error(
|
||||
'invalid write delete key missing from pending deletes'
|
||||
|
@ -441,21 +437,16 @@ async function generateManifest(
|
|||
if (!storageKeyBase64) {
|
||||
throw new Error('No storage key');
|
||||
}
|
||||
const storageKey = base64ToArrayBuffer(storageKeyBase64);
|
||||
const storageManifestKey = await deriveStorageManifestKey(
|
||||
storageKey,
|
||||
version
|
||||
);
|
||||
const encryptedManifest = await Crypto.encryptProfile(
|
||||
typedArrayToArrayBuffer(
|
||||
Proto.ManifestRecord.encode(manifestRecord).finish()
|
||||
),
|
||||
const storageKey = Bytes.fromBase64(storageKeyBase64);
|
||||
const storageManifestKey = deriveStorageManifestKey(storageKey, version);
|
||||
const encryptedManifest = encryptProfile(
|
||||
Proto.ManifestRecord.encode(manifestRecord).finish(),
|
||||
storageManifestKey
|
||||
);
|
||||
|
||||
const storageManifest = new Proto.StorageManifest();
|
||||
storageManifest.version = version;
|
||||
storageManifest.value = new FIXMEU8(encryptedManifest);
|
||||
storageManifest.value = encryptedManifest;
|
||||
|
||||
return {
|
||||
conversationsToUpdate,
|
||||
|
@ -494,13 +485,11 @@ async function uploadManifest(
|
|||
const writeOperation = new Proto.WriteOperation();
|
||||
writeOperation.manifest = storageManifest;
|
||||
writeOperation.insertItem = Array.from(newItems);
|
||||
writeOperation.deleteKey = deleteKeys.map(key => new FIXMEU8(key));
|
||||
writeOperation.deleteKey = deleteKeys;
|
||||
|
||||
log.info('storageService.uploadManifest: uploading...', version);
|
||||
await window.textsecure.messaging.modifyStorageRecords(
|
||||
typedArrayToArrayBuffer(
|
||||
Proto.WriteOperation.encode(writeOperation).finish()
|
||||
),
|
||||
Proto.WriteOperation.encode(writeOperation).finish(),
|
||||
{
|
||||
credentials,
|
||||
}
|
||||
|
@ -626,19 +615,16 @@ async function decryptManifest(
|
|||
if (!storageKeyBase64) {
|
||||
throw new Error('No storage key');
|
||||
}
|
||||
const storageKey = base64ToArrayBuffer(storageKeyBase64);
|
||||
const storageManifestKey = await deriveStorageManifestKey(
|
||||
const storageKey = Bytes.fromBase64(storageKeyBase64);
|
||||
const storageManifestKey = deriveStorageManifestKey(
|
||||
storageKey,
|
||||
normalizeNumber(version ?? 0)
|
||||
);
|
||||
|
||||
strictAssert(value, 'StorageManifest has no value field');
|
||||
const decryptedManifest = await Crypto.decryptProfile(
|
||||
typedArrayToArrayBuffer(value),
|
||||
storageManifestKey
|
||||
);
|
||||
const decryptedManifest = decryptProfile(value, storageManifestKey);
|
||||
|
||||
return Proto.ManifestRecord.decode(new FIXMEU8(decryptedManifest));
|
||||
return Proto.ManifestRecord.decode(decryptedManifest);
|
||||
}
|
||||
|
||||
async function fetchManifest(
|
||||
|
@ -660,9 +646,7 @@ async function fetchManifest(
|
|||
greaterThanVersion: manifestVersion,
|
||||
}
|
||||
);
|
||||
const encryptedManifest = Proto.StorageManifest.decode(
|
||||
new FIXMEU8(manifestBinary)
|
||||
);
|
||||
const encryptedManifest = Proto.StorageManifest.decode(manifestBinary);
|
||||
|
||||
// if we don't get a value we're assuming that there's no newer manifest
|
||||
if (!encryptedManifest.value || !encryptedManifest.version) {
|
||||
|
@ -855,7 +839,7 @@ async function processRemoteRecords(
|
|||
if (!storageKeyBase64) {
|
||||
throw new Error('No storage key');
|
||||
}
|
||||
const storageKey = base64ToArrayBuffer(storageKeyBase64);
|
||||
const storageKey = Bytes.fromBase64(storageKeyBase64);
|
||||
|
||||
log.info(
|
||||
'storageService.processRemoteRecords: remote only keys',
|
||||
|
@ -869,15 +853,13 @@ async function processRemoteRecords(
|
|||
|
||||
const credentials = window.storage.get('storageCredentials');
|
||||
const storageItemsBuffer = await window.textsecure.messaging.getStorageRecords(
|
||||
typedArrayToArrayBuffer(Proto.ReadOperation.encode(readOperation).finish()),
|
||||
Proto.ReadOperation.encode(readOperation).finish(),
|
||||
{
|
||||
credentials,
|
||||
}
|
||||
);
|
||||
|
||||
const storageItems = Proto.StorageItems.decode(
|
||||
new FIXMEU8(storageItemsBuffer)
|
||||
);
|
||||
const storageItems = Proto.StorageItems.decode(storageItemsBuffer);
|
||||
|
||||
if (!storageItems.items) {
|
||||
log.info('storageService.processRemoteRecords: No storage items retrieved');
|
||||
|
@ -903,15 +885,12 @@ async function processRemoteRecords(
|
|||
|
||||
const base64ItemID = Bytes.toBase64(key);
|
||||
|
||||
const storageItemKey = await deriveStorageItemKey(
|
||||
storageKey,
|
||||
base64ItemID
|
||||
);
|
||||
const storageItemKey = deriveStorageItemKey(storageKey, base64ItemID);
|
||||
|
||||
let storageItemPlaintext;
|
||||
try {
|
||||
storageItemPlaintext = await Crypto.decryptProfile(
|
||||
typedArrayToArrayBuffer(storageItemCiphertext),
|
||||
storageItemPlaintext = decryptProfile(
|
||||
storageItemCiphertext,
|
||||
storageItemKey
|
||||
);
|
||||
} catch (err) {
|
||||
|
@ -922,9 +901,7 @@ async function processRemoteRecords(
|
|||
throw err;
|
||||
}
|
||||
|
||||
const storageRecord = Proto.StorageRecord.decode(
|
||||
new FIXMEU8(storageItemPlaintext)
|
||||
);
|
||||
const storageRecord = Proto.StorageRecord.decode(storageItemPlaintext);
|
||||
|
||||
const remoteRecord = remoteOnlyRecords.get(base64ItemID);
|
||||
if (!remoteRecord) {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import { isEqual, isNumber } from 'lodash';
|
||||
import Long from 'long';
|
||||
|
||||
import { deriveMasterKeyFromGroupV1, typedArrayToArrayBuffer } from '../Crypto';
|
||||
import { deriveMasterKeyFromGroupV1 } from '../Crypto';
|
||||
import * as Bytes from '../Bytes';
|
||||
import dataInterface from '../sql/Client';
|
||||
import {
|
||||
|
@ -43,9 +43,6 @@ import * as log from '../logging/log';
|
|||
|
||||
const { updateConversation } = dataInterface;
|
||||
|
||||
// TODO: remove once we move away from ArrayBuffers
|
||||
const FIXMEU8 = Uint8Array;
|
||||
|
||||
type RecordClass =
|
||||
| Proto.IAccountRecord
|
||||
| Proto.IContactRecord
|
||||
|
@ -134,7 +131,7 @@ export async function toContactRecord(
|
|||
? await window.textsecure.storage.protocol.loadIdentityKey(maybeUuid)
|
||||
: undefined;
|
||||
if (identityKey) {
|
||||
contactRecord.identityKey = new FIXMEU8(identityKey);
|
||||
contactRecord.identityKey = identityKey;
|
||||
}
|
||||
const verified = conversation.get('verified');
|
||||
if (verified) {
|
||||
|
@ -397,13 +394,13 @@ function doRecordsConflict(
|
|||
const localValue = localRecord[key];
|
||||
const remoteValue = remoteRecord[key];
|
||||
|
||||
// Sometimes we have a ByteBuffer and an ArrayBuffer, this ensures that we
|
||||
// Sometimes we have a ByteBuffer and an Uint8Array, this ensures that we
|
||||
// are comparing them both equally by converting them into base64 string.
|
||||
if (localValue instanceof Uint8Array) {
|
||||
const areEqual = Bytes.areEqual(localValue, remoteValue);
|
||||
if (!areEqual) {
|
||||
log.info(
|
||||
'storageService.doRecordsConflict: Conflict found for ArrayBuffer',
|
||||
'storageService.doRecordsConflict: Conflict found for Uint8Array',
|
||||
key,
|
||||
idForLogging
|
||||
);
|
||||
|
@ -525,10 +522,8 @@ export async function mergeGroupV1Record(
|
|||
// It's possible this group was migrated to a GV2 if so we attempt to
|
||||
// retrieve the master key and find the conversation locally. If we
|
||||
// are successful then we continue setting and applying state.
|
||||
const masterKeyBuffer = await deriveMasterKeyFromGroupV1(
|
||||
typedArrayToArrayBuffer(groupV1Record.id)
|
||||
);
|
||||
const fields = deriveGroupFields(new FIXMEU8(masterKeyBuffer));
|
||||
const masterKeyBuffer = deriveMasterKeyFromGroupV1(groupV1Record.id);
|
||||
const fields = deriveGroupFields(masterKeyBuffer);
|
||||
const derivedGroupV2Id = Bytes.toBase64(fields.id);
|
||||
|
||||
log.info(
|
||||
|
@ -771,9 +766,7 @@ export async function mergeContactRecord(
|
|||
const storageServiceVerified = contactRecord.identityState || 0;
|
||||
if (verified !== storageServiceVerified) {
|
||||
const verifiedOptions = {
|
||||
key: contactRecord.identityKey
|
||||
? typedArrayToArrayBuffer(contactRecord.identityKey)
|
||||
: undefined,
|
||||
key: contactRecord.identityKey,
|
||||
viaStorageServiceSync: true,
|
||||
};
|
||||
const STATE_ENUM = Proto.ContactRecord.IdentityState;
|
||||
|
@ -900,7 +893,7 @@ export async function mergeAccountRecord(
|
|||
window.storage.put('phoneNumberDiscoverability', discoverability);
|
||||
|
||||
if (profileKey) {
|
||||
ourProfileKeyService.set(typedArrayToArrayBuffer(profileKey));
|
||||
ourProfileKeyService.set(profileKey);
|
||||
}
|
||||
|
||||
if (pinnedConversations) {
|
||||
|
|
|
@ -10,7 +10,7 @@ import { handleMessageSend } from '../util/handleMessageSend';
|
|||
|
||||
export async function writeProfile(
|
||||
conversation: ConversationType,
|
||||
avatarBuffer?: ArrayBuffer
|
||||
avatarBuffer?: Uint8Array
|
||||
): Promise<void> {
|
||||
// Before we write anything we request the user's profile so that we can
|
||||
// have an up-to-date paymentAddress to be able to include it when we write
|
||||
|
|
|
@ -24,7 +24,7 @@ import {
|
|||
uniq,
|
||||
} from 'lodash';
|
||||
|
||||
import { arrayBufferToBase64, base64ToArrayBuffer } from '../Crypto';
|
||||
import * as Bytes from '../Bytes';
|
||||
import { CURRENT_SCHEMA_VERSION } from '../../js/modules/types/message';
|
||||
import { createBatcher } from '../util/batcher';
|
||||
import { assert } from '../util/assert';
|
||||
|
@ -577,7 +577,7 @@ function makeChannel(fnName: string) {
|
|||
};
|
||||
}
|
||||
|
||||
function keysToArrayBuffer(keys: Array<string>, data: any) {
|
||||
function keysToBytes(keys: Array<string>, data: any) {
|
||||
const updated = cloneDeep(data);
|
||||
|
||||
const max = keys.length;
|
||||
|
@ -586,14 +586,14 @@ function keysToArrayBuffer(keys: Array<string>, data: any) {
|
|||
const value = get(data, key);
|
||||
|
||||
if (value) {
|
||||
set(updated, key, base64ToArrayBuffer(value));
|
||||
set(updated, key, Bytes.fromBase64(value));
|
||||
}
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
function keysFromArrayBuffer(keys: Array<string>, data: any) {
|
||||
function keysFromBytes(keys: Array<string>, data: any) {
|
||||
const updated = cloneDeep(data);
|
||||
|
||||
const max = keys.length;
|
||||
|
@ -602,7 +602,7 @@ function keysFromArrayBuffer(keys: Array<string>, data: any) {
|
|||
const value = get(data, key);
|
||||
|
||||
if (value) {
|
||||
set(updated, key, arrayBufferToBase64(value));
|
||||
set(updated, key, Bytes.toBase64(value));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -637,18 +637,16 @@ async function removeIndexedDBFiles() {
|
|||
|
||||
const IDENTITY_KEY_KEYS = ['publicKey'];
|
||||
async function createOrUpdateIdentityKey(data: IdentityKeyType) {
|
||||
const updated = keysFromArrayBuffer(IDENTITY_KEY_KEYS, data);
|
||||
const updated = keysFromBytes(IDENTITY_KEY_KEYS, data);
|
||||
await channels.createOrUpdateIdentityKey(updated);
|
||||
}
|
||||
async function getIdentityKeyById(id: IdentityKeyIdType) {
|
||||
const data = await channels.getIdentityKeyById(id);
|
||||
|
||||
return keysToArrayBuffer(IDENTITY_KEY_KEYS, data);
|
||||
return keysToBytes(IDENTITY_KEY_KEYS, data);
|
||||
}
|
||||
async function bulkAddIdentityKeys(array: Array<IdentityKeyType>) {
|
||||
const updated = map(array, data =>
|
||||
keysFromArrayBuffer(IDENTITY_KEY_KEYS, data)
|
||||
);
|
||||
const updated = map(array, data => keysFromBytes(IDENTITY_KEY_KEYS, data));
|
||||
await channels.bulkAddIdentityKeys(updated);
|
||||
}
|
||||
async function removeIdentityKeyById(id: IdentityKeyIdType) {
|
||||
|
@ -660,22 +658,22 @@ async function removeAllIdentityKeys() {
|
|||
async function getAllIdentityKeys() {
|
||||
const keys = await channels.getAllIdentityKeys();
|
||||
|
||||
return keys.map(key => keysToArrayBuffer(IDENTITY_KEY_KEYS, key));
|
||||
return keys.map(key => keysToBytes(IDENTITY_KEY_KEYS, key));
|
||||
}
|
||||
|
||||
// Pre Keys
|
||||
|
||||
async function createOrUpdatePreKey(data: PreKeyType) {
|
||||
const updated = keysFromArrayBuffer(PRE_KEY_KEYS, data);
|
||||
const updated = keysFromBytes(PRE_KEY_KEYS, data);
|
||||
await channels.createOrUpdatePreKey(updated);
|
||||
}
|
||||
async function getPreKeyById(id: PreKeyIdType) {
|
||||
const data = await channels.getPreKeyById(id);
|
||||
|
||||
return keysToArrayBuffer(PRE_KEY_KEYS, data);
|
||||
return keysToBytes(PRE_KEY_KEYS, data);
|
||||
}
|
||||
async function bulkAddPreKeys(array: Array<PreKeyType>) {
|
||||
const updated = map(array, data => keysFromArrayBuffer(PRE_KEY_KEYS, data));
|
||||
const updated = map(array, data => keysFromBytes(PRE_KEY_KEYS, data));
|
||||
await channels.bulkAddPreKeys(updated);
|
||||
}
|
||||
async function removePreKeyById(id: PreKeyIdType) {
|
||||
|
@ -687,30 +685,28 @@ async function removeAllPreKeys() {
|
|||
async function getAllPreKeys() {
|
||||
const keys = await channels.getAllPreKeys();
|
||||
|
||||
return keys.map(key => keysToArrayBuffer(PRE_KEY_KEYS, key));
|
||||
return keys.map(key => keysToBytes(PRE_KEY_KEYS, key));
|
||||
}
|
||||
|
||||
// Signed Pre Keys
|
||||
|
||||
const PRE_KEY_KEYS = ['privateKey', 'publicKey'];
|
||||
async function createOrUpdateSignedPreKey(data: SignedPreKeyType) {
|
||||
const updated = keysFromArrayBuffer(PRE_KEY_KEYS, data);
|
||||
const updated = keysFromBytes(PRE_KEY_KEYS, data);
|
||||
await channels.createOrUpdateSignedPreKey(updated);
|
||||
}
|
||||
async function getSignedPreKeyById(id: SignedPreKeyIdType) {
|
||||
const data = await channels.getSignedPreKeyById(id);
|
||||
|
||||
return keysToArrayBuffer(PRE_KEY_KEYS, data);
|
||||
return keysToBytes(PRE_KEY_KEYS, data);
|
||||
}
|
||||
async function getAllSignedPreKeys() {
|
||||
const keys = await channels.getAllSignedPreKeys();
|
||||
|
||||
return keys.map((key: SignedPreKeyType) =>
|
||||
keysToArrayBuffer(PRE_KEY_KEYS, key)
|
||||
);
|
||||
return keys.map((key: SignedPreKeyType) => keysToBytes(PRE_KEY_KEYS, key));
|
||||
}
|
||||
async function bulkAddSignedPreKeys(array: Array<SignedPreKeyType>) {
|
||||
const updated = map(array, data => keysFromArrayBuffer(PRE_KEY_KEYS, data));
|
||||
const updated = map(array, data => keysFromBytes(PRE_KEY_KEYS, data));
|
||||
await channels.bulkAddSignedPreKeys(updated);
|
||||
}
|
||||
async function removeSignedPreKeyById(id: SignedPreKeyIdType) {
|
||||
|
@ -736,7 +732,7 @@ async function createOrUpdateItem<K extends ItemKeyType>(data: ItemType<K>) {
|
|||
}
|
||||
|
||||
const keys = ITEM_KEYS[id];
|
||||
const updated = Array.isArray(keys) ? keysFromArrayBuffer(keys, data) : data;
|
||||
const updated = Array.isArray(keys) ? keysFromBytes(keys, data) : data;
|
||||
|
||||
await channels.createOrUpdateItem(updated);
|
||||
}
|
||||
|
@ -746,7 +742,7 @@ async function getItemById<K extends ItemKeyType>(
|
|||
const keys = ITEM_KEYS[id];
|
||||
const data = await channels.getItemById(id);
|
||||
|
||||
return Array.isArray(keys) ? keysToArrayBuffer(keys, data) : data;
|
||||
return Array.isArray(keys) ? keysToBytes(keys, data) : data;
|
||||
}
|
||||
async function getAllItems() {
|
||||
const items = await channels.getAllItems();
|
||||
|
@ -760,7 +756,7 @@ async function getAllItems() {
|
|||
const keys = ITEM_KEYS[key];
|
||||
|
||||
const deserializedValue = Array.isArray(keys)
|
||||
? keysToArrayBuffer(keys, { value }).value
|
||||
? keysToBytes(keys, { value }).value
|
||||
: value;
|
||||
|
||||
result[key] = deserializedValue;
|
||||
|
|
|
@ -65,7 +65,7 @@ export type IdentityKeyType = {
|
|||
firstUse: boolean;
|
||||
id: UUIDStringType | `conversation:${UUIDStringType}`;
|
||||
nonblockingApproval: boolean;
|
||||
publicKey: ArrayBuffer;
|
||||
publicKey: Uint8Array;
|
||||
timestamp: number;
|
||||
verified: number;
|
||||
};
|
||||
|
@ -85,8 +85,8 @@ export type PreKeyType = {
|
|||
id: `${UUIDStringType}:${number}`;
|
||||
keyId: number;
|
||||
ourUuid: UUIDStringType;
|
||||
privateKey: ArrayBuffer;
|
||||
publicKey: ArrayBuffer;
|
||||
privateKey: Uint8Array;
|
||||
publicKey: Uint8Array;
|
||||
};
|
||||
export type PreKeyIdType = PreKeyType['id'];
|
||||
export type SearchResultMessageType = {
|
||||
|
@ -101,7 +101,7 @@ export type ClientSearchResultMessageType = MessageType & {
|
|||
|
||||
export type SentProtoType = {
|
||||
contentHint: number;
|
||||
proto: Buffer;
|
||||
proto: Uint8Array;
|
||||
timestamp: number;
|
||||
};
|
||||
export type SentProtoWithMessageIdsType = SentProtoType & {
|
||||
|
@ -128,7 +128,7 @@ export type SenderKeyType = {
|
|||
senderId: string;
|
||||
distributionId: string;
|
||||
// Raw data to serialize/deserialize into signal-client SenderKeyRecord
|
||||
data: Buffer;
|
||||
data: Uint8Array;
|
||||
lastUpdatedDate: number;
|
||||
};
|
||||
export type SenderKeyIdType = SenderKeyType['id'];
|
||||
|
@ -149,8 +149,8 @@ export type SignedPreKeyType = {
|
|||
ourUuid: UUIDStringType;
|
||||
id: `${UUIDStringType}:${number}`;
|
||||
keyId: number;
|
||||
privateKey: ArrayBuffer;
|
||||
publicKey: ArrayBuffer;
|
||||
privateKey: Uint8Array;
|
||||
publicKey: Uint8Array;
|
||||
};
|
||||
export type SignedPreKeyIdType = SignedPreKeyType['id'];
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ type CleanedDataValue =
|
|||
| boolean
|
||||
| null
|
||||
| undefined
|
||||
| Buffer
|
||||
| Uint8Array
|
||||
| CleanedObject
|
||||
| CleanedArray;
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
|
@ -111,7 +111,7 @@ function cleanDataInner(
|
|||
return undefined;
|
||||
}
|
||||
|
||||
if (data instanceof Buffer) {
|
||||
if (data instanceof Uint8Array) {
|
||||
return data;
|
||||
}
|
||||
|
||||
|
|
|
@ -255,7 +255,7 @@ export enum OneTimeModalState {
|
|||
}
|
||||
|
||||
type ComposerGroupCreationState = {
|
||||
groupAvatar: undefined | ArrayBuffer;
|
||||
groupAvatar: undefined | Uint8Array;
|
||||
groupName: string;
|
||||
groupExpireTimer: number;
|
||||
maximumGroupSizeModalState: OneTimeModalState;
|
||||
|
@ -632,7 +632,7 @@ export type ShowArchivedConversationsActionType = {
|
|||
};
|
||||
type SetComposeGroupAvatarActionType = {
|
||||
type: 'SET_COMPOSE_GROUP_AVATAR';
|
||||
payload: { groupAvatar: undefined | ArrayBuffer };
|
||||
payload: { groupAvatar: undefined | Uint8Array };
|
||||
};
|
||||
type SetComposeGroupNameActionType = {
|
||||
type: 'SET_COMPOSE_GROUP_NAME';
|
||||
|
@ -931,7 +931,7 @@ function saveAvatarToDisk(
|
|||
): ThunkAction<void, RootStateType, unknown, ReplaceAvatarsActionType> {
|
||||
return async (dispatch, getState) => {
|
||||
if (!avatarData.buffer) {
|
||||
throw new Error('No avatar ArrayBuffer provided');
|
||||
throw new Error('No avatar Uint8Array provided');
|
||||
}
|
||||
|
||||
strictAssert(conversationId, 'conversationId not provided');
|
||||
|
@ -966,7 +966,7 @@ function saveAvatarToDisk(
|
|||
|
||||
function myProfileChanged(
|
||||
profileData: ProfileDataType,
|
||||
avatarBuffer?: ArrayBuffer
|
||||
avatarBuffer?: Uint8Array
|
||||
): ThunkAction<
|
||||
void,
|
||||
RootStateType,
|
||||
|
@ -1138,7 +1138,7 @@ function composeSaveAvatarToDisk(
|
|||
): ThunkAction<void, RootStateType, unknown, ComposeSaveAvatarActionType> {
|
||||
return async dispatch => {
|
||||
if (!avatarData.buffer) {
|
||||
throw new Error('No avatar ArrayBuffer provided');
|
||||
throw new Error('No avatar Uint8Array provided');
|
||||
}
|
||||
|
||||
const imagePath = await window.Signal.Migrations.writeNewAvatarData(
|
||||
|
@ -1601,7 +1601,7 @@ function scrollToMessage(
|
|||
}
|
||||
|
||||
function setComposeGroupAvatar(
|
||||
groupAvatar: undefined | ArrayBuffer
|
||||
groupAvatar: undefined | Uint8Array
|
||||
): SetComposeGroupAvatarActionType {
|
||||
return {
|
||||
type: 'SET_COMPOSE_GROUP_AVATAR',
|
||||
|
@ -2884,7 +2884,7 @@ export function reducer(
|
|||
let recommendedGroupSizeModalState: OneTimeModalState;
|
||||
let maximumGroupSizeModalState: OneTimeModalState;
|
||||
let groupName: string;
|
||||
let groupAvatar: undefined | ArrayBuffer;
|
||||
let groupAvatar: undefined | Uint8Array;
|
||||
let groupExpireTimer: number;
|
||||
let userAvatarData = getDefaultAvatars(true);
|
||||
|
||||
|
|
|
@ -541,7 +541,7 @@ const getGroupCreationComposerState = createSelector(
|
|||
composerState
|
||||
): {
|
||||
groupName: string;
|
||||
groupAvatar: undefined | ArrayBuffer;
|
||||
groupAvatar: undefined | Uint8Array;
|
||||
groupExpireTimer: number;
|
||||
selectedConversationIds: Array<string>;
|
||||
} => {
|
||||
|
@ -566,7 +566,7 @@ const getGroupCreationComposerState = createSelector(
|
|||
|
||||
export const getComposeGroupAvatar = createSelector(
|
||||
getGroupCreationComposerState,
|
||||
(composerState): undefined | ArrayBuffer => composerState.groupAvatar
|
||||
(composerState): undefined | Uint8Array => composerState.groupAvatar
|
||||
);
|
||||
|
||||
export const getComposeGroupName = createSelector(
|
||||
|
|
|
@ -36,7 +36,7 @@ export type SmartConversationDetailsProps = {
|
|||
) => void;
|
||||
updateGroupAttributes: (
|
||||
_: Readonly<{
|
||||
avatar?: undefined | ArrayBuffer;
|
||||
avatar?: undefined | Uint8Array;
|
||||
title?: string;
|
||||
}>
|
||||
) => Promise<void>;
|
||||
|
|
|
@ -5,7 +5,6 @@ import { assert } from 'chai';
|
|||
import { Writer } from 'protobufjs';
|
||||
|
||||
import * as Bytes from '../Bytes';
|
||||
import { typedArrayToArrayBuffer } from '../Crypto';
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
import { ContactBuffer, GroupBuffer } from '../textsecure/ContactsParser';
|
||||
|
||||
|
@ -19,7 +18,7 @@ describe('ContactsParser', () => {
|
|||
}
|
||||
|
||||
describe('ContactBuffer', () => {
|
||||
function getTestBuffer(): ArrayBuffer {
|
||||
function getTestBuffer(): Uint8Array {
|
||||
const avatarBuffer = generateAvatar();
|
||||
|
||||
const contactInfoBuffer = Proto.ContactDetails.encode({
|
||||
|
@ -39,12 +38,12 @@ describe('ContactsParser', () => {
|
|||
chunks.push(avatarBuffer);
|
||||
}
|
||||
|
||||
return typedArrayToArrayBuffer(Bytes.concatenate(chunks));
|
||||
return Bytes.concatenate(chunks);
|
||||
}
|
||||
|
||||
it('parses an array buffer of contacts', () => {
|
||||
const arrayBuffer = getTestBuffer();
|
||||
const contactBuffer = new ContactBuffer(arrayBuffer);
|
||||
const bytes = getTestBuffer();
|
||||
const contactBuffer = new ContactBuffer(bytes);
|
||||
let contact = contactBuffer.next();
|
||||
let count = 0;
|
||||
while (contact !== undefined) {
|
||||
|
@ -59,7 +58,7 @@ describe('ContactsParser', () => {
|
|||
assert.strictEqual(contact.avatar?.length, 255);
|
||||
assert.strictEqual(contact.avatar?.data.byteLength, 255);
|
||||
const avatarBytes = new Uint8Array(
|
||||
contact.avatar?.data || new ArrayBuffer(0)
|
||||
contact.avatar?.data || new Uint8Array(0)
|
||||
);
|
||||
for (let j = 0; j < 255; j += 1) {
|
||||
assert.strictEqual(avatarBytes[j], j);
|
||||
|
@ -71,7 +70,7 @@ describe('ContactsParser', () => {
|
|||
});
|
||||
|
||||
describe('GroupBuffer', () => {
|
||||
function getTestBuffer(): ArrayBuffer {
|
||||
function getTestBuffer(): Uint8Array {
|
||||
const avatarBuffer = generateAvatar();
|
||||
|
||||
const groupInfoBuffer = Proto.GroupDetails.encode({
|
||||
|
@ -91,12 +90,12 @@ describe('ContactsParser', () => {
|
|||
chunks.push(avatarBuffer);
|
||||
}
|
||||
|
||||
return typedArrayToArrayBuffer(Bytes.concatenate(chunks));
|
||||
return Bytes.concatenate(chunks);
|
||||
}
|
||||
|
||||
it('parses an array buffer of groups', () => {
|
||||
const arrayBuffer = getTestBuffer();
|
||||
const groupBuffer = new GroupBuffer(arrayBuffer);
|
||||
const bytes = getTestBuffer();
|
||||
const groupBuffer = new GroupBuffer(bytes);
|
||||
let group = groupBuffer.next();
|
||||
let count = 0;
|
||||
while (group !== undefined) {
|
||||
|
@ -113,7 +112,7 @@ describe('ContactsParser', () => {
|
|||
assert.strictEqual(group.avatar?.length, 255);
|
||||
assert.strictEqual(group.avatar?.data.byteLength, 255);
|
||||
const avatarBytes = new Uint8Array(
|
||||
group.avatar?.data || new ArrayBuffer(0)
|
||||
group.avatar?.data || new Uint8Array(0)
|
||||
);
|
||||
for (let j = 0; j < 255; j += 1) {
|
||||
assert.strictEqual(avatarBytes[j], j);
|
||||
|
|
|
@ -6,6 +6,7 @@ import * as sinon from 'sinon';
|
|||
import { noop } from 'lodash';
|
||||
import { sleep } from '../../util/sleep';
|
||||
|
||||
import { constantTimeEqual } from '../../Crypto';
|
||||
import { OurProfileKeyService } from '../../services/ourProfileKey';
|
||||
|
||||
describe('"our profile key" service', () => {
|
||||
|
@ -18,14 +19,17 @@ describe('"our profile key" service', () => {
|
|||
|
||||
describe('get', () => {
|
||||
it("fetches the key from storage if it's there", async () => {
|
||||
const fakeProfileKey = new ArrayBuffer(2);
|
||||
const fakeProfileKey = new Uint8Array(2);
|
||||
const fakeStorage = createFakeStorage();
|
||||
fakeStorage.get.withArgs('profileKey').returns(fakeProfileKey);
|
||||
|
||||
const service = new OurProfileKeyService();
|
||||
service.initialize(fakeStorage);
|
||||
|
||||
assert.strictEqual(await service.get(), fakeProfileKey);
|
||||
const profileKey = await service.get();
|
||||
assert.isTrue(
|
||||
profileKey && constantTimeEqual(profileKey, fakeProfileKey)
|
||||
);
|
||||
});
|
||||
|
||||
it('resolves with undefined if the key is not in storage', async () => {
|
||||
|
@ -39,7 +43,7 @@ describe('"our profile key" service', () => {
|
|||
let onReadyCallback = noop;
|
||||
const fakeStorage = {
|
||||
...createFakeStorage(),
|
||||
get: sinon.stub().returns(new ArrayBuffer(2)),
|
||||
get: sinon.stub().returns(new Uint8Array(2)),
|
||||
onready: sinon.stub().callsFake(callback => {
|
||||
onReadyCallback = callback;
|
||||
}),
|
||||
|
@ -106,7 +110,7 @@ describe('"our profile key" service', () => {
|
|||
|
||||
it("if there are blocking promises, doesn't grab the profile key from storage more than once (in other words, subsequent calls piggyback)", async () => {
|
||||
const fakeStorage = createFakeStorage();
|
||||
fakeStorage.get.returns(new ArrayBuffer(2));
|
||||
fakeStorage.get.returns(new Uint8Array(2));
|
||||
|
||||
const service = new OurProfileKeyService();
|
||||
service.initialize(fakeStorage);
|
||||
|
@ -153,7 +157,7 @@ describe('"our profile key" service', () => {
|
|||
|
||||
describe('set', () => {
|
||||
it('updates the key in storage', async () => {
|
||||
const fakeProfileKey = new ArrayBuffer(2);
|
||||
const fakeProfileKey = new Uint8Array(2);
|
||||
const fakeStorage = createFakeStorage();
|
||||
|
||||
const service = new OurProfileKeyService();
|
||||
|
|
|
@ -62,7 +62,7 @@ describe('cleanDataForIpc', () => {
|
|||
});
|
||||
|
||||
it('keeps Buffers in a field', () => {
|
||||
const buffer = Buffer.from('AABBCC', 'hex');
|
||||
const buffer = new Uint8Array([0xaa, 0xbb, 0xcc]);
|
||||
|
||||
assert.deepEqual(cleanDataForIpc(buffer), {
|
||||
cleaned: buffer,
|
||||
|
@ -85,11 +85,6 @@ describe('cleanDataForIpc', () => {
|
|||
});
|
||||
|
||||
it('converts other iterables to arrays', () => {
|
||||
assert.deepEqual(cleanDataForIpc(new Uint8Array([1, 2, 3])), {
|
||||
cleaned: [1, 2, 3],
|
||||
pathsChanged: ['root'],
|
||||
});
|
||||
|
||||
assert.deepEqual(cleanDataForIpc(new Float32Array([1, 2, 3])), {
|
||||
cleaned: [1, 2, 3],
|
||||
pathsChanged: ['root'],
|
||||
|
|
|
@ -1680,14 +1680,11 @@ describe('both/state/selectors/conversations', () => {
|
|||
...getEmptyState(),
|
||||
composer: {
|
||||
...defaultSetGroupMetadataComposerState,
|
||||
groupAvatar: new Uint8Array([1, 2, 3]).buffer,
|
||||
groupAvatar: new Uint8Array([1, 2, 3]),
|
||||
},
|
||||
},
|
||||
};
|
||||
assert.deepEqual(
|
||||
getComposeGroupAvatar(state),
|
||||
new Uint8Array([1, 2, 3]).buffer
|
||||
);
|
||||
assert.deepEqual(getComposeGroupAvatar(state), new Uint8Array([1, 2, 3]));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
22
ts/test-both/types/SchemaVersion_test.ts
Normal file
22
ts/test-both/types/SchemaVersion_test.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2018-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
import { isValid } from '../../types/SchemaVersion';
|
||||
|
||||
describe('SchemaVersion', () => {
|
||||
describe('isValid', () => {
|
||||
it('should return true for positive integers', () => {
|
||||
assert.isTrue(isValid(0));
|
||||
assert.isTrue(isValid(1));
|
||||
assert.isTrue(isValid(2));
|
||||
});
|
||||
|
||||
it('should return false for any other value', () => {
|
||||
assert.isFalse(isValid(null));
|
||||
assert.isFalse(isValid(-1));
|
||||
assert.isFalse(isValid(''));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,11 +5,11 @@
|
|||
|
||||
import { assert } from 'chai';
|
||||
|
||||
import * as Bytes from '../../Bytes';
|
||||
import {
|
||||
LocalUserDataType,
|
||||
sessionRecordToProtobuf,
|
||||
} from '../../util/sessionTranslation';
|
||||
import { base64ToArrayBuffer } from '../../Crypto';
|
||||
|
||||
const getRecordCopy = (record: any): any => JSON.parse(JSON.stringify(record));
|
||||
|
||||
|
@ -18,7 +18,7 @@ describe('sessionTranslation', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
ourData = {
|
||||
identityKeyPublic: base64ToArrayBuffer(
|
||||
identityKeyPublic: Bytes.fromBase64(
|
||||
'Baioqfzc/5JD6b+GNqapPouf6eHK7xr9ynLJHnvl+444'
|
||||
),
|
||||
registrationId: 3554,
|
||||
|
|
|
@ -3,141 +3,160 @@
|
|||
|
||||
import { assert } from 'chai';
|
||||
|
||||
import * as Bytes from '../Bytes';
|
||||
import * as Curve from '../Curve';
|
||||
import * as Crypto from '../Crypto';
|
||||
import TSCrypto, { PaddedLengths } from '../textsecure/Crypto';
|
||||
import {
|
||||
PaddedLengths,
|
||||
encryptProfileItemWithPadding,
|
||||
decryptProfileName,
|
||||
encryptProfile,
|
||||
decryptProfile,
|
||||
getRandomBytes,
|
||||
constantTimeEqual,
|
||||
generateRegistrationId,
|
||||
deriveSecrets,
|
||||
encryptDeviceName,
|
||||
decryptDeviceName,
|
||||
deriveAccessKey,
|
||||
getAccessKeyVerifier,
|
||||
verifyAccessKey,
|
||||
deriveMasterKeyFromGroupV1,
|
||||
encryptSymmetric,
|
||||
decryptSymmetric,
|
||||
hmacSha256,
|
||||
verifyHmacSha256,
|
||||
uuidToBytes,
|
||||
bytesToUuid,
|
||||
} from '../Crypto';
|
||||
|
||||
describe('Crypto', () => {
|
||||
describe('encrypting and decrypting profile data', () => {
|
||||
const NAME_PADDED_LENGTH = 53;
|
||||
describe('encrypting and decrypting profile names', () => {
|
||||
it('pads, encrypts, decrypts, and unpads a short string', async () => {
|
||||
it('pads, encrypts, decrypts, and unpads a short string', () => {
|
||||
const name = 'Alice';
|
||||
const buffer = Crypto.bytesFromString(name);
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const buffer = Bytes.fromString(name);
|
||||
const key = getRandomBytes(32);
|
||||
|
||||
const encrypted = await TSCrypto.encryptProfileItemWithPadding(
|
||||
const encrypted = encryptProfileItemWithPadding(
|
||||
buffer,
|
||||
key,
|
||||
PaddedLengths.Name
|
||||
);
|
||||
assert.equal(encrypted.byteLength, NAME_PADDED_LENGTH + 16 + 12);
|
||||
|
||||
const { given, family } = await TSCrypto.decryptProfileName(
|
||||
Crypto.arrayBufferToBase64(encrypted),
|
||||
const { given, family } = decryptProfileName(
|
||||
Bytes.toBase64(encrypted),
|
||||
key
|
||||
);
|
||||
assert.strictEqual(family, null);
|
||||
assert.strictEqual(Crypto.stringFromBytes(given), name);
|
||||
assert.strictEqual(Bytes.toString(given), name);
|
||||
});
|
||||
|
||||
it('handles a given name of the max, 53 characters', async () => {
|
||||
it('handles a given name of the max, 53 characters', () => {
|
||||
const name = '01234567890123456789012345678901234567890123456789123';
|
||||
const buffer = Crypto.bytesFromString(name);
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const buffer = Bytes.fromString(name);
|
||||
const key = getRandomBytes(32);
|
||||
|
||||
const encrypted = await TSCrypto.encryptProfileItemWithPadding(
|
||||
const encrypted = encryptProfileItemWithPadding(
|
||||
buffer,
|
||||
key,
|
||||
PaddedLengths.Name
|
||||
);
|
||||
assert.equal(encrypted.byteLength, NAME_PADDED_LENGTH + 16 + 12);
|
||||
const { given, family } = await TSCrypto.decryptProfileName(
|
||||
Crypto.arrayBufferToBase64(encrypted),
|
||||
const { given, family } = decryptProfileName(
|
||||
Bytes.toBase64(encrypted),
|
||||
key
|
||||
);
|
||||
|
||||
assert.strictEqual(Crypto.stringFromBytes(given), name);
|
||||
assert.strictEqual(Bytes.toString(given), name);
|
||||
assert.strictEqual(family, null);
|
||||
});
|
||||
|
||||
it('handles family/given name of the max, 53 characters', async () => {
|
||||
it('handles family/given name of the max, 53 characters', () => {
|
||||
const name =
|
||||
'01234567890123456789\u000001234567890123456789012345678912';
|
||||
const buffer = Crypto.bytesFromString(name);
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const buffer = Bytes.fromString(name);
|
||||
const key = getRandomBytes(32);
|
||||
|
||||
const encrypted = await TSCrypto.encryptProfileItemWithPadding(
|
||||
const encrypted = encryptProfileItemWithPadding(
|
||||
buffer,
|
||||
key,
|
||||
PaddedLengths.Name
|
||||
);
|
||||
assert.equal(encrypted.byteLength, NAME_PADDED_LENGTH + 16 + 12);
|
||||
const { given, family } = await TSCrypto.decryptProfileName(
|
||||
Crypto.arrayBufferToBase64(encrypted),
|
||||
const { given, family } = decryptProfileName(
|
||||
Bytes.toBase64(encrypted),
|
||||
key
|
||||
);
|
||||
assert.strictEqual(Bytes.toString(given), '01234567890123456789');
|
||||
assert.strictEqual(
|
||||
Crypto.stringFromBytes(given),
|
||||
'01234567890123456789'
|
||||
);
|
||||
assert.strictEqual(
|
||||
family && Crypto.stringFromBytes(family),
|
||||
family && Bytes.toString(family),
|
||||
'01234567890123456789012345678912'
|
||||
);
|
||||
});
|
||||
|
||||
it('handles a string with family/given name', async () => {
|
||||
it('handles a string with family/given name', () => {
|
||||
const name = 'Alice\0Jones';
|
||||
const buffer = Crypto.bytesFromString(name);
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const buffer = Bytes.fromString(name);
|
||||
const key = getRandomBytes(32);
|
||||
|
||||
const encrypted = await TSCrypto.encryptProfileItemWithPadding(
|
||||
const encrypted = encryptProfileItemWithPadding(
|
||||
buffer,
|
||||
key,
|
||||
PaddedLengths.Name
|
||||
);
|
||||
assert.equal(encrypted.byteLength, NAME_PADDED_LENGTH + 16 + 12);
|
||||
const { given, family } = await TSCrypto.decryptProfileName(
|
||||
Crypto.arrayBufferToBase64(encrypted),
|
||||
const { given, family } = decryptProfileName(
|
||||
Bytes.toBase64(encrypted),
|
||||
key
|
||||
);
|
||||
assert.strictEqual(Crypto.stringFromBytes(given), 'Alice');
|
||||
assert.strictEqual(family && Crypto.stringFromBytes(family), 'Jones');
|
||||
assert.strictEqual(Bytes.toString(given), 'Alice');
|
||||
assert.strictEqual(family && Bytes.toString(family), 'Jones');
|
||||
});
|
||||
|
||||
it('works for empty string', async () => {
|
||||
const name = Crypto.bytesFromString('');
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
it('works for empty string', () => {
|
||||
const name = Bytes.fromString('');
|
||||
const key = getRandomBytes(32);
|
||||
|
||||
const encrypted = await TSCrypto.encryptProfileItemWithPadding(
|
||||
const encrypted = encryptProfileItemWithPadding(
|
||||
name,
|
||||
key,
|
||||
PaddedLengths.Name
|
||||
);
|
||||
assert.equal(encrypted.byteLength, NAME_PADDED_LENGTH + 16 + 12);
|
||||
|
||||
const { given, family } = await TSCrypto.decryptProfileName(
|
||||
Crypto.arrayBufferToBase64(encrypted),
|
||||
const { given, family } = decryptProfileName(
|
||||
Bytes.toBase64(encrypted),
|
||||
key
|
||||
);
|
||||
assert.strictEqual(family, null);
|
||||
assert.strictEqual(given.byteLength, 0);
|
||||
assert.strictEqual(Crypto.stringFromBytes(given), '');
|
||||
assert.strictEqual(Bytes.toString(given), '');
|
||||
});
|
||||
});
|
||||
|
||||
describe('encrypting and decrypting profile avatars', () => {
|
||||
it('encrypts and decrypts', async () => {
|
||||
const buffer = Crypto.bytesFromString('This is an avatar');
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const buffer = Bytes.fromString('This is an avatar');
|
||||
const key = getRandomBytes(32);
|
||||
|
||||
const encrypted = await TSCrypto.encryptProfile(buffer, key);
|
||||
const encrypted = encryptProfile(buffer, key);
|
||||
assert(encrypted.byteLength === buffer.byteLength + 16 + 12);
|
||||
|
||||
const decrypted = await TSCrypto.decryptProfile(encrypted, key);
|
||||
assert(Crypto.constantTimeEqual(buffer, decrypted));
|
||||
const decrypted = decryptProfile(encrypted, key);
|
||||
assert(constantTimeEqual(buffer, decrypted));
|
||||
});
|
||||
|
||||
it('throws when decrypting with the wrong key', async () => {
|
||||
const buffer = Crypto.bytesFromString('This is an avatar');
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const badKey = Crypto.getRandomBytes(32);
|
||||
it('throws when decrypting with the wrong key', () => {
|
||||
const buffer = Bytes.fromString('This is an avatar');
|
||||
const key = getRandomBytes(32);
|
||||
const badKey = getRandomBytes(32);
|
||||
|
||||
const encrypted = await TSCrypto.encryptProfile(buffer, key);
|
||||
const encrypted = encryptProfile(buffer, key);
|
||||
assert(encrypted.byteLength === buffer.byteLength + 16 + 12);
|
||||
await assert.isRejected(
|
||||
TSCrypto.decryptProfile(encrypted, badKey),
|
||||
assert.throws(
|
||||
() => decryptProfile(encrypted, badKey),
|
||||
'Failed to decrypt profile data. Most likely the profile key has changed.'
|
||||
);
|
||||
});
|
||||
|
@ -147,7 +166,7 @@ describe('Crypto', () => {
|
|||
describe('generateRegistrationId', () => {
|
||||
it('generates an integer between 0 and 16383 (inclusive)', () => {
|
||||
for (let i = 0; i < 100; i += 1) {
|
||||
const id = Crypto.generateRegistrationId();
|
||||
const id = generateRegistrationId();
|
||||
assert.isAtLeast(id, 0);
|
||||
assert.isAtMost(id, 16383);
|
||||
assert(Number.isInteger(id));
|
||||
|
@ -157,27 +176,27 @@ describe('Crypto', () => {
|
|||
|
||||
describe('deriveSecrets', () => {
|
||||
it('derives key parts via HKDF', () => {
|
||||
const input = Crypto.getRandomBytes(32);
|
||||
const salt = Crypto.getRandomBytes(32);
|
||||
const info = Crypto.bytesFromString('Hello world');
|
||||
const result = Crypto.deriveSecrets(input, salt, info);
|
||||
const input = getRandomBytes(32);
|
||||
const salt = getRandomBytes(32);
|
||||
const info = Bytes.fromString('Hello world');
|
||||
const result = deriveSecrets(input, salt, info);
|
||||
assert.lengthOf(result, 3);
|
||||
result.forEach(part => {
|
||||
// This is a smoke test; HKDF is tested as part of @signalapp/signal-client.
|
||||
assert.instanceOf(part, ArrayBuffer);
|
||||
assert.instanceOf(part, Uint8Array);
|
||||
assert.strictEqual(part.byteLength, 32);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('accessKey/profileKey', () => {
|
||||
it('verification roundtrips', async () => {
|
||||
const profileKey = await Crypto.getRandomBytes(32);
|
||||
const accessKey = await Crypto.deriveAccessKey(profileKey);
|
||||
it('verification roundtrips', () => {
|
||||
const profileKey = getRandomBytes(32);
|
||||
const accessKey = deriveAccessKey(profileKey);
|
||||
|
||||
const verifier = await Crypto.getAccessKeyVerifier(accessKey);
|
||||
const verifier = getAccessKeyVerifier(accessKey);
|
||||
|
||||
const correct = await Crypto.verifyAccessKey(accessKey, verifier);
|
||||
const correct = verifyAccessKey(accessKey, verifier);
|
||||
|
||||
assert.strictEqual(correct, true);
|
||||
});
|
||||
|
@ -208,12 +227,12 @@ describe('Crypto', () => {
|
|||
];
|
||||
|
||||
vectors.forEach((vector, index) => {
|
||||
it(`vector ${index}`, async () => {
|
||||
const gv1 = Crypto.hexToArrayBuffer(vector.gv1);
|
||||
it(`vector ${index}`, () => {
|
||||
const gv1 = Bytes.fromHex(vector.gv1);
|
||||
const expectedHex = vector.masterKey;
|
||||
|
||||
const actual = await Crypto.deriveMasterKeyFromGroupV1(gv1);
|
||||
const actualHex = Crypto.arrayBufferToHex(actual);
|
||||
const actual = deriveMasterKeyFromGroupV1(gv1);
|
||||
const actualHex = Bytes.toHex(actual);
|
||||
|
||||
assert.strictEqual(actualHex, expectedHex);
|
||||
});
|
||||
|
@ -221,34 +240,30 @@ describe('Crypto', () => {
|
|||
});
|
||||
|
||||
describe('symmetric encryption', () => {
|
||||
it('roundtrips', async () => {
|
||||
it('roundtrips', () => {
|
||||
const message = 'this is my message';
|
||||
const plaintext = Crypto.bytesFromString(message);
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const plaintext = Bytes.fromString(message);
|
||||
const key = getRandomBytes(32);
|
||||
|
||||
const encrypted = await Crypto.encryptSymmetric(key, plaintext);
|
||||
const decrypted = await Crypto.decryptSymmetric(key, encrypted);
|
||||
const encrypted = encryptSymmetric(key, plaintext);
|
||||
const decrypted = decryptSymmetric(key, encrypted);
|
||||
|
||||
const equal = Crypto.constantTimeEqual(plaintext, decrypted);
|
||||
const equal = constantTimeEqual(plaintext, decrypted);
|
||||
if (!equal) {
|
||||
throw new Error('The output and input did not match!');
|
||||
}
|
||||
});
|
||||
|
||||
it('roundtrip fails if nonce is modified', async () => {
|
||||
it('roundtrip fails if nonce is modified', () => {
|
||||
const message = 'this is my message';
|
||||
const plaintext = Crypto.bytesFromString(message);
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const plaintext = Bytes.fromString(message);
|
||||
const key = getRandomBytes(32);
|
||||
|
||||
const encrypted = await Crypto.encryptSymmetric(key, plaintext);
|
||||
const uintArray = new Uint8Array(encrypted);
|
||||
uintArray[2] += 2;
|
||||
const encrypted = encryptSymmetric(key, plaintext);
|
||||
encrypted[2] += 2;
|
||||
|
||||
try {
|
||||
await Crypto.decryptSymmetric(
|
||||
key,
|
||||
Crypto.typedArrayToArrayBuffer(uintArray)
|
||||
);
|
||||
decryptSymmetric(key, encrypted);
|
||||
} catch (error) {
|
||||
assert.strictEqual(
|
||||
error.message,
|
||||
|
@ -260,20 +275,16 @@ describe('Crypto', () => {
|
|||
throw new Error('Expected error to be thrown');
|
||||
});
|
||||
|
||||
it('roundtrip fails if mac is modified', async () => {
|
||||
it('roundtrip fails if mac is modified', () => {
|
||||
const message = 'this is my message';
|
||||
const plaintext = Crypto.bytesFromString(message);
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const plaintext = Bytes.fromString(message);
|
||||
const key = getRandomBytes(32);
|
||||
|
||||
const encrypted = await Crypto.encryptSymmetric(key, plaintext);
|
||||
const uintArray = new Uint8Array(encrypted);
|
||||
uintArray[uintArray.length - 3] += 2;
|
||||
const encrypted = encryptSymmetric(key, plaintext);
|
||||
encrypted[encrypted.length - 3] += 2;
|
||||
|
||||
try {
|
||||
await Crypto.decryptSymmetric(
|
||||
key,
|
||||
Crypto.typedArrayToArrayBuffer(uintArray)
|
||||
);
|
||||
decryptSymmetric(key, encrypted);
|
||||
} catch (error) {
|
||||
assert.strictEqual(
|
||||
error.message,
|
||||
|
@ -285,20 +296,16 @@ describe('Crypto', () => {
|
|||
throw new Error('Expected error to be thrown');
|
||||
});
|
||||
|
||||
it('roundtrip fails if encrypted contents are modified', async () => {
|
||||
it('roundtrip fails if encrypted contents are modified', () => {
|
||||
const message = 'this is my message';
|
||||
const plaintext = Crypto.bytesFromString(message);
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const plaintext = Bytes.fromString(message);
|
||||
const key = getRandomBytes(32);
|
||||
|
||||
const encrypted = await Crypto.encryptSymmetric(key, plaintext);
|
||||
const uintArray = new Uint8Array(encrypted);
|
||||
uintArray[35] += 9;
|
||||
const encrypted = encryptSymmetric(key, plaintext);
|
||||
encrypted[35] += 9;
|
||||
|
||||
try {
|
||||
await Crypto.decryptSymmetric(
|
||||
key,
|
||||
Crypto.typedArrayToArrayBuffer(uintArray)
|
||||
);
|
||||
decryptSymmetric(key, encrypted);
|
||||
} catch (error) {
|
||||
assert.strictEqual(
|
||||
error.message,
|
||||
|
@ -312,33 +319,24 @@ describe('Crypto', () => {
|
|||
});
|
||||
|
||||
describe('encrypted device name', () => {
|
||||
it('roundtrips', async () => {
|
||||
it('roundtrips', () => {
|
||||
const deviceName = 'v1.19.0 on Windows 10';
|
||||
const identityKey = Curve.generateKeyPair();
|
||||
|
||||
const encrypted = await Crypto.encryptDeviceName(
|
||||
deviceName,
|
||||
identityKey.pubKey
|
||||
);
|
||||
const decrypted = await Crypto.decryptDeviceName(
|
||||
encrypted,
|
||||
identityKey.privKey
|
||||
);
|
||||
const encrypted = encryptDeviceName(deviceName, identityKey.pubKey);
|
||||
const decrypted = decryptDeviceName(encrypted, identityKey.privKey);
|
||||
|
||||
assert.strictEqual(decrypted, deviceName);
|
||||
});
|
||||
|
||||
it('fails if iv is changed', async () => {
|
||||
it('fails if iv is changed', () => {
|
||||
const deviceName = 'v1.19.0 on Windows 10';
|
||||
const identityKey = Curve.generateKeyPair();
|
||||
|
||||
const encrypted = await Crypto.encryptDeviceName(
|
||||
deviceName,
|
||||
identityKey.pubKey
|
||||
);
|
||||
encrypted.syntheticIv = Crypto.getRandomBytes(16);
|
||||
const encrypted = encryptDeviceName(deviceName, identityKey.pubKey);
|
||||
encrypted.syntheticIv = getRandomBytes(16);
|
||||
try {
|
||||
await Crypto.decryptDeviceName(encrypted, identityKey.privKey);
|
||||
decryptDeviceName(encrypted, identityKey.privKey);
|
||||
} catch (error) {
|
||||
assert.strictEqual(
|
||||
error.message,
|
||||
|
@ -348,46 +346,15 @@ describe('Crypto', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('attachment encryption', () => {
|
||||
it('roundtrips', async () => {
|
||||
const staticKeyPair = Curve.generateKeyPair();
|
||||
const message = 'this is my message';
|
||||
const plaintext = Crypto.bytesFromString(message);
|
||||
const path =
|
||||
'fa/facdf99c22945b1c9393345599a276f4b36ad7ccdc8c2467f5441b742c2d11fa';
|
||||
|
||||
const encrypted = await Crypto.encryptAttachment(
|
||||
staticKeyPair.pubKey.slice(1),
|
||||
path,
|
||||
plaintext
|
||||
);
|
||||
const decrypted = await Crypto.decryptAttachment(
|
||||
staticKeyPair.privKey,
|
||||
path,
|
||||
encrypted
|
||||
);
|
||||
|
||||
const equal = Crypto.constantTimeEqual(plaintext, decrypted);
|
||||
if (!equal) {
|
||||
throw new Error('The output and input did not match!');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifyHmacSha256', () => {
|
||||
it('rejects if their MAC is too short', async () => {
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const plaintext = Crypto.bytesFromString('Hello world');
|
||||
const ourMac = await Crypto.hmacSha256(key, plaintext);
|
||||
it('rejects if their MAC is too short', () => {
|
||||
const key = getRandomBytes(32);
|
||||
const plaintext = Bytes.fromString('Hello world');
|
||||
const ourMac = hmacSha256(key, plaintext);
|
||||
const theirMac = ourMac.slice(0, -1);
|
||||
let error;
|
||||
try {
|
||||
await Crypto.verifyHmacSha256(
|
||||
plaintext,
|
||||
key,
|
||||
theirMac,
|
||||
ourMac.byteLength
|
||||
);
|
||||
verifyHmacSha256(plaintext, key, theirMac, ourMac.byteLength);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
@ -395,19 +362,14 @@ describe('Crypto', () => {
|
|||
assert.strictEqual(error.message, 'Bad MAC length');
|
||||
});
|
||||
|
||||
it('rejects if their MAC is too long', async () => {
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const plaintext = Crypto.bytesFromString('Hello world');
|
||||
const ourMac = await Crypto.hmacSha256(key, plaintext);
|
||||
const theirMac = Crypto.concatenateBytes(ourMac, new Uint8Array([0xff]));
|
||||
it('rejects if their MAC is too long', () => {
|
||||
const key = getRandomBytes(32);
|
||||
const plaintext = Bytes.fromString('Hello world');
|
||||
const ourMac = hmacSha256(key, plaintext);
|
||||
const theirMac = Bytes.concatenate([ourMac, new Uint8Array([0xff])]);
|
||||
let error;
|
||||
try {
|
||||
await Crypto.verifyHmacSha256(
|
||||
plaintext,
|
||||
key,
|
||||
theirMac,
|
||||
ourMac.byteLength
|
||||
);
|
||||
verifyHmacSha256(plaintext, key, theirMac, ourMac.byteLength);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
@ -415,19 +377,14 @@ describe('Crypto', () => {
|
|||
assert.strictEqual(error.message, 'Bad MAC length');
|
||||
});
|
||||
|
||||
it('rejects if our MAC is shorter than the specified length', async () => {
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const plaintext = Crypto.bytesFromString('Hello world');
|
||||
const ourMac = await Crypto.hmacSha256(key, plaintext);
|
||||
it('rejects if our MAC is shorter than the specified length', () => {
|
||||
const key = getRandomBytes(32);
|
||||
const plaintext = Bytes.fromString('Hello world');
|
||||
const ourMac = hmacSha256(key, plaintext);
|
||||
const theirMac = ourMac;
|
||||
let error;
|
||||
try {
|
||||
await Crypto.verifyHmacSha256(
|
||||
plaintext,
|
||||
key,
|
||||
theirMac,
|
||||
ourMac.byteLength + 1
|
||||
);
|
||||
verifyHmacSha256(plaintext, key, theirMac, ourMac.byteLength + 1);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
@ -435,20 +392,15 @@ describe('Crypto', () => {
|
|||
assert.strictEqual(error.message, 'Bad MAC length');
|
||||
});
|
||||
|
||||
it("rejects if the MACs don't match", async () => {
|
||||
const plaintext = Crypto.bytesFromString('Hello world');
|
||||
const ourKey = Crypto.getRandomBytes(32);
|
||||
const ourMac = await Crypto.hmacSha256(ourKey, plaintext);
|
||||
const theirKey = Crypto.getRandomBytes(32);
|
||||
const theirMac = await Crypto.hmacSha256(theirKey, plaintext);
|
||||
it("rejects if the MACs don't match", () => {
|
||||
const plaintext = Bytes.fromString('Hello world');
|
||||
const ourKey = getRandomBytes(32);
|
||||
const ourMac = hmacSha256(ourKey, plaintext);
|
||||
const theirKey = getRandomBytes(32);
|
||||
const theirMac = hmacSha256(theirKey, plaintext);
|
||||
let error;
|
||||
try {
|
||||
await Crypto.verifyHmacSha256(
|
||||
plaintext,
|
||||
ourKey,
|
||||
theirMac,
|
||||
ourMac.byteLength
|
||||
);
|
||||
verifyHmacSha256(plaintext, ourKey, theirMac, ourMac.byteLength);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
@ -456,11 +408,11 @@ describe('Crypto', () => {
|
|||
assert.strictEqual(error.message, 'Bad MAC');
|
||||
});
|
||||
|
||||
it('resolves with undefined if the MACs match exactly', async () => {
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const plaintext = Crypto.bytesFromString('Hello world');
|
||||
const theirMac = await Crypto.hmacSha256(key, plaintext);
|
||||
const result = await Crypto.verifyHmacSha256(
|
||||
it('resolves with undefined if the MACs match exactly', () => {
|
||||
const key = getRandomBytes(32);
|
||||
const plaintext = Bytes.fromString('Hello world');
|
||||
const theirMac = hmacSha256(key, plaintext);
|
||||
const result = verifyHmacSha256(
|
||||
plaintext,
|
||||
key,
|
||||
theirMac,
|
||||
|
@ -469,11 +421,11 @@ describe('Crypto', () => {
|
|||
assert.isUndefined(result);
|
||||
});
|
||||
|
||||
it('resolves with undefined if the first `length` bytes of the MACs match', async () => {
|
||||
const key = Crypto.getRandomBytes(32);
|
||||
const plaintext = Crypto.bytesFromString('Hello world');
|
||||
const theirMac = (await Crypto.hmacSha256(key, plaintext)).slice(0, -5);
|
||||
const result = await Crypto.verifyHmacSha256(
|
||||
it('resolves with undefined if the first `length` bytes of the MACs match', () => {
|
||||
const key = getRandomBytes(32);
|
||||
const plaintext = Bytes.fromString('Hello world');
|
||||
const theirMac = hmacSha256(key, plaintext).slice(0, -5);
|
||||
const result = verifyHmacSha256(
|
||||
plaintext,
|
||||
key,
|
||||
theirMac,
|
||||
|
@ -483,102 +435,86 @@ describe('Crypto', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('uuidToArrayBuffer', () => {
|
||||
const { uuidToArrayBuffer } = Crypto;
|
||||
|
||||
it('converts valid UUIDs to ArrayBuffers', () => {
|
||||
const expectedResult = Crypto.typedArrayToArrayBuffer(
|
||||
new Uint8Array([
|
||||
0x22,
|
||||
0x6e,
|
||||
0x44,
|
||||
0x02,
|
||||
0x7f,
|
||||
0xfc,
|
||||
0x45,
|
||||
0x43,
|
||||
0x85,
|
||||
0xc9,
|
||||
0x46,
|
||||
0x22,
|
||||
0xc5,
|
||||
0x0a,
|
||||
0x5b,
|
||||
0x14,
|
||||
])
|
||||
);
|
||||
describe('uuidToBytes', () => {
|
||||
it('converts valid UUIDs to Uint8Arrays', () => {
|
||||
const expectedResult = new Uint8Array([
|
||||
0x22,
|
||||
0x6e,
|
||||
0x44,
|
||||
0x02,
|
||||
0x7f,
|
||||
0xfc,
|
||||
0x45,
|
||||
0x43,
|
||||
0x85,
|
||||
0xc9,
|
||||
0x46,
|
||||
0x22,
|
||||
0xc5,
|
||||
0x0a,
|
||||
0x5b,
|
||||
0x14,
|
||||
]);
|
||||
|
||||
assert.deepEqual(
|
||||
uuidToArrayBuffer('226e4402-7ffc-4543-85c9-4622c50a5b14'),
|
||||
uuidToBytes('226e4402-7ffc-4543-85c9-4622c50a5b14'),
|
||||
expectedResult
|
||||
);
|
||||
assert.deepEqual(
|
||||
uuidToArrayBuffer('226E4402-7FFC-4543-85C9-4622C50A5B14'),
|
||||
uuidToBytes('226E4402-7FFC-4543-85C9-4622C50A5B14'),
|
||||
expectedResult
|
||||
);
|
||||
});
|
||||
|
||||
it('returns an empty ArrayBuffer for strings of the wrong length', () => {
|
||||
assert.deepEqual(uuidToArrayBuffer(''), new ArrayBuffer(0));
|
||||
assert.deepEqual(uuidToArrayBuffer('abc'), new ArrayBuffer(0));
|
||||
it('returns an empty Uint8Array for strings of the wrong length', () => {
|
||||
assert.deepEqual(uuidToBytes(''), new Uint8Array(0));
|
||||
assert.deepEqual(uuidToBytes('abc'), new Uint8Array(0));
|
||||
assert.deepEqual(
|
||||
uuidToArrayBuffer('032deadf0d5e4ee78da28e75b1dfb284'),
|
||||
new ArrayBuffer(0)
|
||||
uuidToBytes('032deadf0d5e4ee78da28e75b1dfb284'),
|
||||
new Uint8Array(0)
|
||||
);
|
||||
assert.deepEqual(
|
||||
uuidToArrayBuffer('deaed5eb-d983-456a-a954-9ad7a006b271aaaaaaaaaa'),
|
||||
new ArrayBuffer(0)
|
||||
uuidToBytes('deaed5eb-d983-456a-a954-9ad7a006b271aaaaaaaaaa'),
|
||||
new Uint8Array(0)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('arrayBufferToUuid', () => {
|
||||
const { arrayBufferToUuid } = Crypto;
|
||||
|
||||
it('converts valid ArrayBuffers to UUID strings', () => {
|
||||
const buf = Crypto.typedArrayToArrayBuffer(
|
||||
new Uint8Array([
|
||||
0x22,
|
||||
0x6e,
|
||||
0x44,
|
||||
0x02,
|
||||
0x7f,
|
||||
0xfc,
|
||||
0x45,
|
||||
0x43,
|
||||
0x85,
|
||||
0xc9,
|
||||
0x46,
|
||||
0x22,
|
||||
0xc5,
|
||||
0x0a,
|
||||
0x5b,
|
||||
0x14,
|
||||
])
|
||||
);
|
||||
describe('bytesToUuid', () => {
|
||||
it('converts valid Uint8Arrays to UUID strings', () => {
|
||||
const buf = new Uint8Array([
|
||||
0x22,
|
||||
0x6e,
|
||||
0x44,
|
||||
0x02,
|
||||
0x7f,
|
||||
0xfc,
|
||||
0x45,
|
||||
0x43,
|
||||
0x85,
|
||||
0xc9,
|
||||
0x46,
|
||||
0x22,
|
||||
0xc5,
|
||||
0x0a,
|
||||
0x5b,
|
||||
0x14,
|
||||
]);
|
||||
|
||||
assert.deepEqual(
|
||||
arrayBufferToUuid(buf),
|
||||
bytesToUuid(buf),
|
||||
'226e4402-7ffc-4543-85c9-4622c50a5b14'
|
||||
);
|
||||
});
|
||||
|
||||
it('returns undefined if passed an all-zero buffer', () => {
|
||||
assert.isUndefined(arrayBufferToUuid(new ArrayBuffer(16)));
|
||||
assert.isUndefined(bytesToUuid(new Uint8Array(16)));
|
||||
});
|
||||
|
||||
it('returns undefined if passed the wrong number of bytes', () => {
|
||||
assert.isUndefined(arrayBufferToUuid(new ArrayBuffer(0)));
|
||||
assert.isUndefined(
|
||||
arrayBufferToUuid(
|
||||
Crypto.typedArrayToArrayBuffer(new Uint8Array([0x22]))
|
||||
)
|
||||
);
|
||||
assert.isUndefined(
|
||||
arrayBufferToUuid(
|
||||
Crypto.typedArrayToArrayBuffer(new Uint8Array(Array(17).fill(0x22)))
|
||||
)
|
||||
);
|
||||
assert.isUndefined(bytesToUuid(new Uint8Array(0)));
|
||||
assert.isUndefined(bytesToUuid(new Uint8Array([0x22])));
|
||||
assert.isUndefined(bytesToUuid(new Uint8Array(Array(17).fill(0x22))));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,18 +3,12 @@
|
|||
|
||||
import { assert } from 'chai';
|
||||
|
||||
import {
|
||||
arrayBufferToHex,
|
||||
constantTimeEqual,
|
||||
getRandomBytes,
|
||||
hexToArrayBuffer,
|
||||
typedArrayToArrayBuffer,
|
||||
} from '../Crypto';
|
||||
import * as Bytes from '../Bytes';
|
||||
import { constantTimeEqual } from '../Crypto';
|
||||
import {
|
||||
calculateSignature,
|
||||
clampPrivateKey,
|
||||
createKeyPair,
|
||||
copyArrayBuffer,
|
||||
calculateAgreement,
|
||||
generateKeyPair,
|
||||
generatePreKey,
|
||||
|
@ -25,7 +19,7 @@ import {
|
|||
|
||||
describe('Curve', () => {
|
||||
it('verifySignature roundtrip', () => {
|
||||
const message = typedArrayToArrayBuffer(Buffer.from('message'));
|
||||
const message = Buffer.from('message');
|
||||
const { pubKey, privKey } = generateKeyPair();
|
||||
const signature = calculateSignature(privKey, message);
|
||||
const verified = verifySignature(pubKey, message, signature);
|
||||
|
@ -87,42 +81,12 @@ describe('Curve', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('#copyArrayBuffer', () => {
|
||||
it('copy matches original', () => {
|
||||
const data = getRandomBytes(200);
|
||||
const dataHex = arrayBufferToHex(data);
|
||||
const copy = copyArrayBuffer(data);
|
||||
|
||||
assert.equal(data.byteLength, copy.byteLength);
|
||||
assert.isTrue(constantTimeEqual(data, copy));
|
||||
|
||||
assert.equal(dataHex, arrayBufferToHex(data));
|
||||
assert.equal(dataHex, arrayBufferToHex(copy));
|
||||
});
|
||||
|
||||
it('copies into new memory location', () => {
|
||||
const data = getRandomBytes(200);
|
||||
const dataHex = arrayBufferToHex(data);
|
||||
const copy = copyArrayBuffer(data);
|
||||
|
||||
const view = new Uint8Array(copy);
|
||||
view[0] += 1;
|
||||
view[1] -= 1;
|
||||
|
||||
assert.equal(data.byteLength, copy.byteLength);
|
||||
assert.isFalse(constantTimeEqual(data, copy));
|
||||
|
||||
assert.equal(dataHex, arrayBufferToHex(data));
|
||||
assert.notEqual(dataHex, arrayBufferToHex(copy));
|
||||
});
|
||||
});
|
||||
|
||||
describe('#createKeyPair', () => {
|
||||
it('does not modify unclamped private key', () => {
|
||||
const initialHex =
|
||||
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
|
||||
const privateKey = hexToArrayBuffer(initialHex);
|
||||
const copyOfPrivateKey = copyArrayBuffer(privateKey);
|
||||
const privateKey = Bytes.fromHex(initialHex);
|
||||
const copyOfPrivateKey = new Uint8Array(privateKey);
|
||||
|
||||
assert.isTrue(
|
||||
constantTimeEqual(privateKey, copyOfPrivateKey),
|
||||
|
@ -143,7 +107,7 @@ describe('Curve', () => {
|
|||
// But the keypair that comes out has indeed been updated
|
||||
assert.notEqual(
|
||||
initialHex,
|
||||
arrayBufferToHex(keyPair.privKey),
|
||||
Bytes.toHex(keyPair.privKey),
|
||||
'keypair check'
|
||||
);
|
||||
assert.isFalse(
|
||||
|
@ -155,10 +119,10 @@ describe('Curve', () => {
|
|||
it('does not modify clamped private key', () => {
|
||||
const initialHex =
|
||||
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
|
||||
const privateKey = hexToArrayBuffer(initialHex);
|
||||
const privateKey = Bytes.fromHex(initialHex);
|
||||
clampPrivateKey(privateKey);
|
||||
const postClampHex = arrayBufferToHex(privateKey);
|
||||
const copyOfPrivateKey = copyArrayBuffer(privateKey);
|
||||
const postClampHex = Bytes.toHex(privateKey);
|
||||
const copyOfPrivateKey = new Uint8Array(privateKey);
|
||||
|
||||
assert.notEqual(postClampHex, initialHex, 'initial clamp check');
|
||||
assert.isTrue(
|
||||
|
@ -178,11 +142,7 @@ describe('Curve', () => {
|
|||
);
|
||||
|
||||
// The keypair that comes out hasn't been updated either
|
||||
assert.equal(
|
||||
postClampHex,
|
||||
arrayBufferToHex(keyPair.privKey),
|
||||
'keypair check'
|
||||
);
|
||||
assert.equal(postClampHex, Bytes.toHex(keyPair.privKey), 'keypair check');
|
||||
assert.isTrue(
|
||||
constantTimeEqual(privateKey, keyPair.privKey),
|
||||
'keypair vs incoming value'
|
||||
|
|
|
@ -15,9 +15,6 @@ import { DecryptionErrorEvent } from '../textsecure/messageReceiverEvents';
|
|||
import { SignalService as Proto } from '../protobuf';
|
||||
import * as Crypto from '../Crypto';
|
||||
|
||||
// TODO: remove once we move away from ArrayBuffers
|
||||
const FIXMEU8 = Uint8Array;
|
||||
|
||||
describe('MessageReceiver', () => {
|
||||
const number = '+19999999999';
|
||||
const uuid = 'aaaaaaaa-bbbb-4ccc-9ddd-eeeeeeeeeeee';
|
||||
|
@ -54,7 +51,7 @@ describe('MessageReceiver', () => {
|
|||
sourceUuid: uuid,
|
||||
sourceDevice: deviceId,
|
||||
timestamp: Date.now(),
|
||||
content: new FIXMEU8(Crypto.getRandomBytes(200)),
|
||||
content: Crypto.getRandomBytes(200),
|
||||
}).finish();
|
||||
|
||||
messageReceiver.handleRequest(
|
||||
|
|
|
@ -13,15 +13,11 @@ import {
|
|||
import { v4 as getGuid } from 'uuid';
|
||||
|
||||
import { signal } from '../protobuf/compiled';
|
||||
import { sessionStructureToArrayBuffer } from '../util/sessionTranslation';
|
||||
import { sessionStructureToBytes } from '../util/sessionTranslation';
|
||||
import { Zone } from '../util/Zone';
|
||||
|
||||
import {
|
||||
getRandomBytes,
|
||||
constantTimeEqual,
|
||||
typedArrayToArrayBuffer as toArrayBuffer,
|
||||
arrayBufferToBase64 as toBase64,
|
||||
} from '../Crypto';
|
||||
import * as Bytes from '../Bytes';
|
||||
import { getRandomBytes, constantTimeEqual } from '../Crypto';
|
||||
import { clampPrivateKey, setPublicKeyTypeByte } from '../Curve';
|
||||
import { GLOBAL_ZONE, SignalProtocolStore } from '../SignalProtocolStore';
|
||||
import { Address } from '../types/Address';
|
||||
|
@ -53,20 +49,20 @@ describe('SignalProtocolStore', () => {
|
|||
if (isOpen) {
|
||||
proto.currentSession = new SessionStructure();
|
||||
|
||||
proto.currentSession.aliceBaseKey = toUint8Array(getPublicKey());
|
||||
proto.currentSession.localIdentityPublic = toUint8Array(getPublicKey());
|
||||
proto.currentSession.aliceBaseKey = getPublicKey();
|
||||
proto.currentSession.localIdentityPublic = getPublicKey();
|
||||
proto.currentSession.localRegistrationId = 435;
|
||||
|
||||
proto.currentSession.previousCounter = 1;
|
||||
proto.currentSession.remoteIdentityPublic = toUint8Array(getPublicKey());
|
||||
proto.currentSession.remoteIdentityPublic = getPublicKey();
|
||||
proto.currentSession.remoteRegistrationId = 243;
|
||||
|
||||
proto.currentSession.rootKey = toUint8Array(getPrivateKey());
|
||||
proto.currentSession.rootKey = getPrivateKey();
|
||||
proto.currentSession.sessionVersion = 3;
|
||||
}
|
||||
|
||||
return SessionRecord.deserialize(
|
||||
Buffer.from(sessionStructureToArrayBuffer(proto))
|
||||
Buffer.from(sessionStructureToBytes(proto))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -80,19 +76,19 @@ describe('SignalProtocolStore', () => {
|
|||
const senderChainKey = new SenderKeyStateStructure.SenderChainKey();
|
||||
|
||||
senderChainKey.iteration = 10;
|
||||
senderChainKey.seed = toUint8Array(getPublicKey());
|
||||
senderChainKey.seed = getPublicKey();
|
||||
state.senderChainKey = senderChainKey;
|
||||
|
||||
const senderSigningKey = new SenderKeyStateStructure.SenderSigningKey();
|
||||
senderSigningKey.public = toUint8Array(getPublicKey());
|
||||
senderSigningKey.private = toUint8Array(getPrivateKey());
|
||||
senderSigningKey.public = getPublicKey();
|
||||
senderSigningKey.private = getPrivateKey();
|
||||
|
||||
state.senderSigningKey = senderSigningKey;
|
||||
|
||||
state.senderMessageKeys = [];
|
||||
const messageKey = new SenderKeyStateStructure.SenderMessageKey();
|
||||
messageKey.iteration = 234;
|
||||
messageKey.seed = toUint8Array(getPublicKey());
|
||||
messageKey.seed = getPublicKey();
|
||||
state.senderMessageKeys.push(messageKey);
|
||||
|
||||
proto.senderKeyStates = [];
|
||||
|
@ -105,10 +101,6 @@ describe('SignalProtocolStore', () => {
|
|||
);
|
||||
}
|
||||
|
||||
function toUint8Array(buffer: ArrayBuffer): Uint8Array {
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
|
||||
function getPrivateKey() {
|
||||
const key = getRandomBytes(32);
|
||||
clampPrivateKey(key);
|
||||
|
@ -141,8 +133,8 @@ describe('SignalProtocolStore', () => {
|
|||
window.storage.put('registrationIdMap', { [ourUuid.toString()]: 1337 });
|
||||
window.storage.put('identityKeyMap', {
|
||||
[ourUuid.toString()]: {
|
||||
privKey: toBase64(identityKey.privKey),
|
||||
pubKey: toBase64(identityKey.pubKey),
|
||||
privKey: Bytes.toBase64(identityKey.privKey),
|
||||
pubKey: Bytes.toBase64(identityKey.pubKey),
|
||||
},
|
||||
});
|
||||
await window.storage.fetch();
|
||||
|
@ -194,10 +186,7 @@ describe('SignalProtocolStore', () => {
|
|||
}
|
||||
|
||||
assert.isTrue(
|
||||
constantTimeEqual(
|
||||
toArrayBuffer(expected.serialize()),
|
||||
toArrayBuffer(actual.serialize())
|
||||
)
|
||||
constantTimeEqual(expected.serialize(), actual.serialize())
|
||||
);
|
||||
|
||||
await store.removeSenderKey(qualifiedAddress, distributionId);
|
||||
|
@ -230,10 +219,7 @@ describe('SignalProtocolStore', () => {
|
|||
}
|
||||
|
||||
assert.isTrue(
|
||||
constantTimeEqual(
|
||||
toArrayBuffer(expected.serialize()),
|
||||
toArrayBuffer(actual.serialize())
|
||||
)
|
||||
constantTimeEqual(expected.serialize(), actual.serialize())
|
||||
);
|
||||
|
||||
await store.removeSenderKey(qualifiedAddress, distributionId);
|
||||
|
@ -1254,12 +1240,8 @@ describe('SignalProtocolStore', () => {
|
|||
}
|
||||
|
||||
const keyPair = {
|
||||
pubKey: window.Signal.Crypto.typedArrayToArrayBuffer(
|
||||
key.publicKey().serialize()
|
||||
),
|
||||
privKey: window.Signal.Crypto.typedArrayToArrayBuffer(
|
||||
key.privateKey().serialize()
|
||||
),
|
||||
pubKey: key.publicKey().serialize(),
|
||||
privKey: key.privateKey().serialize(),
|
||||
};
|
||||
|
||||
assert.isTrue(constantTimeEqual(keyPair.pubKey, testKey.pubKey));
|
||||
|
@ -1286,12 +1268,8 @@ describe('SignalProtocolStore', () => {
|
|||
}
|
||||
|
||||
const keyPair = {
|
||||
pubKey: window.Signal.Crypto.typedArrayToArrayBuffer(
|
||||
key.publicKey().serialize()
|
||||
),
|
||||
privKey: window.Signal.Crypto.typedArrayToArrayBuffer(
|
||||
key.privateKey().serialize()
|
||||
),
|
||||
pubKey: key.publicKey().serialize(),
|
||||
privKey: key.privateKey().serialize(),
|
||||
};
|
||||
|
||||
assert.isTrue(constantTimeEqual(keyPair.pubKey, testKey.pubKey));
|
||||
|
|
|
@ -4,22 +4,19 @@
|
|||
import { assert } from 'chai';
|
||||
import crypto from 'crypto';
|
||||
|
||||
import { typedArrayToArrayBuffer as toArrayBuffer } from '../../Crypto';
|
||||
import {
|
||||
CipherType,
|
||||
HashType,
|
||||
hash,
|
||||
sign,
|
||||
encrypt,
|
||||
decrypt,
|
||||
} from '../../util/synchronousCrypto';
|
||||
} from '../../Crypto';
|
||||
|
||||
describe('synchronousCrypto', () => {
|
||||
describe('SignalContext.Crypto', () => {
|
||||
describe('hash', () => {
|
||||
it('returns SHA512 hash of the input', () => {
|
||||
const result = hash(
|
||||
HashType.size512,
|
||||
toArrayBuffer(Buffer.from('signal'))
|
||||
);
|
||||
const result = hash(HashType.size512, Buffer.from('signal'));
|
||||
assert.strictEqual(
|
||||
Buffer.from(result).toString('base64'),
|
||||
'WxneQjrfSlY95Bi+SAzDAr2cf3mxUXePeNYn6DILN4a8NFr9VelTbP5tGHdthi+' +
|
||||
|
@ -30,10 +27,7 @@ describe('synchronousCrypto', () => {
|
|||
|
||||
describe('sign', () => {
|
||||
it('returns hmac SHA256 hash of the input', () => {
|
||||
const result = sign(
|
||||
toArrayBuffer(Buffer.from('secret')),
|
||||
toArrayBuffer(Buffer.from('signal'))
|
||||
);
|
||||
const result = sign(Buffer.from('secret'), Buffer.from('signal'));
|
||||
|
||||
assert.strictEqual(
|
||||
Buffer.from(result).toString('base64'),
|
||||
|
@ -44,12 +38,20 @@ describe('synchronousCrypto', () => {
|
|||
|
||||
describe('encrypt+decrypt', () => {
|
||||
it('returns original input', () => {
|
||||
const iv = toArrayBuffer(crypto.randomBytes(16));
|
||||
const key = toArrayBuffer(crypto.randomBytes(32));
|
||||
const input = toArrayBuffer(Buffer.from('plaintext'));
|
||||
const iv = crypto.randomBytes(16);
|
||||
const key = crypto.randomBytes(32);
|
||||
const input = Buffer.from('plaintext');
|
||||
|
||||
const ciphertext = encrypt(key, input, iv);
|
||||
const plaintext = decrypt(key, ciphertext, iv);
|
||||
const ciphertext = encrypt(CipherType.AES256CBC, {
|
||||
key,
|
||||
iv,
|
||||
plaintext: input,
|
||||
});
|
||||
const plaintext = decrypt(CipherType.AES256CBC, {
|
||||
key,
|
||||
iv,
|
||||
ciphertext,
|
||||
});
|
||||
|
||||
assert.strictEqual(Buffer.from(plaintext).toString(), 'plaintext');
|
||||
});
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { Response } from 'node-fetch';
|
||||
import * as sinon from 'sinon';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
@ -9,8 +10,6 @@ import AbortController from 'abort-controller';
|
|||
import { IMAGE_JPEG, stringToMIMEType } from '../../types/MIME';
|
||||
import * as log from '../../logging/log';
|
||||
|
||||
import { typedArrayToArrayBuffer } from '../../Crypto';
|
||||
|
||||
import {
|
||||
fetchLinkPreviewImage,
|
||||
fetchLinkPreviewMetadata,
|
||||
|
@ -1155,7 +1154,7 @@ describe('link preview fetching', () => {
|
|||
new AbortController().signal
|
||||
),
|
||||
{
|
||||
data: typedArrayToArrayBuffer(fixture),
|
||||
data: fixture,
|
||||
contentType: stringToMIMEType(contentType),
|
||||
}
|
||||
);
|
||||
|
@ -1217,7 +1216,7 @@ describe('link preview fetching', () => {
|
|||
|
||||
const fakeFetch = stub();
|
||||
fakeFetch.onFirstCall().resolves(
|
||||
new Response(null, {
|
||||
new Response(Buffer.from(''), {
|
||||
status: 301,
|
||||
headers: {
|
||||
Location: '/result.jpg',
|
||||
|
@ -1240,7 +1239,7 @@ describe('link preview fetching', () => {
|
|||
new AbortController().signal
|
||||
),
|
||||
{
|
||||
data: typedArrayToArrayBuffer(fixture),
|
||||
data: fixture,
|
||||
contentType: IMAGE_JPEG,
|
||||
}
|
||||
);
|
||||
|
@ -1336,7 +1335,7 @@ describe('link preview fetching', () => {
|
|||
});
|
||||
|
||||
it('sends WhatsApp as the User-Agent for compatibility', async () => {
|
||||
const fakeFetch = stub().resolves(new Response(null));
|
||||
const fakeFetch = stub().resolves(new Response(Buffer.from('')));
|
||||
|
||||
await fetchLinkPreviewImage(
|
||||
fakeFetch,
|
||||
|
@ -1368,7 +1367,7 @@ describe('link preview fetching', () => {
|
|||
},
|
||||
});
|
||||
sinon
|
||||
.stub(response, 'arrayBuffer')
|
||||
.stub(response, 'buffer')
|
||||
.rejects(new Error('Should not be called'));
|
||||
sinon.stub(response, 'blob').rejects(new Error('Should not be called'));
|
||||
sinon.stub(response, 'text').rejects(new Error('Should not be called'));
|
||||
|
@ -1402,9 +1401,9 @@ describe('link preview fetching', () => {
|
|||
'Content-Length': fixture.length.toString(),
|
||||
},
|
||||
});
|
||||
const oldArrayBufferMethod = response.arrayBuffer.bind(response);
|
||||
sinon.stub(response, 'arrayBuffer').callsFake(async () => {
|
||||
const data = await oldArrayBufferMethod();
|
||||
const oldBufferMethod = response.buffer.bind(response);
|
||||
sinon.stub(response, 'buffer').callsFake(async () => {
|
||||
const data = await oldBufferMethod();
|
||||
abortController.abort();
|
||||
return data;
|
||||
});
|
||||
|
|
|
@ -136,7 +136,7 @@ describe('Message', () => {
|
|||
},
|
||||
});
|
||||
|
||||
const fakeDataMessage = new ArrayBuffer(0);
|
||||
const fakeDataMessage = new Uint8Array(0);
|
||||
const conversation1Uuid = conversation1.get('uuid');
|
||||
const ignoredUuid = window.getGuid();
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ import { v4 as uuid } from 'uuid';
|
|||
import Long from 'long';
|
||||
import * as durations from '../../util/durations';
|
||||
import * as Bytes from '../../Bytes';
|
||||
import { typedArrayToArrayBuffer } from '../../Crypto';
|
||||
import { SenderCertificateMode } from '../../textsecure/OutgoingMessage';
|
||||
import { SignalService as Proto } from '../../protobuf';
|
||||
|
||||
|
@ -22,6 +21,7 @@ describe('SenderCertificateService', () => {
|
|||
const FIFTEEN_MINUTES = 15 * durations.MINUTE;
|
||||
|
||||
let fakeValidCertificate: SenderCertificate;
|
||||
let fakeValidEncodedCertificate: Uint8Array;
|
||||
let fakeValidCertificateExpiry: number;
|
||||
let fakeServer: any;
|
||||
let fakeNavigator: { onLine: boolean };
|
||||
|
@ -47,12 +47,13 @@ describe('SenderCertificateService', () => {
|
|||
fakeValidCertificate.certificate = SenderCertificate.Certificate.encode(
|
||||
certificate
|
||||
).finish();
|
||||
fakeValidEncodedCertificate = SenderCertificate.encode(
|
||||
fakeValidCertificate
|
||||
).finish();
|
||||
|
||||
fakeServer = {
|
||||
getSenderCertificate: sinon.stub().resolves({
|
||||
certificate: Bytes.toBase64(
|
||||
SenderCertificate.encode(fakeValidCertificate).finish()
|
||||
),
|
||||
certificate: Bytes.toBase64(fakeValidEncodedCertificate),
|
||||
}),
|
||||
};
|
||||
|
||||
|
@ -77,7 +78,7 @@ describe('SenderCertificateService', () => {
|
|||
it('returns valid yes-E164 certificates from storage if they exist', async () => {
|
||||
const cert = {
|
||||
expires: Date.now() + 123456,
|
||||
serialized: new ArrayBuffer(2),
|
||||
serialized: new Uint8Array(2),
|
||||
};
|
||||
fakeStorage.get.withArgs('senderCertificate').returns(cert);
|
||||
|
||||
|
@ -94,7 +95,7 @@ describe('SenderCertificateService', () => {
|
|||
it('returns valid no-E164 certificates from storage if they exist', async () => {
|
||||
const cert = {
|
||||
expires: Date.now() + 123456,
|
||||
serialized: new ArrayBuffer(2),
|
||||
serialized: new Uint8Array(2),
|
||||
};
|
||||
fakeStorage.get.withArgs('senderCertificateNoE164').returns(cert);
|
||||
|
||||
|
@ -113,16 +114,12 @@ describe('SenderCertificateService', () => {
|
|||
|
||||
assert.deepEqual(await service.get(SenderCertificateMode.WithE164), {
|
||||
expires: fakeValidCertificateExpiry - FIFTEEN_MINUTES,
|
||||
serialized: typedArrayToArrayBuffer(
|
||||
SenderCertificate.encode(fakeValidCertificate).finish()
|
||||
),
|
||||
serialized: fakeValidEncodedCertificate,
|
||||
});
|
||||
|
||||
sinon.assert.calledWithMatch(fakeStorage.put, 'senderCertificate', {
|
||||
expires: fakeValidCertificateExpiry - FIFTEEN_MINUTES,
|
||||
serialized: typedArrayToArrayBuffer(
|
||||
SenderCertificate.encode(fakeValidCertificate).finish()
|
||||
),
|
||||
serialized: Buffer.from(fakeValidEncodedCertificate),
|
||||
});
|
||||
|
||||
sinon.assert.calledWith(fakeServer.getSenderCertificate, false);
|
||||
|
@ -133,16 +130,12 @@ describe('SenderCertificateService', () => {
|
|||
|
||||
assert.deepEqual(await service.get(SenderCertificateMode.WithoutE164), {
|
||||
expires: fakeValidCertificateExpiry - FIFTEEN_MINUTES,
|
||||
serialized: typedArrayToArrayBuffer(
|
||||
SenderCertificate.encode(fakeValidCertificate).finish()
|
||||
),
|
||||
serialized: fakeValidEncodedCertificate,
|
||||
});
|
||||
|
||||
sinon.assert.calledWithMatch(fakeStorage.put, 'senderCertificateNoE164', {
|
||||
expires: fakeValidCertificateExpiry - FIFTEEN_MINUTES,
|
||||
serialized: typedArrayToArrayBuffer(
|
||||
SenderCertificate.encode(fakeValidCertificate).finish()
|
||||
),
|
||||
serialized: Buffer.from(fakeValidEncodedCertificate),
|
||||
});
|
||||
|
||||
sinon.assert.calledWith(fakeServer.getSenderCertificate, true);
|
||||
|
@ -153,7 +146,7 @@ describe('SenderCertificateService', () => {
|
|||
|
||||
fakeStorage.get.withArgs('senderCertificate').returns({
|
||||
expires: Date.now() - 1000,
|
||||
serialized: new ArrayBuffer(2),
|
||||
serialized: new Uint8Array(2),
|
||||
});
|
||||
|
||||
await service.get(SenderCertificateMode.WithE164);
|
||||
|
@ -165,7 +158,7 @@ describe('SenderCertificateService', () => {
|
|||
const service = initializeTestService();
|
||||
|
||||
fakeStorage.get.withArgs('senderCertificate').returns({
|
||||
serialized: 'not an arraybuffer',
|
||||
serialized: 'not an uint8array',
|
||||
});
|
||||
|
||||
await service.get(SenderCertificateMode.WithE164);
|
||||
|
|
|
@ -6,11 +6,7 @@ import { v4 as getGuid } from 'uuid';
|
|||
import { assert } from 'chai';
|
||||
|
||||
import dataInterface from '../../sql/Client';
|
||||
import {
|
||||
constantTimeEqual,
|
||||
getRandomBytes,
|
||||
typedArrayToArrayBuffer,
|
||||
} from '../../Crypto';
|
||||
import { constantTimeEqual, getRandomBytes } from '../../Crypto';
|
||||
|
||||
const {
|
||||
_getAllSentProtoMessageIds,
|
||||
|
@ -33,7 +29,7 @@ describe('sendLog', () => {
|
|||
});
|
||||
|
||||
it('roundtrips with insertSentProto/getAllSentProtos', async () => {
|
||||
const bytes = Buffer.from(getRandomBytes(128));
|
||||
const bytes = getRandomBytes(128);
|
||||
const timestamp = Date.now();
|
||||
const proto = {
|
||||
contentHint: 1,
|
||||
|
@ -52,12 +48,7 @@ describe('sendLog', () => {
|
|||
const actual = allProtos[0];
|
||||
|
||||
assert.strictEqual(actual.contentHint, proto.contentHint);
|
||||
assert.isTrue(
|
||||
constantTimeEqual(
|
||||
typedArrayToArrayBuffer(actual.proto),
|
||||
typedArrayToArrayBuffer(proto.proto)
|
||||
)
|
||||
);
|
||||
assert.isTrue(constantTimeEqual(actual.proto, proto.proto));
|
||||
assert.strictEqual(actual.timestamp, proto.timestamp);
|
||||
|
||||
await removeAllSentProtos();
|
||||
|
@ -70,7 +61,7 @@ describe('sendLog', () => {
|
|||
assert.lengthOf(await _getAllSentProtoMessageIds(), 0);
|
||||
assert.lengthOf(await _getAllSentProtoRecipients(), 0);
|
||||
|
||||
const bytes = Buffer.from(getRandomBytes(128));
|
||||
const bytes = getRandomBytes(128);
|
||||
const timestamp = Date.now();
|
||||
const proto = {
|
||||
contentHint: 1,
|
||||
|
@ -114,7 +105,7 @@ describe('sendLog', () => {
|
|||
{ forceSave: true }
|
||||
);
|
||||
|
||||
const bytes = Buffer.from(getRandomBytes(128));
|
||||
const bytes = getRandomBytes(128);
|
||||
const proto = {
|
||||
contentHint: 1,
|
||||
proto: bytes,
|
||||
|
@ -148,12 +139,12 @@ describe('sendLog', () => {
|
|||
};
|
||||
const proto1 = {
|
||||
contentHint: 7,
|
||||
proto: Buffer.from(getRandomBytes(128)),
|
||||
proto: getRandomBytes(128),
|
||||
timestamp,
|
||||
};
|
||||
const proto2 = {
|
||||
contentHint: 9,
|
||||
proto: Buffer.from(getRandomBytes(128)),
|
||||
proto: getRandomBytes(128),
|
||||
timestamp,
|
||||
};
|
||||
|
||||
|
@ -182,7 +173,7 @@ describe('sendLog', () => {
|
|||
const messageIds = [getGuid()];
|
||||
const proto = {
|
||||
contentHint: 1,
|
||||
proto: Buffer.from(getRandomBytes(128)),
|
||||
proto: getRandomBytes(128),
|
||||
timestamp,
|
||||
};
|
||||
|
||||
|
@ -220,17 +211,17 @@ describe('sendLog', () => {
|
|||
|
||||
const proto1 = {
|
||||
contentHint: 1,
|
||||
proto: Buffer.from(getRandomBytes(128)),
|
||||
proto: getRandomBytes(128),
|
||||
timestamp: timestamp + 10,
|
||||
};
|
||||
const proto2 = {
|
||||
contentHint: 2,
|
||||
proto: Buffer.from(getRandomBytes(128)),
|
||||
proto: getRandomBytes(128),
|
||||
timestamp,
|
||||
};
|
||||
const proto3 = {
|
||||
contentHint: 0,
|
||||
proto: Buffer.from(getRandomBytes(128)),
|
||||
proto: getRandomBytes(128),
|
||||
timestamp: timestamp - 15,
|
||||
};
|
||||
await insertSentProto(proto1, {
|
||||
|
@ -261,22 +252,12 @@ describe('sendLog', () => {
|
|||
|
||||
const actual1 = allProtos[0];
|
||||
assert.strictEqual(actual1.contentHint, proto1.contentHint);
|
||||
assert.isTrue(
|
||||
constantTimeEqual(
|
||||
typedArrayToArrayBuffer(actual1.proto),
|
||||
typedArrayToArrayBuffer(proto1.proto)
|
||||
)
|
||||
);
|
||||
assert.isTrue(constantTimeEqual(actual1.proto, proto1.proto));
|
||||
assert.strictEqual(actual1.timestamp, proto1.timestamp);
|
||||
|
||||
const actual2 = allProtos[1];
|
||||
assert.strictEqual(actual2.contentHint, proto2.contentHint);
|
||||
assert.isTrue(
|
||||
constantTimeEqual(
|
||||
typedArrayToArrayBuffer(actual2.proto),
|
||||
typedArrayToArrayBuffer(proto2.proto)
|
||||
)
|
||||
);
|
||||
assert.isTrue(constantTimeEqual(actual2.proto, proto2.proto));
|
||||
assert.strictEqual(actual2.timestamp, proto2.timestamp);
|
||||
});
|
||||
});
|
||||
|
@ -291,17 +272,17 @@ describe('sendLog', () => {
|
|||
const timestamp = Date.now();
|
||||
const proto1 = {
|
||||
contentHint: 1,
|
||||
proto: Buffer.from(getRandomBytes(128)),
|
||||
proto: getRandomBytes(128),
|
||||
timestamp,
|
||||
};
|
||||
const proto2 = {
|
||||
contentHint: 1,
|
||||
proto: Buffer.from(getRandomBytes(128)),
|
||||
proto: getRandomBytes(128),
|
||||
timestamp: timestamp - 10,
|
||||
};
|
||||
const proto3 = {
|
||||
contentHint: 1,
|
||||
proto: Buffer.from(getRandomBytes(128)),
|
||||
proto: getRandomBytes(128),
|
||||
timestamp: timestamp - 20,
|
||||
};
|
||||
await insertSentProto(proto1, {
|
||||
|
@ -344,7 +325,7 @@ describe('sendLog', () => {
|
|||
const recipientUuid2 = getGuid();
|
||||
const proto = {
|
||||
contentHint: 1,
|
||||
proto: Buffer.from(getRandomBytes(128)),
|
||||
proto: getRandomBytes(128),
|
||||
timestamp,
|
||||
};
|
||||
await insertSentProto(proto, {
|
||||
|
@ -375,7 +356,7 @@ describe('sendLog', () => {
|
|||
const recipientUuid2 = getGuid();
|
||||
const proto = {
|
||||
contentHint: 1,
|
||||
proto: Buffer.from(getRandomBytes(128)),
|
||||
proto: getRandomBytes(128),
|
||||
timestamp,
|
||||
};
|
||||
await insertSentProto(proto, {
|
||||
|
@ -424,7 +405,7 @@ describe('sendLog', () => {
|
|||
const recipientUuid2 = getGuid();
|
||||
const proto = {
|
||||
contentHint: 1,
|
||||
proto: Buffer.from(getRandomBytes(128)),
|
||||
proto: getRandomBytes(128),
|
||||
timestamp,
|
||||
};
|
||||
await insertSentProto(proto, {
|
||||
|
@ -469,7 +450,7 @@ describe('sendLog', () => {
|
|||
const messageIds = [getGuid(), getGuid()];
|
||||
const proto = {
|
||||
contentHint: 1,
|
||||
proto: Buffer.from(getRandomBytes(128)),
|
||||
proto: getRandomBytes(128),
|
||||
timestamp,
|
||||
};
|
||||
await insertSentProto(proto, {
|
||||
|
@ -493,12 +474,7 @@ describe('sendLog', () => {
|
|||
throw new Error('Failed to fetch proto!');
|
||||
}
|
||||
assert.strictEqual(actual.contentHint, proto.contentHint);
|
||||
assert.isTrue(
|
||||
constantTimeEqual(
|
||||
typedArrayToArrayBuffer(actual.proto),
|
||||
typedArrayToArrayBuffer(proto.proto)
|
||||
)
|
||||
);
|
||||
assert.isTrue(constantTimeEqual(actual.proto, proto.proto));
|
||||
assert.strictEqual(actual.timestamp, proto.timestamp);
|
||||
assert.sameMembers(actual.messageIds, messageIds);
|
||||
});
|
||||
|
@ -509,7 +485,7 @@ describe('sendLog', () => {
|
|||
const recipientUuid = getGuid();
|
||||
const proto = {
|
||||
contentHint: 1,
|
||||
proto: Buffer.from(getRandomBytes(128)),
|
||||
proto: getRandomBytes(128),
|
||||
timestamp,
|
||||
};
|
||||
await insertSentProto(proto, {
|
||||
|
@ -533,12 +509,7 @@ describe('sendLog', () => {
|
|||
throw new Error('Failed to fetch proto!');
|
||||
}
|
||||
assert.strictEqual(actual.contentHint, proto.contentHint);
|
||||
assert.isTrue(
|
||||
constantTimeEqual(
|
||||
typedArrayToArrayBuffer(actual.proto),
|
||||
typedArrayToArrayBuffer(proto.proto)
|
||||
)
|
||||
);
|
||||
assert.isTrue(constantTimeEqual(actual.proto, proto.proto));
|
||||
assert.strictEqual(actual.timestamp, proto.timestamp);
|
||||
assert.deepEqual(actual.messageIds, []);
|
||||
});
|
||||
|
@ -549,7 +520,7 @@ describe('sendLog', () => {
|
|||
const recipientUuid = getGuid();
|
||||
const proto = {
|
||||
contentHint: 1,
|
||||
proto: Buffer.from(getRandomBytes(128)),
|
||||
proto: getRandomBytes(128),
|
||||
timestamp,
|
||||
};
|
||||
await insertSentProto(proto, {
|
||||
|
@ -577,7 +548,7 @@ describe('sendLog', () => {
|
|||
const recipientUuid = getGuid();
|
||||
const proto = {
|
||||
contentHint: 1,
|
||||
proto: Buffer.from(getRandomBytes(128)),
|
||||
proto: getRandomBytes(128),
|
||||
timestamp,
|
||||
};
|
||||
await insertSentProto(proto, {
|
||||
|
@ -606,7 +577,7 @@ describe('sendLog', () => {
|
|||
const recipientUuid = getGuid();
|
||||
const proto = {
|
||||
contentHint: 1,
|
||||
proto: Buffer.from(getRandomBytes(128)),
|
||||
proto: getRandomBytes(128),
|
||||
timestamp,
|
||||
};
|
||||
await insertSentProto(proto, {
|
||||
|
|
|
@ -645,7 +645,7 @@ describe('both/state/ducks/conversations', () => {
|
|||
...defaultSetGroupMetadataComposerState,
|
||||
selectedConversationIds: ['abc123'],
|
||||
groupName: 'Foo Bar Group',
|
||||
groupAvatar: new Uint8Array([1, 2, 3]).buffer,
|
||||
groupAvatar: new Uint8Array([1, 2, 3]),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -688,7 +688,7 @@ describe('both/state/ducks/conversations', () => {
|
|||
sinon.assert.calledOnce(createGroupStub);
|
||||
sinon.assert.calledWith(createGroupStub, {
|
||||
name: 'Foo Bar Group',
|
||||
avatar: new Uint8Array([1, 2, 3]).buffer,
|
||||
avatar: new Uint8Array([1, 2, 3]),
|
||||
avatars: [],
|
||||
expireTimer: 0,
|
||||
conversationIds: ['abc123'],
|
||||
|
@ -1172,7 +1172,7 @@ describe('both/state/ducks/conversations', () => {
|
|||
...getEmptyState(),
|
||||
composer: {
|
||||
...defaultSetGroupMetadataComposerState,
|
||||
groupAvatar: new ArrayBuffer(2),
|
||||
groupAvatar: new Uint8Array(2),
|
||||
},
|
||||
};
|
||||
const action = setComposeGroupAvatar(undefined);
|
||||
|
@ -1185,7 +1185,7 @@ describe('both/state/ducks/conversations', () => {
|
|||
});
|
||||
|
||||
it("can set the composer's group avatar", () => {
|
||||
const avatar = new Uint8Array([1, 2, 3]).buffer;
|
||||
const avatar = new Uint8Array([1, 2, 3]);
|
||||
|
||||
const state = {
|
||||
...getEmptyState(),
|
||||
|
@ -1450,7 +1450,7 @@ describe('both/state/ducks/conversations', () => {
|
|||
composer: {
|
||||
...defaultSetGroupMetadataComposerState,
|
||||
groupName: 'Foo Bar Group',
|
||||
groupAvatar: new Uint8Array([4, 2]).buffer,
|
||||
groupAvatar: new Uint8Array([4, 2]),
|
||||
},
|
||||
};
|
||||
const action = showChooseGroupMembers();
|
||||
|
@ -1460,7 +1460,7 @@ describe('both/state/ducks/conversations', () => {
|
|||
assert.deepEqual(result.composer, {
|
||||
...defaultChooseGroupMembersComposerState,
|
||||
groupName: 'Foo Bar Group',
|
||||
groupAvatar: new Uint8Array([4, 2]).buffer,
|
||||
groupAvatar: new Uint8Array([4, 2]),
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1518,7 +1518,7 @@ describe('both/state/ducks/conversations', () => {
|
|||
searchTerm: 'foo bar',
|
||||
selectedConversationIds: ['abc', 'def'],
|
||||
groupName: 'Foo Bar Group',
|
||||
groupAvatar: new Uint8Array([6, 9]).buffer,
|
||||
groupAvatar: new Uint8Array([6, 9]),
|
||||
},
|
||||
};
|
||||
const action = startSettingGroupMetadata();
|
||||
|
@ -1528,7 +1528,7 @@ describe('both/state/ducks/conversations', () => {
|
|||
...defaultSetGroupMetadataComposerState,
|
||||
selectedConversationIds: ['abc', 'def'],
|
||||
groupName: 'Foo Bar Group',
|
||||
groupAvatar: new Uint8Array([6, 9]).buffer,
|
||||
groupAvatar: new Uint8Array([6, 9]),
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ describe('AccountManager', () => {
|
|||
describe('encrypted device name', () => {
|
||||
it('roundtrips', async () => {
|
||||
const deviceName = 'v2.5.0 on Ubunto 20.04';
|
||||
const encrypted = await accountManager.encryptDeviceName(
|
||||
const encrypted = accountManager.encryptDeviceName(
|
||||
deviceName,
|
||||
identityKey
|
||||
);
|
||||
|
@ -72,11 +72,8 @@ describe('AccountManager', () => {
|
|||
assert.strictEqual(decrypted, deviceName);
|
||||
});
|
||||
|
||||
it('handles falsey deviceName', async () => {
|
||||
const encrypted = await accountManager.encryptDeviceName(
|
||||
'',
|
||||
identityKey
|
||||
);
|
||||
it('handles falsey deviceName', () => {
|
||||
const encrypted = accountManager.encryptDeviceName('', identityKey);
|
||||
assert.strictEqual(encrypted, null);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,11 +3,8 @@
|
|||
|
||||
import { assert } from 'chai';
|
||||
|
||||
import {
|
||||
typedArrayToArrayBuffer as toArrayBuffer,
|
||||
arrayBufferToBase64 as toBase64,
|
||||
constantTimeEqual,
|
||||
} from '../../Crypto';
|
||||
import { toBase64 } from '../../Bytes';
|
||||
import { constantTimeEqual } from '../../Crypto';
|
||||
import { generateKeyPair } from '../../Curve';
|
||||
import AccountManager, {
|
||||
GeneratedKeysType,
|
||||
|
@ -17,7 +14,7 @@ import { UUID } from '../../types/UUID';
|
|||
|
||||
const { textsecure } = window;
|
||||
|
||||
const assertEqualArrayBuffers = (a: ArrayBuffer, b: ArrayBuffer) => {
|
||||
const assertEqualBuffers = (a: Uint8Array, b: Uint8Array) => {
|
||||
assert.isTrue(constantTimeEqual(a, b));
|
||||
};
|
||||
|
||||
|
@ -54,10 +51,7 @@ describe('Key generation', function thisNeeded() {
|
|||
if (!keyPair) {
|
||||
throw new Error(`PreKey ${resultKey.keyId} not found`);
|
||||
}
|
||||
assertEqualArrayBuffers(
|
||||
resultKey.publicKey,
|
||||
toArrayBuffer(keyPair.publicKey().serialize())
|
||||
);
|
||||
assertEqualBuffers(resultKey.publicKey, keyPair.publicKey().serialize());
|
||||
}
|
||||
async function validateResultSignedKey(
|
||||
resultSignedKey: Pick<SignedPreKeyType, 'keyId' | 'publicKey'>
|
||||
|
@ -69,9 +63,9 @@ describe('Key generation', function thisNeeded() {
|
|||
if (!keyPair) {
|
||||
throw new Error(`SignedPreKey ${resultSignedKey.keyId} not found`);
|
||||
}
|
||||
assertEqualArrayBuffers(
|
||||
assertEqualBuffers(
|
||||
resultSignedKey.publicKey,
|
||||
toArrayBuffer(keyPair.publicKey().serialize())
|
||||
keyPair.publicKey().serialize()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -123,7 +117,7 @@ describe('Key generation', function thisNeeded() {
|
|||
});
|
||||
it('returns a signed prekey', () => {
|
||||
assert.strictEqual(result.signedPreKey.keyId, 1);
|
||||
assert.instanceOf(result.signedPreKey.signature, ArrayBuffer);
|
||||
assert.instanceOf(result.signedPreKey.signature, Uint8Array);
|
||||
return validateResultSignedKey(result.signedPreKey);
|
||||
});
|
||||
});
|
||||
|
@ -157,7 +151,7 @@ describe('Key generation', function thisNeeded() {
|
|||
});
|
||||
it('returns a signed prekey', () => {
|
||||
assert.strictEqual(result.signedPreKey.keyId, 2);
|
||||
assert.instanceOf(result.signedPreKey.signature, ArrayBuffer);
|
||||
assert.instanceOf(result.signedPreKey.signature, Uint8Array);
|
||||
return validateResultSignedKey(result.signedPreKey);
|
||||
});
|
||||
});
|
||||
|
@ -191,7 +185,7 @@ describe('Key generation', function thisNeeded() {
|
|||
});
|
||||
it('result contains a signed prekey', () => {
|
||||
assert.strictEqual(result.signedPreKey.keyId, 3);
|
||||
assert.instanceOf(result.signedPreKey.signature, ArrayBuffer);
|
||||
assert.instanceOf(result.signedPreKey.signature, Uint8Array);
|
||||
return validateResultSignedKey(result.signedPreKey);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,7 +26,7 @@ describe('canvasToBlob', () => {
|
|||
const result = await canvasToBlob(canvas);
|
||||
|
||||
assert.strictEqual(
|
||||
sniffImageMimeType(await result.arrayBuffer()),
|
||||
sniffImageMimeType(new Uint8Array(await result.arrayBuffer())),
|
||||
IMAGE_JPEG
|
||||
);
|
||||
|
||||
|
@ -39,7 +39,7 @@ describe('canvasToBlob', () => {
|
|||
const result = await canvasToBlob(canvas, IMAGE_PNG);
|
||||
|
||||
assert.strictEqual(
|
||||
sniffImageMimeType(await result.arrayBuffer()),
|
||||
sniffImageMimeType(new Uint8Array(await result.arrayBuffer())),
|
||||
IMAGE_PNG
|
||||
);
|
||||
});
|
||||
|
|
|
@ -5,9 +5,9 @@ import { assert } from 'chai';
|
|||
import { IMAGE_JPEG, IMAGE_PNG } from '../../types/MIME';
|
||||
import { sniffImageMimeType } from '../../util/sniffImageMimeType';
|
||||
|
||||
import { canvasToArrayBuffer } from '../../util/canvasToArrayBuffer';
|
||||
import { canvasToBytes } from '../../util/canvasToBytes';
|
||||
|
||||
describe('canvasToArrayBuffer', () => {
|
||||
describe('canvasToBytes', () => {
|
||||
let canvas: HTMLCanvasElement;
|
||||
beforeEach(() => {
|
||||
canvas = document.createElement('canvas');
|
||||
|
@ -22,18 +22,18 @@ describe('canvasToArrayBuffer', () => {
|
|||
context.fillRect(10, 10, 20, 20);
|
||||
});
|
||||
|
||||
it('converts a canvas to an ArrayBuffer, JPEG by default', async () => {
|
||||
const result = await canvasToArrayBuffer(canvas);
|
||||
it('converts a canvas to an Uint8Array, JPEG by default', async () => {
|
||||
const result = await canvasToBytes(canvas);
|
||||
|
||||
assert.strictEqual(sniffImageMimeType(result), IMAGE_JPEG);
|
||||
|
||||
// These are just smoke tests.
|
||||
assert.instanceOf(result, ArrayBuffer);
|
||||
assert.instanceOf(result, Uint8Array);
|
||||
assert.isAtLeast(result.byteLength, 50);
|
||||
});
|
||||
|
||||
it('can convert a canvas to a PNG ArrayBuffer', async () => {
|
||||
const result = await canvasToArrayBuffer(canvas, IMAGE_PNG);
|
||||
it('can convert a canvas to a PNG Uint8Array', async () => {
|
||||
const result = await canvasToBytes(canvas, IMAGE_PNG);
|
||||
|
||||
assert.strictEqual(sniffImageMimeType(result), IMAGE_PNG);
|
||||
});
|
|
@ -4,24 +4,24 @@
|
|||
import { assert } from 'chai';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import Crypto from '../../textsecure/Crypto';
|
||||
import * as Bytes from '../../Bytes';
|
||||
import {
|
||||
arrayBufferToBase64,
|
||||
base64ToArrayBuffer,
|
||||
stringFromBytes,
|
||||
trimForDisplay,
|
||||
getRandomBytes,
|
||||
decryptProfileName,
|
||||
decryptProfile,
|
||||
} from '../../Crypto';
|
||||
import { encryptProfileData } from '../../util/encryptProfileData';
|
||||
|
||||
describe('encryptProfileData', () => {
|
||||
it('encrypts and decrypts properly', async () => {
|
||||
const keyBuffer = Crypto.getRandomBytes(32);
|
||||
const keyBuffer = getRandomBytes(32);
|
||||
const conversation = {
|
||||
aboutEmoji: '🐢',
|
||||
aboutText: 'I like turtles',
|
||||
familyName: 'Kid',
|
||||
firstName: 'Zombie',
|
||||
profileKey: arrayBufferToBase64(keyBuffer),
|
||||
profileKey: Bytes.toBase64(keyBuffer),
|
||||
uuid: uuid(),
|
||||
|
||||
// To satisfy TS
|
||||
|
@ -39,17 +39,17 @@ describe('encryptProfileData', () => {
|
|||
assert.isDefined(encrypted.name);
|
||||
assert.isDefined(encrypted.commitment);
|
||||
|
||||
const decryptedProfileNameBytes = await Crypto.decryptProfileName(
|
||||
const decryptedProfileNameBytes = decryptProfileName(
|
||||
encrypted.name,
|
||||
keyBuffer
|
||||
);
|
||||
assert.equal(
|
||||
stringFromBytes(decryptedProfileNameBytes.given),
|
||||
Bytes.toString(decryptedProfileNameBytes.given),
|
||||
conversation.firstName
|
||||
);
|
||||
if (decryptedProfileNameBytes.family) {
|
||||
assert.equal(
|
||||
stringFromBytes(decryptedProfileNameBytes.family),
|
||||
Bytes.toString(decryptedProfileNameBytes.family),
|
||||
conversation.familyName
|
||||
);
|
||||
} else {
|
||||
|
@ -57,12 +57,12 @@ describe('encryptProfileData', () => {
|
|||
}
|
||||
|
||||
if (encrypted.about) {
|
||||
const decryptedAboutBytes = await Crypto.decryptProfile(
|
||||
base64ToArrayBuffer(encrypted.about),
|
||||
const decryptedAboutBytes = decryptProfile(
|
||||
Bytes.fromBase64(encrypted.about),
|
||||
keyBuffer
|
||||
);
|
||||
assert.equal(
|
||||
stringFromBytes(trimForDisplay(decryptedAboutBytes)),
|
||||
Bytes.toString(trimForDisplay(decryptedAboutBytes)),
|
||||
conversation.aboutText
|
||||
);
|
||||
} else {
|
||||
|
@ -70,12 +70,12 @@ describe('encryptProfileData', () => {
|
|||
}
|
||||
|
||||
if (encrypted.aboutEmoji) {
|
||||
const decryptedAboutEmojiBytes = await Crypto.decryptProfile(
|
||||
base64ToArrayBuffer(encrypted.aboutEmoji),
|
||||
const decryptedAboutEmojiBytes = await decryptProfile(
|
||||
Bytes.fromBase64(encrypted.aboutEmoji),
|
||||
keyBuffer
|
||||
);
|
||||
assert.equal(
|
||||
stringFromBytes(trimForDisplay(decryptedAboutEmojiBytes)),
|
||||
Bytes.toString(trimForDisplay(decryptedAboutEmojiBytes)),
|
||||
conversation.aboutEmoji
|
||||
);
|
||||
} else {
|
||||
|
|
|
@ -4,16 +4,16 @@
|
|||
import { assert } from 'chai';
|
||||
import path from 'path';
|
||||
|
||||
import { imagePathToArrayBuffer } from '../../util/imagePathToArrayBuffer';
|
||||
import { imagePathToBytes } from '../../util/imagePathToBytes';
|
||||
|
||||
describe('imagePathToArrayBuffer', () => {
|
||||
it('converts an image to an ArrayBuffer', async () => {
|
||||
describe('imagePathToBytes', () => {
|
||||
it('converts an image to an Bytes', async () => {
|
||||
const avatarPath = path.join(
|
||||
__dirname,
|
||||
'../../../fixtures/kitten-3-64-64.jpg'
|
||||
);
|
||||
const buffer = await imagePathToArrayBuffer(avatarPath);
|
||||
const buffer = await imagePathToBytes(avatarPath);
|
||||
assert.isDefined(buffer);
|
||||
assert(buffer instanceof ArrayBuffer);
|
||||
assert(buffer instanceof Uint8Array);
|
||||
});
|
||||
});
|
|
@ -6,7 +6,7 @@ import { assert } from 'chai';
|
|||
import * as Attachment from '../../types/Attachment';
|
||||
import * as MIME from '../../types/MIME';
|
||||
import { SignalService } from '../../protobuf';
|
||||
import { stringToArrayBuffer } from '../../util/stringToArrayBuffer';
|
||||
import * as Bytes from '../../Bytes';
|
||||
import * as logger from '../../logging/log';
|
||||
|
||||
describe('Attachment', () => {
|
||||
|
@ -38,7 +38,7 @@ describe('Attachment', () => {
|
|||
describe('getFileExtension', () => {
|
||||
it('should return file extension from content type', () => {
|
||||
const input: Attachment.AttachmentType = {
|
||||
data: stringToArrayBuffer('foo'),
|
||||
data: Bytes.fromString('foo'),
|
||||
contentType: MIME.IMAGE_GIF,
|
||||
};
|
||||
assert.strictEqual(Attachment.getFileExtension(input), 'gif');
|
||||
|
@ -46,7 +46,7 @@ describe('Attachment', () => {
|
|||
|
||||
it('should return file extension for QuickTime videos', () => {
|
||||
const input: Attachment.AttachmentType = {
|
||||
data: stringToArrayBuffer('foo'),
|
||||
data: Bytes.fromString('foo'),
|
||||
contentType: MIME.VIDEO_QUICKTIME,
|
||||
};
|
||||
assert.strictEqual(Attachment.getFileExtension(input), 'mov');
|
||||
|
@ -58,7 +58,7 @@ describe('Attachment', () => {
|
|||
it('should return existing filename if present', () => {
|
||||
const attachment: Attachment.AttachmentType = {
|
||||
fileName: 'funny-cat.mov',
|
||||
data: stringToArrayBuffer('foo'),
|
||||
data: Bytes.fromString('foo'),
|
||||
contentType: MIME.VIDEO_QUICKTIME,
|
||||
};
|
||||
const actual = Attachment.getSuggestedFilename({ attachment });
|
||||
|
@ -69,7 +69,7 @@ describe('Attachment', () => {
|
|||
context('for attachment without filename', () => {
|
||||
it('should generate a filename based on timestamp', () => {
|
||||
const attachment: Attachment.AttachmentType = {
|
||||
data: stringToArrayBuffer('foo'),
|
||||
data: Bytes.fromString('foo'),
|
||||
contentType: MIME.VIDEO_QUICKTIME,
|
||||
};
|
||||
const timestamp = new Date(new Date(0).getTimezoneOffset() * 60 * 1000);
|
||||
|
@ -84,7 +84,7 @@ describe('Attachment', () => {
|
|||
context('for attachment with index', () => {
|
||||
it('should generate a filename based on timestamp', () => {
|
||||
const attachment: Attachment.AttachmentType = {
|
||||
data: stringToArrayBuffer('foo'),
|
||||
data: Bytes.fromString('foo'),
|
||||
contentType: MIME.VIDEO_QUICKTIME,
|
||||
};
|
||||
const timestamp = new Date(new Date(0).getTimezoneOffset() * 60 * 1000);
|
||||
|
@ -103,7 +103,7 @@ describe('Attachment', () => {
|
|||
it('should return true for images', () => {
|
||||
const attachment: Attachment.AttachmentType = {
|
||||
fileName: 'meme.gif',
|
||||
data: stringToArrayBuffer('gif'),
|
||||
data: Bytes.fromString('gif'),
|
||||
contentType: MIME.IMAGE_GIF,
|
||||
};
|
||||
assert.isTrue(Attachment.isVisualMedia(attachment));
|
||||
|
@ -112,7 +112,7 @@ describe('Attachment', () => {
|
|||
it('should return true for videos', () => {
|
||||
const attachment: Attachment.AttachmentType = {
|
||||
fileName: 'meme.mp4',
|
||||
data: stringToArrayBuffer('mp4'),
|
||||
data: Bytes.fromString('mp4'),
|
||||
contentType: MIME.VIDEO_MP4,
|
||||
};
|
||||
assert.isTrue(Attachment.isVisualMedia(attachment));
|
||||
|
@ -122,7 +122,7 @@ describe('Attachment', () => {
|
|||
const attachment: Attachment.AttachmentType = {
|
||||
fileName: 'Voice Message.aac',
|
||||
flags: SignalService.AttachmentPointer.Flags.VOICE_MESSAGE,
|
||||
data: stringToArrayBuffer('voice message'),
|
||||
data: Bytes.fromString('voice message'),
|
||||
contentType: MIME.AUDIO_AAC,
|
||||
};
|
||||
assert.isFalse(Attachment.isVisualMedia(attachment));
|
||||
|
@ -131,7 +131,7 @@ describe('Attachment', () => {
|
|||
it('should return false for other attachments', () => {
|
||||
const attachment: Attachment.AttachmentType = {
|
||||
fileName: 'foo.json',
|
||||
data: stringToArrayBuffer('{"foo": "bar"}'),
|
||||
data: Bytes.fromString('{"foo": "bar"}'),
|
||||
contentType: MIME.APPLICATION_JSON,
|
||||
};
|
||||
assert.isFalse(Attachment.isVisualMedia(attachment));
|
||||
|
@ -142,7 +142,7 @@ describe('Attachment', () => {
|
|||
it('should return true for JSON', () => {
|
||||
const attachment: Attachment.AttachmentType = {
|
||||
fileName: 'foo.json',
|
||||
data: stringToArrayBuffer('{"foo": "bar"}'),
|
||||
data: Bytes.fromString('{"foo": "bar"}'),
|
||||
contentType: MIME.APPLICATION_JSON,
|
||||
};
|
||||
assert.isTrue(Attachment.isFile(attachment));
|
||||
|
@ -151,7 +151,7 @@ describe('Attachment', () => {
|
|||
it('should return false for images', () => {
|
||||
const attachment: Attachment.AttachmentType = {
|
||||
fileName: 'meme.gif',
|
||||
data: stringToArrayBuffer('gif'),
|
||||
data: Bytes.fromString('gif'),
|
||||
contentType: MIME.IMAGE_GIF,
|
||||
};
|
||||
assert.isFalse(Attachment.isFile(attachment));
|
||||
|
@ -160,7 +160,7 @@ describe('Attachment', () => {
|
|||
it('should return false for videos', () => {
|
||||
const attachment: Attachment.AttachmentType = {
|
||||
fileName: 'meme.mp4',
|
||||
data: stringToArrayBuffer('mp4'),
|
||||
data: Bytes.fromString('mp4'),
|
||||
contentType: MIME.VIDEO_MP4,
|
||||
};
|
||||
assert.isFalse(Attachment.isFile(attachment));
|
||||
|
@ -170,7 +170,7 @@ describe('Attachment', () => {
|
|||
const attachment: Attachment.AttachmentType = {
|
||||
fileName: 'Voice Message.aac',
|
||||
flags: SignalService.AttachmentPointer.Flags.VOICE_MESSAGE,
|
||||
data: stringToArrayBuffer('voice message'),
|
||||
data: Bytes.fromString('voice message'),
|
||||
contentType: MIME.AUDIO_AAC,
|
||||
};
|
||||
assert.isFalse(Attachment.isFile(attachment));
|
||||
|
@ -182,7 +182,7 @@ describe('Attachment', () => {
|
|||
const attachment: Attachment.AttachmentType = {
|
||||
fileName: 'Voice Message.aac',
|
||||
flags: SignalService.AttachmentPointer.Flags.VOICE_MESSAGE,
|
||||
data: stringToArrayBuffer('voice message'),
|
||||
data: Bytes.fromString('voice message'),
|
||||
contentType: MIME.AUDIO_AAC,
|
||||
};
|
||||
assert.isTrue(Attachment.isVoiceMessage(attachment));
|
||||
|
@ -190,7 +190,7 @@ describe('Attachment', () => {
|
|||
|
||||
it('should return true for legacy Android voice message attachment', () => {
|
||||
const attachment: Attachment.AttachmentType = {
|
||||
data: stringToArrayBuffer('voice message'),
|
||||
data: Bytes.fromString('voice message'),
|
||||
contentType: MIME.AUDIO_MP3,
|
||||
};
|
||||
assert.isTrue(Attachment.isVoiceMessage(attachment));
|
||||
|
@ -199,7 +199,7 @@ describe('Attachment', () => {
|
|||
it('should return false for other attachments', () => {
|
||||
const attachment: Attachment.AttachmentType = {
|
||||
fileName: 'foo.gif',
|
||||
data: stringToArrayBuffer('foo'),
|
||||
data: Bytes.fromString('foo'),
|
||||
contentType: MIME.IMAGE_GIF,
|
||||
};
|
||||
assert.isFalse(Attachment.isVoiceMessage(attachment));
|
||||
|
@ -359,7 +359,7 @@ describe('Attachment', () => {
|
|||
it('should write data to disk and store relative path to it', async () => {
|
||||
const input = {
|
||||
contentType: MIME.IMAGE_JPEG,
|
||||
data: stringToArrayBuffer('Above us only sky'),
|
||||
data: Bytes.fromString('Above us only sky'),
|
||||
fileName: 'foo.jpg',
|
||||
size: 1111,
|
||||
};
|
||||
|
@ -371,8 +371,8 @@ describe('Attachment', () => {
|
|||
size: 1111,
|
||||
};
|
||||
|
||||
const expectedAttachmentData = stringToArrayBuffer('Above us only sky');
|
||||
const writeNewAttachmentData = async (attachmentData: ArrayBuffer) => {
|
||||
const expectedAttachmentData = Bytes.fromString('Above us only sky');
|
||||
const writeNewAttachmentData = async (attachmentData: Uint8Array) => {
|
||||
assert.deepEqual(attachmentData, expectedAttachmentData);
|
||||
return 'abc/abcdefgh123456789';
|
||||
};
|
||||
|
@ -419,7 +419,7 @@ describe('Attachment', () => {
|
|||
Attachment.migrateDataToFileSystem(input, {
|
||||
writeNewAttachmentData,
|
||||
}),
|
||||
'Expected `attachment.data` to be an array buffer; got: number'
|
||||
'Expected `attachment.data` to be a typed array; got: number'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,7 +6,6 @@ import * as sinon from 'sinon';
|
|||
|
||||
import { IMAGE_GIF, IMAGE_PNG } from '../../types/MIME';
|
||||
import { MessageAttributesType } from '../../model-types.d';
|
||||
import { stringToArrayBuffer } from '../../util/stringToArrayBuffer';
|
||||
import {
|
||||
Avatar,
|
||||
Email,
|
||||
|
@ -400,7 +399,7 @@ describe('Contact', () => {
|
|||
otherKey: 'otherValue',
|
||||
avatar: {
|
||||
contentType: 'image/png',
|
||||
data: stringToArrayBuffer('It’s easy if you try'),
|
||||
data: Buffer.from('It’s easy if you try'),
|
||||
},
|
||||
} as unknown) as Avatar,
|
||||
},
|
||||
|
|
|
@ -7,7 +7,7 @@ import * as Message from '../../../types/message/initializeAttachmentMetadata';
|
|||
import { IncomingMessage } from '../../../types/Message';
|
||||
import { SignalService } from '../../../protobuf';
|
||||
import * as MIME from '../../../types/MIME';
|
||||
import { stringToArrayBuffer } from '../../../util/stringToArrayBuffer';
|
||||
import * as Bytes from '../../../Bytes';
|
||||
|
||||
describe('Message', () => {
|
||||
describe('initializeAttachmentMetadata', () => {
|
||||
|
@ -22,7 +22,7 @@ describe('Message', () => {
|
|||
attachments: [
|
||||
{
|
||||
contentType: MIME.IMAGE_JPEG,
|
||||
data: stringToArrayBuffer('foo'),
|
||||
data: Bytes.fromString('foo'),
|
||||
fileName: 'foo.jpg',
|
||||
size: 1111,
|
||||
},
|
||||
|
@ -38,7 +38,7 @@ describe('Message', () => {
|
|||
attachments: [
|
||||
{
|
||||
contentType: MIME.IMAGE_JPEG,
|
||||
data: stringToArrayBuffer('foo'),
|
||||
data: Bytes.fromString('foo'),
|
||||
fileName: 'foo.jpg',
|
||||
size: 1111,
|
||||
},
|
||||
|
@ -63,7 +63,7 @@ describe('Message', () => {
|
|||
attachments: [
|
||||
{
|
||||
contentType: MIME.APPLICATION_OCTET_STREAM,
|
||||
data: stringToArrayBuffer('foo'),
|
||||
data: Bytes.fromString('foo'),
|
||||
fileName: 'foo.bin',
|
||||
size: 1111,
|
||||
},
|
||||
|
@ -79,7 +79,7 @@ describe('Message', () => {
|
|||
attachments: [
|
||||
{
|
||||
contentType: MIME.APPLICATION_OCTET_STREAM,
|
||||
data: stringToArrayBuffer('foo'),
|
||||
data: Bytes.fromString('foo'),
|
||||
fileName: 'foo.bin',
|
||||
size: 1111,
|
||||
},
|
||||
|
@ -105,7 +105,7 @@ describe('Message', () => {
|
|||
{
|
||||
contentType: MIME.AUDIO_AAC,
|
||||
flags: SignalService.AttachmentPointer.Flags.VOICE_MESSAGE,
|
||||
data: stringToArrayBuffer('foo'),
|
||||
data: Bytes.fromString('foo'),
|
||||
fileName: 'Voice Message.aac',
|
||||
size: 1111,
|
||||
},
|
||||
|
@ -122,7 +122,7 @@ describe('Message', () => {
|
|||
{
|
||||
contentType: MIME.AUDIO_AAC,
|
||||
flags: SignalService.AttachmentPointer.Flags.VOICE_MESSAGE,
|
||||
data: stringToArrayBuffer('foo'),
|
||||
data: Bytes.fromString('foo'),
|
||||
fileName: 'Voice Message.aac',
|
||||
size: 1111,
|
||||
},
|
||||
|
@ -147,7 +147,7 @@ describe('Message', () => {
|
|||
attachments: [
|
||||
{
|
||||
contentType: MIME.LONG_MESSAGE,
|
||||
data: stringToArrayBuffer('foo'),
|
||||
data: Bytes.fromString('foo'),
|
||||
fileName: 'message.txt',
|
||||
size: 1111,
|
||||
},
|
||||
|
@ -163,7 +163,7 @@ describe('Message', () => {
|
|||
attachments: [
|
||||
{
|
||||
contentType: MIME.LONG_MESSAGE,
|
||||
data: stringToArrayBuffer('foo'),
|
||||
data: Bytes.fromString('foo'),
|
||||
fileName: 'message.txt',
|
||||
size: 1111,
|
||||
},
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
import { assert } from 'chai';
|
||||
import { size } from '../../util/iterables';
|
||||
|
||||
import { typedArrayToArrayBuffer } from '../../Crypto';
|
||||
|
||||
import { getProvisioningUrl } from '../../util/getProvisioningUrl';
|
||||
|
||||
// It'd be nice to run these tests in the renderer, too, but [Chromium's `URL` doesn't
|
||||
|
@ -17,7 +15,7 @@ describe('getProvisioningUrl', () => {
|
|||
const uuid = 'a08bf1fd-1799-427f-a551-70af747e3956';
|
||||
const publicKey = new Uint8Array([9, 8, 7, 6, 5, 4, 3]);
|
||||
|
||||
const result = getProvisioningUrl(uuid, typedArrayToArrayBuffer(publicKey));
|
||||
const result = getProvisioningUrl(uuid, publicKey);
|
||||
const resultUrl = new URL(result);
|
||||
|
||||
assert.strictEqual(resultUrl.protocol, 'sgnl:');
|
||||
|
|
|
@ -13,8 +13,6 @@ import {
|
|||
IMAGE_WEBP,
|
||||
} from '../../types/MIME';
|
||||
|
||||
import { typedArrayToArrayBuffer } from '../../Crypto';
|
||||
|
||||
import { sniffImageMimeType } from '../../util/sniffImageMimeType';
|
||||
|
||||
describe('sniffImageMimeType', () => {
|
||||
|
@ -89,11 +87,4 @@ describe('sniffImageMimeType', () => {
|
|||
IMAGE_JPEG
|
||||
);
|
||||
});
|
||||
|
||||
it('handles ArrayBuffers', async () => {
|
||||
const arrayBuffer = typedArrayToArrayBuffer(
|
||||
await fixture('kitten-1-64-64.jpg')
|
||||
);
|
||||
assert.strictEqual(sniffImageMimeType(arrayBuffer), IMAGE_JPEG);
|
||||
});
|
||||
});
|
||||
|
|
2
ts/textsecure.d.ts
vendored
2
ts/textsecure.d.ts
vendored
|
@ -3,7 +3,6 @@
|
|||
|
||||
import { UnidentifiedSenderMessageContent } from '@signalapp/signal-client';
|
||||
|
||||
import Crypto from './textsecure/Crypto';
|
||||
import MessageSender from './textsecure/SendMessage';
|
||||
import SyncRequest from './textsecure/SyncRequest';
|
||||
import EventTarget from './textsecure/EventTarget';
|
||||
|
@ -37,7 +36,6 @@ export type UnprocessedType = {
|
|||
export { StorageServiceCallOptionsType, StorageServiceCredentials };
|
||||
|
||||
export type TextSecureType = {
|
||||
crypto: typeof Crypto;
|
||||
storage: Storage;
|
||||
server: WebAPIType;
|
||||
messageSender: MessageSender;
|
||||
|
|
|
@ -13,7 +13,6 @@ import EventTarget from './EventTarget';
|
|||
import type { WebAPIType } from './WebAPI';
|
||||
import { HTTPError } from './Errors';
|
||||
import { KeyPairType, CompatSignedPreKeyType } from './Types.d';
|
||||
import utils from './Helpers';
|
||||
import ProvisioningCipher from './ProvisioningCipher';
|
||||
import { IncomingWebSocketRequest } from './WebsocketResources';
|
||||
import createTaskWithTimeout from './TaskWithTimeout';
|
||||
|
@ -22,8 +21,8 @@ import {
|
|||
deriveAccessKey,
|
||||
generateRegistrationId,
|
||||
getRandomBytes,
|
||||
typedArrayToArrayBuffer,
|
||||
arrayBufferToBase64,
|
||||
decryptDeviceName,
|
||||
encryptDeviceName,
|
||||
} from '../Crypto';
|
||||
import {
|
||||
generateKeyPair,
|
||||
|
@ -45,9 +44,6 @@ const PREKEY_ROTATION_AGE = DAY * 1.5;
|
|||
const PROFILE_KEY_LENGTH = 32;
|
||||
const SIGNED_KEY_GEN_BATCH_SIZE = 100;
|
||||
|
||||
// TODO: remove once we move away from ArrayBuffers
|
||||
const FIXMEU8 = Uint8Array;
|
||||
|
||||
function getIdentifier(id: string | undefined) {
|
||||
if (!id || !id.length) {
|
||||
return id;
|
||||
|
@ -64,15 +60,15 @@ function getIdentifier(id: string | undefined) {
|
|||
export type GeneratedKeysType = {
|
||||
preKeys: Array<{
|
||||
keyId: number;
|
||||
publicKey: ArrayBuffer;
|
||||
publicKey: Uint8Array;
|
||||
}>;
|
||||
signedPreKey: {
|
||||
keyId: number;
|
||||
publicKey: ArrayBuffer;
|
||||
signature: ArrayBuffer;
|
||||
publicKey: Uint8Array;
|
||||
signature: Uint8Array;
|
||||
keyPair: KeyPairType;
|
||||
};
|
||||
identityKey: ArrayBuffer;
|
||||
identityKey: Uint8Array;
|
||||
};
|
||||
|
||||
export default class AccountManager extends EventTarget {
|
||||
|
@ -94,19 +90,16 @@ export default class AccountManager extends EventTarget {
|
|||
return this.server.requestVerificationSMS(number);
|
||||
}
|
||||
|
||||
async encryptDeviceName(name: string, identityKey: KeyPairType) {
|
||||
encryptDeviceName(name: string, identityKey: KeyPairType) {
|
||||
if (!name) {
|
||||
return null;
|
||||
}
|
||||
const encrypted = await window.Signal.Crypto.encryptDeviceName(
|
||||
name,
|
||||
identityKey.pubKey
|
||||
);
|
||||
const encrypted = encryptDeviceName(name, identityKey.pubKey);
|
||||
|
||||
const proto = new Proto.DeviceName();
|
||||
proto.ephemeralPublic = new FIXMEU8(encrypted.ephemeralPublic);
|
||||
proto.syntheticIv = new FIXMEU8(encrypted.syntheticIv);
|
||||
proto.ciphertext = new FIXMEU8(encrypted.ciphertext);
|
||||
proto.ephemeralPublic = encrypted.ephemeralPublic;
|
||||
proto.syntheticIv = encrypted.syntheticIv;
|
||||
proto.ciphertext = encrypted.ciphertext;
|
||||
|
||||
const bytes = Proto.DeviceName.encode(proto).finish();
|
||||
return Bytes.toBase64(bytes);
|
||||
|
@ -127,16 +120,8 @@ export default class AccountManager extends EventTarget {
|
|||
proto.ephemeralPublic && proto.syntheticIv && proto.ciphertext,
|
||||
'Missing required fields in DeviceName'
|
||||
);
|
||||
const encrypted = {
|
||||
ephemeralPublic: typedArrayToArrayBuffer(proto.ephemeralPublic),
|
||||
syntheticIv: typedArrayToArrayBuffer(proto.syntheticIv),
|
||||
ciphertext: typedArrayToArrayBuffer(proto.ciphertext),
|
||||
};
|
||||
|
||||
const name = await window.Signal.Crypto.decryptDeviceName(
|
||||
encrypted,
|
||||
identityKey.privKey
|
||||
);
|
||||
const name = decryptDeviceName(proto, identityKey.privKey);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
@ -155,10 +140,7 @@ export default class AccountManager extends EventTarget {
|
|||
identityKeyPair !== undefined,
|
||||
"Can't encrypt device name without identity key pair"
|
||||
);
|
||||
const base64 = await this.encryptDeviceName(
|
||||
deviceName || '',
|
||||
identityKeyPair
|
||||
);
|
||||
const base64 = this.encryptDeviceName(deviceName || '', identityKeyPair);
|
||||
|
||||
if (base64) {
|
||||
await this.server.updateDeviceName(base64);
|
||||
|
@ -173,7 +155,7 @@ export default class AccountManager extends EventTarget {
|
|||
return this.queueTask(async () => {
|
||||
const identityKeyPair = generateKeyPair();
|
||||
const profileKey = getRandomBytes(PROFILE_KEY_LENGTH);
|
||||
const accessKey = await deriveAccessKey(profileKey);
|
||||
const accessKey = deriveAccessKey(profileKey);
|
||||
|
||||
await this.createAccount(
|
||||
number,
|
||||
|
@ -469,15 +451,15 @@ export default class AccountManager extends EventTarget {
|
|||
number: string,
|
||||
verificationCode: string,
|
||||
identityKeyPair: KeyPairType,
|
||||
profileKey: ArrayBuffer | undefined,
|
||||
profileKey: Uint8Array | undefined,
|
||||
deviceName: string | null,
|
||||
userAgent?: string | null,
|
||||
readReceipts?: boolean | null,
|
||||
options: { accessKey?: ArrayBuffer; uuid?: string } = {}
|
||||
options: { accessKey?: Uint8Array; uuid?: string } = {}
|
||||
): Promise<void> {
|
||||
const { storage } = window.textsecure;
|
||||
const { accessKey, uuid } = options;
|
||||
let password = btoa(utils.getString(getRandomBytes(16)));
|
||||
let password = Bytes.toBase64(getRandomBytes(16));
|
||||
password = password.substring(0, password.length - 2);
|
||||
const registrationId = generateRegistrationId();
|
||||
|
||||
|
@ -486,10 +468,7 @@ export default class AccountManager extends EventTarget {
|
|||
|
||||
let encryptedDeviceName;
|
||||
if (deviceName) {
|
||||
encryptedDeviceName = await this.encryptDeviceName(
|
||||
deviceName,
|
||||
identityKeyPair
|
||||
);
|
||||
encryptedDeviceName = this.encryptDeviceName(deviceName, identityKeyPair);
|
||||
await this.deviceNameIsEncrypted();
|
||||
}
|
||||
|
||||
|
@ -601,8 +580,8 @@ export default class AccountManager extends EventTarget {
|
|||
const identityKeyMap = {
|
||||
...(storage.get('identityKeyMap') || {}),
|
||||
[ourUuid]: {
|
||||
pubKey: arrayBufferToBase64(identityKeyPair.pubKey),
|
||||
privKey: arrayBufferToBase64(identityKeyPair.privKey),
|
||||
pubKey: Bytes.toBase64(identityKeyPair.pubKey),
|
||||
privKey: Bytes.toBase64(identityKeyPair.privKey),
|
||||
},
|
||||
};
|
||||
const registrationIdMap = {
|
||||
|
|
|
@ -7,7 +7,6 @@ import { Reader } from 'protobufjs';
|
|||
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
import { normalizeUuid } from '../util/normalizeUuid';
|
||||
import { typedArrayToArrayBuffer } from '../Crypto';
|
||||
import * as log from '../logging/log';
|
||||
|
||||
import Avatar = Proto.ContactDetails.IAvatar;
|
||||
|
@ -22,24 +21,21 @@ export type MessageWithAvatar<Message extends OptionalAvatar> = Omit<
|
|||
Message,
|
||||
'avatar'
|
||||
> & {
|
||||
avatar?: (Avatar & { data: ArrayBuffer }) | null;
|
||||
avatar?: (Avatar & { data: Uint8Array }) | null;
|
||||
};
|
||||
|
||||
export type ModifiedGroupDetails = MessageWithAvatar<Proto.GroupDetails>;
|
||||
|
||||
export type ModifiedContactDetails = MessageWithAvatar<Proto.ContactDetails>;
|
||||
|
||||
// TODO: remove once we move away from ArrayBuffers
|
||||
const FIXMEU8 = Uint8Array;
|
||||
|
||||
class ParserBase<
|
||||
Message extends OptionalAvatar,
|
||||
Decoder extends DecoderBase<Message>
|
||||
> {
|
||||
protected readonly reader: Reader;
|
||||
|
||||
constructor(arrayBuffer: ArrayBuffer, private readonly decoder: Decoder) {
|
||||
this.reader = new Reader(new FIXMEU8(arrayBuffer));
|
||||
constructor(bytes: Uint8Array, private readonly decoder: Decoder) {
|
||||
this.reader = new Reader(bytes);
|
||||
}
|
||||
|
||||
protected decodeDelimited(): MessageWithAvatar<Message> | undefined {
|
||||
|
@ -74,7 +70,7 @@ class ParserBase<
|
|||
avatar: {
|
||||
...proto.avatar,
|
||||
|
||||
data: typedArrayToArrayBuffer(avatarData),
|
||||
data: avatarData,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
|
@ -91,7 +87,7 @@ export class GroupBuffer extends ParserBase<
|
|||
Proto.GroupDetails,
|
||||
typeof Proto.GroupDetails
|
||||
> {
|
||||
constructor(arrayBuffer: ArrayBuffer) {
|
||||
constructor(arrayBuffer: Uint8Array) {
|
||||
super(arrayBuffer, Proto.GroupDetails);
|
||||
}
|
||||
|
||||
|
@ -124,7 +120,7 @@ export class ContactBuffer extends ParserBase<
|
|||
Proto.ContactDetails,
|
||||
typeof Proto.ContactDetails
|
||||
> {
|
||||
constructor(arrayBuffer: ArrayBuffer) {
|
||||
constructor(arrayBuffer: Uint8Array) {
|
||||
super(arrayBuffer, Proto.ContactDetails);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,255 +0,0 @@
|
|||
// Copyright 2020-2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable no-bitwise */
|
||||
/* eslint-disable more/no-then */
|
||||
import {
|
||||
decryptAes256CbcPkcsPadding,
|
||||
encryptAes256CbcPkcsPadding,
|
||||
getRandomBytes as outerGetRandomBytes,
|
||||
hmacSha256,
|
||||
sha256,
|
||||
verifyHmacSha256,
|
||||
base64ToArrayBuffer,
|
||||
typedArrayToArrayBuffer,
|
||||
} from '../Crypto';
|
||||
|
||||
const PROFILE_IV_LENGTH = 12; // bytes
|
||||
const PROFILE_KEY_LENGTH = 32; // bytes
|
||||
const PROFILE_TAG_LENGTH = 128; // bits
|
||||
|
||||
// bytes
|
||||
export const PaddedLengths = {
|
||||
Name: [53, 257],
|
||||
About: [128, 254, 512],
|
||||
AboutEmoji: [32],
|
||||
PaymentAddress: [554],
|
||||
};
|
||||
|
||||
type EncryptedAttachment = {
|
||||
ciphertext: ArrayBuffer;
|
||||
digest: ArrayBuffer;
|
||||
};
|
||||
|
||||
async function verifyDigest(
|
||||
data: ArrayBuffer,
|
||||
theirDigest: ArrayBuffer
|
||||
): Promise<void> {
|
||||
return window.crypto.subtle
|
||||
.digest({ name: 'SHA-256' }, data)
|
||||
.then(ourDigest => {
|
||||
const a = new Uint8Array(ourDigest);
|
||||
const b = new Uint8Array(theirDigest);
|
||||
let result = 0;
|
||||
for (let i = 0; i < theirDigest.byteLength; i += 1) {
|
||||
result |= a[i] ^ b[i];
|
||||
}
|
||||
if (result !== 0) {
|
||||
throw new Error('Bad digest');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const Crypto = {
|
||||
async decryptAttachment(
|
||||
encryptedBin: ArrayBuffer,
|
||||
keys: ArrayBuffer,
|
||||
theirDigest?: ArrayBuffer
|
||||
): Promise<ArrayBuffer> {
|
||||
if (keys.byteLength !== 64) {
|
||||
throw new Error('Got invalid length attachment keys');
|
||||
}
|
||||
if (encryptedBin.byteLength < 16 + 32) {
|
||||
throw new Error('Got invalid length attachment');
|
||||
}
|
||||
|
||||
const aesKey = keys.slice(0, 32);
|
||||
const macKey = keys.slice(32, 64);
|
||||
|
||||
const iv = encryptedBin.slice(0, 16);
|
||||
const ciphertext = encryptedBin.slice(16, encryptedBin.byteLength - 32);
|
||||
const ivAndCiphertext = encryptedBin.slice(0, encryptedBin.byteLength - 32);
|
||||
const mac = encryptedBin.slice(
|
||||
encryptedBin.byteLength - 32,
|
||||
encryptedBin.byteLength
|
||||
);
|
||||
|
||||
await verifyHmacSha256(ivAndCiphertext, macKey, mac, 32);
|
||||
|
||||
if (theirDigest) {
|
||||
await verifyDigest(encryptedBin, theirDigest);
|
||||
}
|
||||
|
||||
return decryptAes256CbcPkcsPadding(aesKey, ciphertext, iv);
|
||||
},
|
||||
|
||||
async encryptAttachment(
|
||||
plaintext: ArrayBuffer,
|
||||
keys: ArrayBuffer,
|
||||
iv: ArrayBuffer
|
||||
): Promise<EncryptedAttachment> {
|
||||
if (!(plaintext instanceof ArrayBuffer) && !ArrayBuffer.isView(plaintext)) {
|
||||
throw new TypeError(
|
||||
`\`plaintext\` must be an \`ArrayBuffer\` or \`ArrayBufferView\`; got: ${typeof plaintext}`
|
||||
);
|
||||
}
|
||||
|
||||
if (keys.byteLength !== 64) {
|
||||
throw new Error('Got invalid length attachment keys');
|
||||
}
|
||||
if (iv.byteLength !== 16) {
|
||||
throw new Error('Got invalid length attachment iv');
|
||||
}
|
||||
const aesKey = keys.slice(0, 32);
|
||||
const macKey = keys.slice(32, 64);
|
||||
|
||||
const ciphertext = await encryptAes256CbcPkcsPadding(aesKey, plaintext, iv);
|
||||
|
||||
const ivAndCiphertext = new Uint8Array(16 + ciphertext.byteLength);
|
||||
ivAndCiphertext.set(new Uint8Array(iv));
|
||||
ivAndCiphertext.set(new Uint8Array(ciphertext), 16);
|
||||
|
||||
const mac = await hmacSha256(macKey, ivAndCiphertext.buffer as ArrayBuffer);
|
||||
|
||||
const encryptedBin = new Uint8Array(16 + ciphertext.byteLength + 32);
|
||||
encryptedBin.set(ivAndCiphertext);
|
||||
encryptedBin.set(new Uint8Array(mac), 16 + ciphertext.byteLength);
|
||||
const digest = await sha256(encryptedBin.buffer as ArrayBuffer);
|
||||
|
||||
return {
|
||||
ciphertext: encryptedBin.buffer,
|
||||
digest,
|
||||
};
|
||||
},
|
||||
|
||||
async encryptProfile(
|
||||
data: ArrayBuffer,
|
||||
key: ArrayBuffer
|
||||
): Promise<ArrayBuffer> {
|
||||
const iv = outerGetRandomBytes(PROFILE_IV_LENGTH);
|
||||
if (key.byteLength !== PROFILE_KEY_LENGTH) {
|
||||
throw new Error('Got invalid length profile key');
|
||||
}
|
||||
if (iv.byteLength !== PROFILE_IV_LENGTH) {
|
||||
throw new Error('Got invalid length profile iv');
|
||||
}
|
||||
return window.crypto.subtle
|
||||
.importKey('raw', key, { name: 'AES-GCM' } as any, false, ['encrypt'])
|
||||
.then(async keyForEncryption =>
|
||||
window.crypto.subtle
|
||||
.encrypt(
|
||||
{ name: 'AES-GCM', iv, tagLength: PROFILE_TAG_LENGTH },
|
||||
keyForEncryption,
|
||||
data
|
||||
)
|
||||
.then(ciphertext => {
|
||||
const ivAndCiphertext = new Uint8Array(
|
||||
PROFILE_IV_LENGTH + ciphertext.byteLength
|
||||
);
|
||||
ivAndCiphertext.set(new Uint8Array(iv));
|
||||
ivAndCiphertext.set(new Uint8Array(ciphertext), PROFILE_IV_LENGTH);
|
||||
return ivAndCiphertext.buffer;
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
async decryptProfile(
|
||||
data: ArrayBuffer,
|
||||
key: ArrayBuffer
|
||||
): Promise<ArrayBuffer> {
|
||||
if (data.byteLength < 12 + 16 + 1) {
|
||||
throw new Error(`Got too short input: ${data.byteLength}`);
|
||||
}
|
||||
const iv = data.slice(0, PROFILE_IV_LENGTH);
|
||||
const ciphertext = data.slice(PROFILE_IV_LENGTH, data.byteLength);
|
||||
if (key.byteLength !== PROFILE_KEY_LENGTH) {
|
||||
throw new Error('Got invalid length profile key');
|
||||
}
|
||||
if (iv.byteLength !== PROFILE_IV_LENGTH) {
|
||||
throw new Error('Got invalid length profile iv');
|
||||
}
|
||||
const error = new Error(); // save stack
|
||||
return window.crypto.subtle
|
||||
.importKey('raw', key, { name: 'AES-GCM' } as any, false, ['decrypt'])
|
||||
.then(async keyForEncryption =>
|
||||
window.crypto.subtle
|
||||
.decrypt(
|
||||
{ name: 'AES-GCM', iv, tagLength: PROFILE_TAG_LENGTH },
|
||||
keyForEncryption,
|
||||
ciphertext
|
||||
)
|
||||
.catch((e: Error) => {
|
||||
if (e.name === 'OperationError') {
|
||||
// bad mac, basically.
|
||||
error.message =
|
||||
'Failed to decrypt profile data. Most likely the profile key has changed.';
|
||||
error.name = 'ProfileDecryptError';
|
||||
throw error;
|
||||
}
|
||||
|
||||
return (undefined as unknown) as ArrayBuffer; // uses of this function are not guarded
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
async encryptProfileItemWithPadding(
|
||||
item: ArrayBuffer,
|
||||
profileKey: ArrayBuffer,
|
||||
paddedLengths: typeof PaddedLengths[keyof typeof PaddedLengths]
|
||||
): Promise<ArrayBuffer> {
|
||||
const paddedLength = paddedLengths.find(
|
||||
(length: number) => item.byteLength <= length
|
||||
);
|
||||
if (!paddedLength) {
|
||||
throw new Error('Oversized value');
|
||||
}
|
||||
const padded = new Uint8Array(paddedLength);
|
||||
padded.set(new Uint8Array(item));
|
||||
return Crypto.encryptProfile(padded.buffer as ArrayBuffer, profileKey);
|
||||
},
|
||||
|
||||
async decryptProfileName(
|
||||
encryptedProfileName: string,
|
||||
key: ArrayBuffer
|
||||
): Promise<{ given: ArrayBuffer; family: ArrayBuffer | null }> {
|
||||
const data = base64ToArrayBuffer(encryptedProfileName);
|
||||
return Crypto.decryptProfile(data, key).then(decrypted => {
|
||||
const padded = new Uint8Array(decrypted);
|
||||
|
||||
// Given name is the start of the string to the first null character
|
||||
let givenEnd;
|
||||
for (givenEnd = 0; givenEnd < padded.length; givenEnd += 1) {
|
||||
if (padded[givenEnd] === 0x00) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Family name is the next chunk of non-null characters after that first null
|
||||
let familyEnd;
|
||||
for (
|
||||
familyEnd = givenEnd + 1;
|
||||
familyEnd < padded.length;
|
||||
familyEnd += 1
|
||||
) {
|
||||
if (padded[familyEnd] === 0x00) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const foundFamilyName = familyEnd > givenEnd + 1;
|
||||
|
||||
return {
|
||||
given: typedArrayToArrayBuffer(padded.slice(0, givenEnd)),
|
||||
family: foundFamilyName
|
||||
? typedArrayToArrayBuffer(padded.slice(givenEnd + 1, familyEnd))
|
||||
: null,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
getRandomBytes(size: number): ArrayBuffer {
|
||||
return outerGetRandomBytes(size);
|
||||
},
|
||||
};
|
||||
|
||||
export default Crypto;
|
|
@ -77,14 +77,14 @@ export class ReplayableError extends Error {
|
|||
export class OutgoingIdentityKeyError extends ReplayableError {
|
||||
identifier: string;
|
||||
|
||||
identityKey: ArrayBuffer;
|
||||
identityKey: Uint8Array;
|
||||
|
||||
// Note: Data to resend message is no longer captured
|
||||
constructor(
|
||||
incomingIdentifier: string,
|
||||
_m: ArrayBuffer,
|
||||
_m: Uint8Array,
|
||||
_t: number,
|
||||
identityKey: ArrayBuffer
|
||||
identityKey: Uint8Array
|
||||
) {
|
||||
const identifier = incomingIdentifier.split('.')[0];
|
||||
|
||||
|
@ -188,7 +188,7 @@ export class SendMessageProtoError extends Error implements CallbackResultType {
|
|||
|
||||
public readonly unidentifiedDeliveries?: Array<string>;
|
||||
|
||||
public readonly dataMessage?: ArrayBuffer;
|
||||
public readonly dataMessage?: Uint8Array;
|
||||
|
||||
// Fields necesary for send log save
|
||||
public readonly contentHint?: number;
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { stringToArrayBuffer } from '../util/stringToArrayBuffer';
|
||||
|
||||
/* eslint-disable guard-for-in */
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
/* eslint-disable no-proto */
|
||||
|
@ -71,7 +69,6 @@ const utils = {
|
|||
number[0] === '+' && /^[0-9]+$/.test(number.substring(1)),
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
jsonThing: (thing: unknown) => JSON.stringify(ensureStringed(thing)),
|
||||
stringToArrayBuffer,
|
||||
unencodeNumber: (number: string): Array<string> => number.split('.'),
|
||||
};
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ import { normalizeUuid } from '../util/normalizeUuid';
|
|||
import { normalizeNumber } from '../util/normalizeNumber';
|
||||
import { parseIntOrThrow } from '../util/parseIntOrThrow';
|
||||
import { Zone } from '../util/Zone';
|
||||
import { deriveMasterKeyFromGroupV1, typedArrayToArrayBuffer } from '../Crypto';
|
||||
import { deriveMasterKeyFromGroupV1 } from '../Crypto';
|
||||
import { DownloadedAttachmentType } from '../types/Attachment';
|
||||
import { Address } from '../types/Address';
|
||||
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||
|
@ -103,9 +103,6 @@ import {
|
|||
} from './messageReceiverEvents';
|
||||
import * as log from '../logging/log';
|
||||
|
||||
// TODO: remove once we move away from ArrayBuffers
|
||||
const FIXMEU8 = Uint8Array;
|
||||
|
||||
const GROUPV1_ID_LENGTH = 16;
|
||||
const GROUPV2_ID_LENGTH = 32;
|
||||
const RETRY_TIMEOUT = 2 * 60 * 1000;
|
||||
|
@ -1137,9 +1134,9 @@ export default class MessageReceiver
|
|||
|
||||
if (
|
||||
!verifySignature(
|
||||
typedArrayToArrayBuffer(this.serverTrustRoot),
|
||||
typedArrayToArrayBuffer(serverCertificate.certificateData()),
|
||||
typedArrayToArrayBuffer(serverCertificate.signature())
|
||||
this.serverTrustRoot,
|
||||
serverCertificate.certificateData(),
|
||||
serverCertificate.signature()
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
|
@ -1150,9 +1147,9 @@ export default class MessageReceiver
|
|||
|
||||
if (
|
||||
!verifySignature(
|
||||
typedArrayToArrayBuffer(serverCertificate.key().serialize()),
|
||||
typedArrayToArrayBuffer(certificate.certificate()),
|
||||
typedArrayToArrayBuffer(certificate.signature())
|
||||
serverCertificate.key().serialize(),
|
||||
certificate.certificate(),
|
||||
certificate.signature()
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
|
@ -1448,9 +1445,7 @@ export default class MessageReceiver
|
|||
ciphertext: Uint8Array
|
||||
): Promise<Uint8Array | undefined> {
|
||||
try {
|
||||
const plaintext = await this.innerDecrypt(stores, envelope, ciphertext);
|
||||
|
||||
return new FIXMEU8(plaintext);
|
||||
return await this.innerDecrypt(stores, envelope, ciphertext);
|
||||
} catch (error) {
|
||||
this.removeFromCache(envelope);
|
||||
const uuid = envelope.sourceUuid;
|
||||
|
@ -1486,9 +1481,7 @@ export default class MessageReceiver
|
|||
if (uuid && deviceId) {
|
||||
const { usmc } = envelope;
|
||||
const event = new DecryptionErrorEvent({
|
||||
cipherTextBytes: usmc
|
||||
? typedArrayToArrayBuffer(usmc.contents())
|
||||
: undefined,
|
||||
cipherTextBytes: usmc ? usmc.contents() : undefined,
|
||||
cipherTextType: usmc ? usmc.msgType() : undefined,
|
||||
contentHint: envelope.contentHint,
|
||||
groupId: envelope.groupId,
|
||||
|
@ -1955,7 +1948,7 @@ export default class MessageReceiver
|
|||
if (groupId && groupId.byteLength > 0) {
|
||||
if (groupId.byteLength === GROUPV1_ID_LENGTH) {
|
||||
groupIdString = Bytes.toBinary(groupId);
|
||||
groupV2IdString = await this.deriveGroupV2FromV1(groupId);
|
||||
groupV2IdString = this.deriveGroupV2FromV1(groupId);
|
||||
} else if (groupId.byteLength === GROUPV2_ID_LENGTH) {
|
||||
groupV2IdString = Bytes.toBase64(groupId);
|
||||
} else {
|
||||
|
@ -2024,16 +2017,14 @@ export default class MessageReceiver
|
|||
return false;
|
||||
}
|
||||
|
||||
private async deriveGroupV2FromV1(groupId: Uint8Array): Promise<string> {
|
||||
private deriveGroupV2FromV1(groupId: Uint8Array): string {
|
||||
if (groupId.byteLength !== GROUPV1_ID_LENGTH) {
|
||||
throw new Error(
|
||||
`deriveGroupV2FromV1: had id with wrong byteLength: ${groupId.byteLength}`
|
||||
);
|
||||
}
|
||||
const masterKey = await deriveMasterKeyFromGroupV1(
|
||||
typedArrayToArrayBuffer(groupId)
|
||||
);
|
||||
const data = deriveGroupFields(new FIXMEU8(masterKey));
|
||||
const masterKey = deriveMasterKeyFromGroupV1(groupId);
|
||||
const data = deriveGroupFields(masterKey);
|
||||
|
||||
return Bytes.toBase64(data.id);
|
||||
}
|
||||
|
@ -2246,7 +2237,7 @@ export default class MessageReceiver
|
|||
if (groupId && groupId.byteLength > 0) {
|
||||
if (groupId.byteLength === GROUPV1_ID_LENGTH) {
|
||||
groupIdString = Bytes.toBinary(groupId);
|
||||
groupV2IdString = await this.deriveGroupV2FromV1(groupId);
|
||||
groupV2IdString = this.deriveGroupV2FromV1(groupId);
|
||||
} else if (groupId.byteLength === GROUPV2_ID_LENGTH) {
|
||||
groupV2IdString = Bytes.toBase64(groupId);
|
||||
} else {
|
||||
|
@ -2300,7 +2291,7 @@ export default class MessageReceiver
|
|||
}
|
||||
|
||||
const ev = new KeysEvent(
|
||||
typedArrayToArrayBuffer(sync.storageService),
|
||||
sync.storageService,
|
||||
this.removeFromCache.bind(this, envelope)
|
||||
);
|
||||
|
||||
|
@ -2343,9 +2334,7 @@ export default class MessageReceiver
|
|||
'handleVerified.destinationUuid'
|
||||
)
|
||||
: undefined,
|
||||
identityKey: verified.identityKey
|
||||
? typedArrayToArrayBuffer(verified.identityKey)
|
||||
: undefined,
|
||||
identityKey: verified.identityKey ? verified.identityKey : undefined,
|
||||
},
|
||||
this.removeFromCache.bind(this, envelope)
|
||||
);
|
||||
|
|
|
@ -37,7 +37,6 @@ import { Address } from '../types/Address';
|
|||
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||
import { UUID } from '../types/UUID';
|
||||
import { Sessions, IdentityKeys } from '../LibSignalStores';
|
||||
import { typedArrayToArrayBuffer as toArrayBuffer } from '../Crypto';
|
||||
import { updateConversationsWithUuidLookup } from '../updateConversationsWithUuidLookup';
|
||||
import { getKeysForIdentifier } from './getKeysForIdentifier';
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
|
@ -63,7 +62,7 @@ type SendMetadata = {
|
|||
export const serializedCertificateSchema = z
|
||||
.object({
|
||||
expires: z.number().optional(),
|
||||
serialized: z.instanceof(ArrayBuffer),
|
||||
serialized: z.instanceof(Uint8Array),
|
||||
})
|
||||
.nonstrict();
|
||||
|
||||
|
@ -331,7 +330,7 @@ export default class OutgoingMessage {
|
|||
});
|
||||
}
|
||||
|
||||
getPlaintext(): ArrayBuffer {
|
||||
getPlaintext(): Uint8Array {
|
||||
if (!this.plaintext) {
|
||||
const { message } = this;
|
||||
|
||||
|
@ -341,7 +340,7 @@ export default class OutgoingMessage {
|
|||
this.plaintext = message.serialize();
|
||||
}
|
||||
}
|
||||
return toArrayBuffer(this.plaintext);
|
||||
return this.plaintext;
|
||||
}
|
||||
|
||||
getContentProtoBytes(): Uint8Array | undefined {
|
||||
|
@ -720,7 +719,7 @@ export default class OutgoingMessage {
|
|||
identifier,
|
||||
error.originalMessage,
|
||||
error.timestamp,
|
||||
error.identityKey
|
||||
error.identityKey && new Uint8Array(error.identityKey)
|
||||
);
|
||||
this.registerError(identifier, 'Untrusted identity', newError);
|
||||
} else {
|
||||
|
|
|
@ -5,21 +5,17 @@
|
|||
/* eslint-disable max-classes-per-file */
|
||||
|
||||
import { KeyPairType } from './Types.d';
|
||||
import * as Bytes from '../Bytes';
|
||||
import {
|
||||
decryptAes256CbcPkcsPadding,
|
||||
deriveSecrets,
|
||||
bytesFromString,
|
||||
verifyHmacSha256,
|
||||
typedArrayToArrayBuffer,
|
||||
} from '../Crypto';
|
||||
import { calculateAgreement, createKeyPair, generateKeyPair } from '../Curve';
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
import { strictAssert } from '../util/assert';
|
||||
import { normalizeUuid } from '../util/normalizeUuid';
|
||||
|
||||
// TODO: remove once we move away from ArrayBuffers
|
||||
const FIXMEU8 = Uint8Array;
|
||||
|
||||
type ProvisionDecryptResult = {
|
||||
identityKeyPair: KeyPairType;
|
||||
number?: string;
|
||||
|
@ -27,7 +23,7 @@ type ProvisionDecryptResult = {
|
|||
provisioningCode?: string;
|
||||
userAgent?: string;
|
||||
readReceipts?: boolean;
|
||||
profileKey?: ArrayBuffer;
|
||||
profileKey?: Uint8Array;
|
||||
};
|
||||
|
||||
class ProvisioningCipherInner {
|
||||
|
@ -55,34 +51,20 @@ class ProvisioningCipherInner {
|
|||
throw new Error('ProvisioningCipher.decrypt: No keypair!');
|
||||
}
|
||||
|
||||
const ecRes = calculateAgreement(
|
||||
typedArrayToArrayBuffer(masterEphemeral),
|
||||
this.keyPair.privKey
|
||||
);
|
||||
const ecRes = calculateAgreement(masterEphemeral, this.keyPair.privKey);
|
||||
const keys = deriveSecrets(
|
||||
ecRes,
|
||||
new ArrayBuffer(32),
|
||||
bytesFromString('TextSecure Provisioning Message')
|
||||
);
|
||||
await verifyHmacSha256(
|
||||
typedArrayToArrayBuffer(ivAndCiphertext),
|
||||
keys[1],
|
||||
typedArrayToArrayBuffer(mac),
|
||||
32
|
||||
new Uint8Array(32),
|
||||
Bytes.fromString('TextSecure Provisioning Message')
|
||||
);
|
||||
verifyHmacSha256(ivAndCiphertext, keys[1], mac, 32);
|
||||
|
||||
const plaintext = await decryptAes256CbcPkcsPadding(
|
||||
keys[0],
|
||||
typedArrayToArrayBuffer(ciphertext),
|
||||
typedArrayToArrayBuffer(iv)
|
||||
);
|
||||
const provisionMessage = Proto.ProvisionMessage.decode(
|
||||
new FIXMEU8(plaintext)
|
||||
);
|
||||
const plaintext = decryptAes256CbcPkcsPadding(keys[0], ciphertext, iv);
|
||||
const provisionMessage = Proto.ProvisionMessage.decode(plaintext);
|
||||
const privKey = provisionMessage.identityKeyPrivate;
|
||||
strictAssert(privKey, 'Missing identityKeyPrivate in ProvisionMessage');
|
||||
|
||||
const keyPair = createKeyPair(typedArrayToArrayBuffer(privKey));
|
||||
const keyPair = createKeyPair(privKey);
|
||||
|
||||
const { uuid } = provisionMessage;
|
||||
strictAssert(uuid, 'Missing uuid in provisioning message');
|
||||
|
@ -96,12 +78,12 @@ class ProvisioningCipherInner {
|
|||
readReceipts: provisionMessage.readReceipts,
|
||||
};
|
||||
if (provisionMessage.profileKey) {
|
||||
ret.profileKey = typedArrayToArrayBuffer(provisionMessage.profileKey);
|
||||
ret.profileKey = provisionMessage.profileKey;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
async getPublicKey(): Promise<ArrayBuffer> {
|
||||
async getPublicKey(): Promise<Uint8Array> {
|
||||
if (!this.keyPair) {
|
||||
this.keyPair = generateKeyPair();
|
||||
}
|
||||
|
@ -126,5 +108,5 @@ export default class ProvisioningCipher {
|
|||
provisionEnvelope: Proto.ProvisionEnvelope
|
||||
) => Promise<ProvisionDecryptResult>;
|
||||
|
||||
getPublicKey: () => Promise<ArrayBuffer>;
|
||||
getPublicKey: () => Promise<Uint8Array>;
|
||||
}
|
||||
|
|
|
@ -39,14 +39,8 @@ import OutgoingMessage, {
|
|||
SerializedCertificateType,
|
||||
SendLogCallbackType,
|
||||
} from './OutgoingMessage';
|
||||
import Crypto from './Crypto';
|
||||
import * as Bytes from '../Bytes';
|
||||
import {
|
||||
concatenateBytes,
|
||||
getRandomBytes,
|
||||
getZeroes,
|
||||
typedArrayToArrayBuffer,
|
||||
} from '../Crypto';
|
||||
import { getRandomBytes, getZeroes, encryptAttachment } from '../Crypto';
|
||||
import {
|
||||
StorageServiceCallOptionsType,
|
||||
StorageServiceCredentials,
|
||||
|
@ -111,7 +105,7 @@ type GroupCallUpdateType = {
|
|||
|
||||
export type AttachmentType = {
|
||||
size: number;
|
||||
data: ArrayBuffer;
|
||||
data: Uint8Array;
|
||||
contentType: string;
|
||||
|
||||
fileName: string;
|
||||
|
@ -137,7 +131,7 @@ export type MessageOptionsType = {
|
|||
groupV2?: GroupV2InfoType;
|
||||
needsSync?: boolean;
|
||||
preview?: ReadonlyArray<PreviewType> | null;
|
||||
profileKey?: ArrayBuffer;
|
||||
profileKey?: Uint8Array;
|
||||
quote?: any;
|
||||
recipients: ReadonlyArray<string>;
|
||||
sticker?: any;
|
||||
|
@ -154,7 +148,7 @@ export type GroupSendOptionsType = {
|
|||
groupV1?: GroupV1InfoType;
|
||||
messageText?: string;
|
||||
preview?: any;
|
||||
profileKey?: ArrayBuffer;
|
||||
profileKey?: Uint8Array;
|
||||
quote?: any;
|
||||
reaction?: any;
|
||||
sticker?: any;
|
||||
|
@ -164,9 +158,6 @@ export type GroupSendOptionsType = {
|
|||
groupCallUpdate?: GroupCallUpdateType;
|
||||
};
|
||||
|
||||
// TODO: remove once we move away from ArrayBuffers
|
||||
const FIXMEU8 = Uint8Array;
|
||||
|
||||
class Message {
|
||||
attachments: ReadonlyArray<any>;
|
||||
|
||||
|
@ -187,7 +178,7 @@ class Message {
|
|||
|
||||
preview: any;
|
||||
|
||||
profileKey?: ArrayBuffer;
|
||||
profileKey?: Uint8Array;
|
||||
|
||||
quote?: {
|
||||
id?: number;
|
||||
|
@ -210,7 +201,7 @@ class Message {
|
|||
|
||||
timestamp: number;
|
||||
|
||||
dataMessage: any;
|
||||
dataMessage?: Proto.DataMessage;
|
||||
|
||||
attachmentPointers: Array<Proto.IAttachmentPointer> = [];
|
||||
|
||||
|
@ -298,7 +289,7 @@ class Message {
|
|||
}
|
||||
|
||||
toProto(): Proto.DataMessage {
|
||||
if (this.dataMessage instanceof Proto.DataMessage) {
|
||||
if (this.dataMessage) {
|
||||
return this.dataMessage;
|
||||
}
|
||||
const proto = new Proto.DataMessage();
|
||||
|
@ -405,7 +396,7 @@ class Message {
|
|||
proto.expireTimer = this.expireTimer;
|
||||
}
|
||||
if (this.profileKey) {
|
||||
proto.profileKey = new FIXMEU8(this.profileKey);
|
||||
proto.profileKey = this.profileKey;
|
||||
}
|
||||
if (this.deletedForEveryoneTimestamp) {
|
||||
proto.delete = {
|
||||
|
@ -437,10 +428,8 @@ class Message {
|
|||
return proto;
|
||||
}
|
||||
|
||||
toArrayBuffer() {
|
||||
return typedArrayToArrayBuffer(
|
||||
Proto.DataMessage.encode(this.toProto()).finish()
|
||||
);
|
||||
encode() {
|
||||
return Proto.DataMessage.encode(this.toProto()).finish();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -489,15 +478,15 @@ export default class MessageSender {
|
|||
const paddingLength = (new Uint16Array(buffer)[0] & 0x1ff) + 1;
|
||||
|
||||
// Generate a random padding buffer of the chosen size
|
||||
return new FIXMEU8(getRandomBytes(paddingLength));
|
||||
return getRandomBytes(paddingLength);
|
||||
}
|
||||
|
||||
getPaddedAttachment(data: Readonly<ArrayBuffer>): ArrayBuffer {
|
||||
getPaddedAttachment(data: Readonly<Uint8Array>): Uint8Array {
|
||||
const size = data.byteLength;
|
||||
const paddedSize = this._getAttachmentSizeBucket(size);
|
||||
const padding = getZeroes(paddedSize - size);
|
||||
|
||||
return concatenateBytes(data, padding);
|
||||
return Bytes.concatenate([data, padding]);
|
||||
}
|
||||
|
||||
async makeAttachmentPointer(
|
||||
|
@ -509,9 +498,9 @@ export default class MessageSender {
|
|||
);
|
||||
|
||||
const { data, size } = attachment;
|
||||
if (!(data instanceof ArrayBuffer) && !ArrayBuffer.isView(data)) {
|
||||
if (!(data instanceof Uint8Array)) {
|
||||
throw new Error(
|
||||
`makeAttachmentPointer: data was a '${typeof data}' instead of ArrayBuffer/ArrayBufferView`
|
||||
`makeAttachmentPointer: data was a '${typeof data}' instead of Uint8Array`
|
||||
);
|
||||
}
|
||||
if (data.byteLength !== size) {
|
||||
|
@ -524,15 +513,15 @@ export default class MessageSender {
|
|||
const key = getRandomBytes(64);
|
||||
const iv = getRandomBytes(16);
|
||||
|
||||
const result = await Crypto.encryptAttachment(padded, key, iv);
|
||||
const result = encryptAttachment(padded, key, iv);
|
||||
const id = await this.server.putAttachment(result.ciphertext);
|
||||
|
||||
const proto = new Proto.AttachmentPointer();
|
||||
proto.cdnId = Long.fromString(id);
|
||||
proto.contentType = attachment.contentType;
|
||||
proto.key = new FIXMEU8(key);
|
||||
proto.key = key;
|
||||
proto.size = attachment.size;
|
||||
proto.digest = new FIXMEU8(result.digest);
|
||||
proto.digest = result.digest;
|
||||
|
||||
if (attachment.fileName) {
|
||||
proto.fileName = attachment.fileName;
|
||||
|
@ -651,9 +640,9 @@ export default class MessageSender {
|
|||
|
||||
async getDataMessage(
|
||||
options: Readonly<MessageOptionsType>
|
||||
): Promise<ArrayBuffer> {
|
||||
): Promise<Uint8Array> {
|
||||
const message = await this.getHydratedMessage(options);
|
||||
return message.toArrayBuffer();
|
||||
return message.encode();
|
||||
}
|
||||
|
||||
async getContentMessage(
|
||||
|
@ -685,7 +674,7 @@ export default class MessageSender {
|
|||
getTypingContentMessage(
|
||||
options: Readonly<{
|
||||
recipientId?: string;
|
||||
groupId?: ArrayBuffer;
|
||||
groupId?: Uint8Array;
|
||||
groupMembers: ReadonlyArray<string>;
|
||||
isTyping: boolean;
|
||||
timestamp?: number;
|
||||
|
@ -705,7 +694,7 @@ export default class MessageSender {
|
|||
|
||||
const typingMessage = new Proto.TypingMessage();
|
||||
if (groupId) {
|
||||
typingMessage.groupId = new FIXMEU8(groupId);
|
||||
typingMessage.groupId = groupId;
|
||||
}
|
||||
typingMessage.action = action;
|
||||
typingMessage.timestamp = finalTimestamp;
|
||||
|
@ -823,7 +812,7 @@ export default class MessageSender {
|
|||
new Promise((resolve, reject) => {
|
||||
this.sendMessageProto({
|
||||
callback: (res: CallbackResultType) => {
|
||||
res.dataMessage = message.toArrayBuffer();
|
||||
res.dataMessage = message.encode();
|
||||
if (res.errors && res.errors.length > 0) {
|
||||
reject(new SendMessageProtoError(res));
|
||||
} else {
|
||||
|
@ -988,7 +977,7 @@ export default class MessageSender {
|
|||
expireTimer: number | undefined;
|
||||
contentHint: number;
|
||||
groupId: string | undefined;
|
||||
profileKey?: ArrayBuffer;
|
||||
profileKey?: Uint8Array;
|
||||
options?: SendOptionsType;
|
||||
}>): Promise<CallbackResultType> {
|
||||
return this.sendMessage({
|
||||
|
@ -1026,7 +1015,7 @@ export default class MessageSender {
|
|||
isUpdate,
|
||||
options,
|
||||
}: Readonly<{
|
||||
encodedDataMessage: ArrayBuffer;
|
||||
encodedDataMessage: Uint8Array;
|
||||
timestamp: number;
|
||||
destination: string | undefined;
|
||||
destinationUuid: string | null | undefined;
|
||||
|
@ -1038,9 +1027,7 @@ export default class MessageSender {
|
|||
}>): Promise<CallbackResultType> {
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
|
||||
const dataMessage = Proto.DataMessage.decode(
|
||||
new FIXMEU8(encodedDataMessage)
|
||||
);
|
||||
const dataMessage = Proto.DataMessage.decode(encodedDataMessage);
|
||||
const sentMessage = new Proto.SyncMessage.Sent();
|
||||
sentMessage.timestamp = timestamp;
|
||||
sentMessage.message = dataMessage;
|
||||
|
@ -1356,7 +1343,7 @@ export default class MessageSender {
|
|||
responseArgs: Readonly<{
|
||||
threadE164?: string;
|
||||
threadUuid?: string;
|
||||
groupId?: ArrayBuffer;
|
||||
groupId?: Uint8Array;
|
||||
type: number;
|
||||
}>,
|
||||
options?: Readonly<SendOptionsType>
|
||||
|
@ -1373,7 +1360,7 @@ export default class MessageSender {
|
|||
response.threadUuid = responseArgs.threadUuid;
|
||||
}
|
||||
if (responseArgs.groupId) {
|
||||
response.groupId = new FIXMEU8(responseArgs.groupId);
|
||||
response.groupId = responseArgs.groupId;
|
||||
}
|
||||
response.type = responseArgs.type;
|
||||
syncMessage.messageRequestResponse = response;
|
||||
|
@ -1435,7 +1422,7 @@ export default class MessageSender {
|
|||
destinationE164: string | undefined,
|
||||
destinationUuid: string | undefined,
|
||||
state: number,
|
||||
identityKey: Readonly<ArrayBuffer>,
|
||||
identityKey: Readonly<Uint8Array>,
|
||||
options?: Readonly<SendOptionsType>
|
||||
): Promise<CallbackResultType> {
|
||||
const myUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
|
@ -1468,7 +1455,7 @@ export default class MessageSender {
|
|||
if (destinationUuid) {
|
||||
verified.destinationUuid = destinationUuid;
|
||||
}
|
||||
verified.identityKey = new FIXMEU8(identityKey);
|
||||
verified.identityKey = identityKey;
|
||||
verified.nullMessage = padding;
|
||||
|
||||
const syncMessage = this.createSyncMessage();
|
||||
|
@ -1491,7 +1478,7 @@ export default class MessageSender {
|
|||
// Sending messages to contacts
|
||||
|
||||
async sendProfileKeyUpdate(
|
||||
profileKey: Readonly<ArrayBuffer>,
|
||||
profileKey: Readonly<Uint8Array>,
|
||||
recipients: ReadonlyArray<string>,
|
||||
options: Readonly<SendOptionsType>,
|
||||
groupId?: string
|
||||
|
@ -1712,11 +1699,9 @@ export default class MessageSender {
|
|||
return sendToContactPromise;
|
||||
}
|
||||
|
||||
const buffer = typedArrayToArrayBuffer(
|
||||
Proto.DataMessage.encode(proto).finish()
|
||||
);
|
||||
const encodedDataMessage = Proto.DataMessage.encode(proto).finish();
|
||||
const sendSyncPromise = this.sendSyncMessage({
|
||||
encodedDataMessage: buffer,
|
||||
encodedDataMessage,
|
||||
timestamp,
|
||||
destination: e164,
|
||||
destinationUuid: uuid,
|
||||
|
@ -1738,7 +1723,7 @@ export default class MessageSender {
|
|||
identifier: string,
|
||||
expireTimer: number | undefined,
|
||||
timestamp: number,
|
||||
profileKey?: Readonly<ArrayBuffer>,
|
||||
profileKey?: Readonly<Uint8Array>,
|
||||
options?: Readonly<SendOptionsType>
|
||||
): Promise<CallbackResultType> {
|
||||
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
||||
|
@ -1869,9 +1854,7 @@ export default class MessageSender {
|
|||
timestamp: number;
|
||||
}>): Promise<CallbackResultType> {
|
||||
const dataMessage = proto.dataMessage
|
||||
? typedArrayToArrayBuffer(
|
||||
Proto.DataMessage.encode(proto.dataMessage).finish()
|
||||
)
|
||||
? Proto.DataMessage.encode(proto.dataMessage).finish()
|
||||
: undefined;
|
||||
|
||||
const myE164 = window.textsecure.storage.user.getNumber();
|
||||
|
@ -2034,7 +2017,7 @@ export default class MessageSender {
|
|||
groupIdentifiers: ReadonlyArray<string>,
|
||||
expireTimer: number | undefined,
|
||||
timestamp: number,
|
||||
profileKey?: Readonly<ArrayBuffer>,
|
||||
profileKey?: Readonly<Uint8Array>,
|
||||
options?: Readonly<SendOptionsType>
|
||||
): Promise<CallbackResultType> {
|
||||
const myNumber = window.textsecure.storage.user.getNumber();
|
||||
|
@ -2163,7 +2146,7 @@ export default class MessageSender {
|
|||
return this.server.getGroupLog(startVersion, options);
|
||||
}
|
||||
|
||||
async getGroupAvatar(key: string): Promise<ArrayBuffer> {
|
||||
async getGroupAvatar(key: string): Promise<Uint8Array> {
|
||||
return this.server.getGroupAvatar(key);
|
||||
}
|
||||
|
||||
|
@ -2176,8 +2159,8 @@ export default class MessageSender {
|
|||
}
|
||||
|
||||
async sendWithSenderKey(
|
||||
data: Readonly<ArrayBuffer>,
|
||||
accessKeys: Readonly<ArrayBuffer>,
|
||||
data: Readonly<Uint8Array>,
|
||||
accessKeys: Readonly<Uint8Array>,
|
||||
timestamp: number,
|
||||
online?: boolean
|
||||
): Promise<MultiRecipient200ResponseType> {
|
||||
|
@ -2211,21 +2194,21 @@ export default class MessageSender {
|
|||
|
||||
async getStorageManifest(
|
||||
options: Readonly<StorageServiceCallOptionsType>
|
||||
): Promise<ArrayBuffer> {
|
||||
): Promise<Uint8Array> {
|
||||
return this.server.getStorageManifest(options);
|
||||
}
|
||||
|
||||
async getStorageRecords(
|
||||
data: Readonly<ArrayBuffer>,
|
||||
data: Readonly<Uint8Array>,
|
||||
options: Readonly<StorageServiceCallOptionsType>
|
||||
): Promise<ArrayBuffer> {
|
||||
): Promise<Uint8Array> {
|
||||
return this.server.getStorageRecords(data, options);
|
||||
}
|
||||
|
||||
async modifyStorageRecords(
|
||||
data: Readonly<ArrayBuffer>,
|
||||
data: Readonly<Uint8Array>,
|
||||
options: Readonly<StorageServiceCallOptionsType>
|
||||
): Promise<ArrayBuffer> {
|
||||
): Promise<Uint8Array> {
|
||||
return this.server.modifyStorageRecords(data, options);
|
||||
}
|
||||
|
||||
|
@ -2249,7 +2232,7 @@ export default class MessageSender {
|
|||
|
||||
async uploadAvatar(
|
||||
requestHeaders: Readonly<UploadAvatarHeadersType>,
|
||||
avatarData: Readonly<ArrayBuffer>
|
||||
avatarData: Readonly<Uint8Array>
|
||||
): Promise<string> {
|
||||
return this.server.uploadAvatar(requestHeaders, avatarData);
|
||||
}
|
||||
|
|
|
@ -28,9 +28,6 @@ import { ConnectTimeoutError, HTTPError } from './Errors';
|
|||
import { handleStatusCode, translateError } from './Utils';
|
||||
import { WebAPICredentials, IRequestHandler } from './Types.d';
|
||||
|
||||
// TODO: remove once we move away from ArrayBuffers
|
||||
const FIXMEU8 = Uint8Array;
|
||||
|
||||
const TEN_SECONDS = 10 * durations.SECOND;
|
||||
|
||||
const FIVE_MINUTES = 5 * durations.MINUTE;
|
||||
|
@ -289,7 +286,7 @@ export class SocketManager extends EventListener {
|
|||
} else if (body instanceof Uint8Array) {
|
||||
bodyBytes = body;
|
||||
} else if (body instanceof ArrayBuffer) {
|
||||
bodyBytes = new FIXMEU8(body);
|
||||
throw new Error('Unsupported body type: ArrayBuffer');
|
||||
} else if (typeof body === 'string') {
|
||||
bodyBytes = Bytes.fromString(body);
|
||||
} else {
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
/* eslint-disable no-bitwise */
|
||||
/* eslint-disable no-nested-ternary */
|
||||
|
||||
const StringView = {
|
||||
/*
|
||||
* These functions from the Mozilla Developer Network
|
||||
* and have been placed in the public domain.
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
|
||||
* https://developer.mozilla.org/en-US/docs/MDN/About#Copyrights_and_licenses
|
||||
*/
|
||||
|
||||
// prettier-ignore
|
||||
b64ToUint6(nChr: number): number {
|
||||
return nChr > 64 && nChr < 91
|
||||
? nChr - 65
|
||||
: nChr > 96 && nChr < 123
|
||||
? nChr - 71
|
||||
: nChr > 47 && nChr < 58
|
||||
? nChr + 4
|
||||
: nChr === 43
|
||||
? 62
|
||||
: nChr === 47
|
||||
? 63
|
||||
: 0;
|
||||
},
|
||||
|
||||
base64ToBytes(sBase64: string, nBlocksSize: number): ArrayBuffer {
|
||||
const sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, '');
|
||||
const nInLen = sB64Enc.length;
|
||||
const nOutLen = nBlocksSize
|
||||
? Math.ceil(((nInLen * 3 + 1) >> 2) / nBlocksSize) * nBlocksSize
|
||||
: (nInLen * 3 + 1) >> 2;
|
||||
const aBBytes = new ArrayBuffer(nOutLen);
|
||||
const taBytes = new Uint8Array(aBBytes);
|
||||
|
||||
let nMod3;
|
||||
let nMod4;
|
||||
let nOutIdx = 0;
|
||||
let nInIdx = 0;
|
||||
for (let nUint24 = 0; nInIdx < nInLen; nInIdx += 1) {
|
||||
nMod4 = nInIdx & 3;
|
||||
nUint24 |=
|
||||
StringView.b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << (18 - 6 * nMod4);
|
||||
if (nMod4 === 3 || nInLen - nInIdx === 1) {
|
||||
for (
|
||||
nMod3 = 0;
|
||||
nMod3 < 3 && nOutIdx < nOutLen;
|
||||
nMod3 += 1, nOutIdx += 1
|
||||
) {
|
||||
taBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255;
|
||||
}
|
||||
nUint24 = 0;
|
||||
}
|
||||
}
|
||||
return aBBytes;
|
||||
},
|
||||
|
||||
// prettier-ignore
|
||||
uint6ToB64(nUint6: number): number {
|
||||
return nUint6 < 26
|
||||
? nUint6 + 65
|
||||
: nUint6 < 52
|
||||
? nUint6 + 71
|
||||
: nUint6 < 62
|
||||
? nUint6 - 4
|
||||
: nUint6 === 62
|
||||
? 43
|
||||
: nUint6 === 63
|
||||
? 47
|
||||
: 65;
|
||||
},
|
||||
|
||||
bytesToBase64(aBytes: Uint8Array): string {
|
||||
let nMod3;
|
||||
let sB64Enc = '';
|
||||
let nUint24 = 0;
|
||||
const nLen = aBytes.length;
|
||||
for (let nIdx = 0; nIdx < nLen; nIdx += 1) {
|
||||
nMod3 = nIdx % 3;
|
||||
if (nIdx > 0 && ((nIdx * 4) / 3) % 76 === 0) {
|
||||
sB64Enc += '\r\n';
|
||||
}
|
||||
nUint24 |= aBytes[nIdx] << ((16 >>> nMod3) & 24);
|
||||
if (nMod3 === 2 || aBytes.length - nIdx === 1) {
|
||||
sB64Enc += String.fromCharCode(
|
||||
StringView.uint6ToB64((nUint24 >>> 18) & 63),
|
||||
StringView.uint6ToB64((nUint24 >>> 12) & 63),
|
||||
StringView.uint6ToB64((nUint24 >>> 6) & 63),
|
||||
StringView.uint6ToB64(nUint24 & 63)
|
||||
);
|
||||
nUint24 = 0;
|
||||
}
|
||||
}
|
||||
return sB64Enc.replace(/A(?=A$|$)/g, '=');
|
||||
},
|
||||
};
|
||||
|
||||
export default StringView;
|
12
ts/textsecure/Types.d.ts
vendored
12
ts/textsecure/Types.d.ts
vendored
|
@ -45,7 +45,7 @@ export type DeviceType = {
|
|||
export type CompatSignedPreKeyType = {
|
||||
keyId: number;
|
||||
keyPair: KeyPairType;
|
||||
signature: ArrayBuffer;
|
||||
signature: Uint8Array;
|
||||
};
|
||||
|
||||
export type CompatPreKeyType = {
|
||||
|
@ -56,8 +56,8 @@ export type CompatPreKeyType = {
|
|||
// How we work with these types thereafter
|
||||
|
||||
export type KeyPairType = {
|
||||
privKey: ArrayBuffer;
|
||||
pubKey: ArrayBuffer;
|
||||
privKey: Uint8Array;
|
||||
pubKey: Uint8Array;
|
||||
};
|
||||
|
||||
export type OuterSignedPrekeyType = {
|
||||
|
@ -65,8 +65,8 @@ export type OuterSignedPrekeyType = {
|
|||
// eslint-disable-next-line camelcase
|
||||
created_at: number;
|
||||
keyId: number;
|
||||
privKey: ArrayBuffer;
|
||||
pubKey: ArrayBuffer;
|
||||
privKey: Uint8Array;
|
||||
pubKey: Uint8Array;
|
||||
};
|
||||
|
||||
export type SessionResetsType = Record<string, number>;
|
||||
|
@ -231,7 +231,7 @@ export interface CallbackResultType {
|
|||
failoverIdentifiers?: Array<string>;
|
||||
errors?: Array<CustomError>;
|
||||
unidentifiedDeliveries?: Array<string>;
|
||||
dataMessage?: ArrayBuffer;
|
||||
dataMessage?: Uint8Array;
|
||||
|
||||
// Fields necesary for send log save
|
||||
contentHint?: number;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -8,9 +8,8 @@ import { dropNull } from '../util/dropNull';
|
|||
import { DownloadedAttachmentType } from '../types/Attachment';
|
||||
import * as MIME from '../types/MIME';
|
||||
import * as Bytes from '../Bytes';
|
||||
import { typedArrayToArrayBuffer } from '../Crypto';
|
||||
import { getFirstBytes, decryptAttachment } from '../Crypto';
|
||||
|
||||
import Crypto from './Crypto';
|
||||
import { ProcessedAttachment } from './Types.d';
|
||||
import type { WebAPIType } from './WebAPI';
|
||||
|
||||
|
@ -36,10 +35,10 @@ export async function downloadAttachment(
|
|||
strictAssert(key, 'attachment has no key');
|
||||
strictAssert(digest, 'attachment has no digest');
|
||||
|
||||
const paddedData = await Crypto.decryptAttachment(
|
||||
const paddedData = decryptAttachment(
|
||||
encrypted,
|
||||
typedArrayToArrayBuffer(Bytes.fromBase64(key)),
|
||||
typedArrayToArrayBuffer(Bytes.fromBase64(digest))
|
||||
Bytes.fromBase64(key),
|
||||
Bytes.fromBase64(digest)
|
||||
);
|
||||
|
||||
if (!isNumber(size)) {
|
||||
|
@ -48,7 +47,7 @@ export async function downloadAttachment(
|
|||
);
|
||||
}
|
||||
|
||||
const data = window.Signal.Crypto.getFirstBytes(paddedData, size);
|
||||
const data = getFirstBytes(paddedData, size);
|
||||
|
||||
return {
|
||||
...omit(attachment, 'digest', 'key'),
|
||||
|
|
|
@ -5,17 +5,14 @@ import EventTarget from './EventTarget';
|
|||
import AccountManager from './AccountManager';
|
||||
import MessageReceiver from './MessageReceiver';
|
||||
import utils from './Helpers';
|
||||
import Crypto from './Crypto';
|
||||
import { ContactBuffer, GroupBuffer } from './ContactsParser';
|
||||
import SyncRequest from './SyncRequest';
|
||||
import MessageSender from './SendMessage';
|
||||
import StringView from './StringView';
|
||||
import { Storage } from './Storage';
|
||||
import * as WebAPI from './WebAPI';
|
||||
import WebSocketResource from './WebsocketResources';
|
||||
|
||||
export const textsecure = {
|
||||
crypto: Crypto,
|
||||
utils,
|
||||
storage: new Storage(),
|
||||
|
||||
|
@ -26,7 +23,6 @@ export const textsecure = {
|
|||
MessageReceiver,
|
||||
MessageSender,
|
||||
SyncRequest,
|
||||
StringView,
|
||||
WebAPI,
|
||||
WebSocketResource,
|
||||
};
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue