decrypt/encrypt with libsignal-client, remove libsignal-protocol-javascript
This commit is contained in:
parent
37ff4a1df4
commit
86d2a4b5dd
60 changed files with 2508 additions and 28714 deletions
131
ts/Crypto.ts
131
ts/Crypto.ts
|
@ -3,6 +3,8 @@
|
|||
|
||||
import pProps from 'p-props';
|
||||
import { chunk } from 'lodash';
|
||||
import { HKDF } from 'libsignal-client';
|
||||
import { calculateAgreement, generateKeyPair } from './Curve';
|
||||
|
||||
import {
|
||||
CipherType,
|
||||
|
@ -13,6 +15,14 @@ import {
|
|||
sign,
|
||||
} from './util/synchronousCrypto';
|
||||
|
||||
// Generate a number between zero and 16383
|
||||
export function generateRegistrationId(): number {
|
||||
const id = new Uint16Array(getRandomBytes(2))[0];
|
||||
|
||||
// eslint-disable-next-line no-bitwise
|
||||
return id & 0x3fff;
|
||||
}
|
||||
|
||||
export function typedArrayToArrayBuffer(typedArray: Uint8Array): ArrayBuffer {
|
||||
const ab = new ArrayBuffer(typedArray.length);
|
||||
// Create a new Uint8Array backed by the ArrayBuffer and copy all values from
|
||||
|
@ -63,26 +73,37 @@ export async function deriveStickerPackKey(
|
|||
const salt = getZeroes(32);
|
||||
const info = bytesFromString('Sticker Pack');
|
||||
|
||||
const [part1, part2] = await window.libsignal.HKDF.deriveSecrets(
|
||||
packKey,
|
||||
salt,
|
||||
info
|
||||
);
|
||||
const [part1, part2] = await deriveSecrets(packKey, salt, info);
|
||||
|
||||
return concatenateBytes(part1, part2);
|
||||
}
|
||||
|
||||
export function deriveSecrets(
|
||||
input: ArrayBuffer,
|
||||
salt: ArrayBuffer,
|
||||
info: ArrayBuffer
|
||||
): [ArrayBuffer, ArrayBuffer, ArrayBuffer] {
|
||||
const hkdf = HKDF.new(3);
|
||||
const output = hkdf.deriveSecrets(
|
||||
3 * 32,
|
||||
Buffer.from(input),
|
||||
Buffer.from(info),
|
||||
Buffer.from(salt)
|
||||
);
|
||||
return [
|
||||
typedArrayToArrayBuffer(output.slice(0, 32)),
|
||||
typedArrayToArrayBuffer(output.slice(32, 64)),
|
||||
typedArrayToArrayBuffer(output.slice(64, 96)),
|
||||
];
|
||||
}
|
||||
|
||||
export async function deriveMasterKeyFromGroupV1(
|
||||
groupV1Id: ArrayBuffer
|
||||
): Promise<ArrayBuffer> {
|
||||
const salt = getZeroes(32);
|
||||
const info = bytesFromString('GV2 Migration');
|
||||
|
||||
const [part1] = await window.libsignal.HKDF.deriveSecrets(
|
||||
groupV1Id,
|
||||
salt,
|
||||
info
|
||||
);
|
||||
const [part1] = await deriveSecrets(groupV1Id, salt, info);
|
||||
|
||||
return part1;
|
||||
}
|
||||
|
@ -99,8 +120,8 @@ export async function encryptDeviceName(
|
|||
identityPublic: ArrayBuffer
|
||||
): Promise<Record<string, ArrayBuffer>> {
|
||||
const plaintext = bytesFromString(deviceName);
|
||||
const ephemeralKeyPair = await window.libsignal.KeyHelper.generateIdentityKeyPair();
|
||||
const masterSecret = await window.libsignal.Curve.async.calculateAgreement(
|
||||
const ephemeralKeyPair = generateKeyPair();
|
||||
const masterSecret = calculateAgreement(
|
||||
identityPublic,
|
||||
ephemeralKeyPair.privKey
|
||||
);
|
||||
|
@ -133,10 +154,7 @@ export async function decryptDeviceName(
|
|||
},
|
||||
identityPrivate: ArrayBuffer
|
||||
): Promise<string> {
|
||||
const masterSecret = await window.libsignal.Curve.async.calculateAgreement(
|
||||
ephemeralPublic,
|
||||
identityPrivate
|
||||
);
|
||||
const masterSecret = calculateAgreement(ephemeralPublic, identityPrivate);
|
||||
|
||||
const key2 = await hmacSha256(masterSecret, bytesFromString('cipher'));
|
||||
const cipherKey = await hmacSha256(key2, syntheticIv);
|
||||
|
@ -187,8 +205,8 @@ export async function encryptFile(
|
|||
uniqueId: ArrayBuffer,
|
||||
plaintext: ArrayBuffer
|
||||
): Promise<ArrayBuffer> {
|
||||
const ephemeralKeyPair = await window.libsignal.KeyHelper.generateIdentityKeyPair();
|
||||
const agreement = await window.libsignal.Curve.async.calculateAgreement(
|
||||
const ephemeralKeyPair = generateKeyPair();
|
||||
const agreement = calculateAgreement(
|
||||
staticPublicKey,
|
||||
ephemeralKeyPair.privKey
|
||||
);
|
||||
|
@ -206,10 +224,7 @@ export async function decryptFile(
|
|||
): Promise<ArrayBuffer> {
|
||||
const ephemeralPublicKey = getFirstBytes(data, PUB_KEY_LENGTH);
|
||||
const ciphertext = getBytes(data, PUB_KEY_LENGTH, data.byteLength);
|
||||
const agreement = await window.libsignal.Curve.async.calculateAgreement(
|
||||
ephemeralPublicKey,
|
||||
staticPrivateKey
|
||||
);
|
||||
const agreement = calculateAgreement(ephemeralPublicKey, staticPrivateKey);
|
||||
|
||||
const key = await hmacSha256(agreement, uniqueId);
|
||||
|
||||
|
@ -275,14 +290,14 @@ export async function encryptSymmetric(
|
|||
const cipherKey = await hmacSha256(key, nonce);
|
||||
const macKey = await hmacSha256(key, cipherKey);
|
||||
|
||||
const cipherText = await _encryptAes256CbcPkcsPadding(
|
||||
const ciphertext = await encryptAes256CbcPkcsPadding(
|
||||
cipherKey,
|
||||
iv,
|
||||
plaintext
|
||||
plaintext,
|
||||
iv
|
||||
);
|
||||
const mac = getFirstBytes(await hmacSha256(macKey, cipherText), MAC_LENGTH);
|
||||
const mac = getFirstBytes(await hmacSha256(macKey, ciphertext), MAC_LENGTH);
|
||||
|
||||
return concatenateBytes(nonce, cipherText, mac);
|
||||
return concatenateBytes(nonce, ciphertext, mac);
|
||||
}
|
||||
|
||||
export async function decryptSymmetric(
|
||||
|
@ -292,7 +307,7 @@ export async function decryptSymmetric(
|
|||
const iv = getZeroes(IV_LENGTH);
|
||||
|
||||
const nonce = getFirstBytes(data, NONCE_LENGTH);
|
||||
const cipherText = getBytes(
|
||||
const ciphertext = getBytes(
|
||||
data,
|
||||
NONCE_LENGTH,
|
||||
data.byteLength - NONCE_LENGTH - MAC_LENGTH
|
||||
|
@ -303,7 +318,7 @@ export async function decryptSymmetric(
|
|||
const macKey = await hmacSha256(key, cipherKey);
|
||||
|
||||
const ourMac = getFirstBytes(
|
||||
await hmacSha256(macKey, cipherText),
|
||||
await hmacSha256(macKey, ciphertext),
|
||||
MAC_LENGTH
|
||||
);
|
||||
if (!constantTimeEqual(theirMac, ourMac)) {
|
||||
|
@ -312,7 +327,7 @@ export async function decryptSymmetric(
|
|||
);
|
||||
}
|
||||
|
||||
return _decryptAes256CbcPkcsPadding(cipherKey, iv, cipherText);
|
||||
return decryptAes256CbcPkcsPadding(cipherKey, ciphertext, iv);
|
||||
}
|
||||
|
||||
export function constantTimeEqual(
|
||||
|
@ -343,10 +358,37 @@ export async function hmacSha256(
|
|||
return sign(key, plaintext);
|
||||
}
|
||||
|
||||
export async function _encryptAes256CbcPkcsPadding(
|
||||
// We use part of the constantTimeEqual algorithm from below here, but we allow ourMac
|
||||
// to be longer than the passed-in length. This allows easy comparisons against
|
||||
// arbitrary MAC lengths.
|
||||
export async function verifyHmacSha256(
|
||||
plaintext: ArrayBuffer,
|
||||
key: ArrayBuffer,
|
||||
iv: ArrayBuffer,
|
||||
plaintext: ArrayBuffer
|
||||
theirMac: ArrayBuffer,
|
||||
length: number
|
||||
): Promise<void> {
|
||||
const ourMac = await hmacSha256(key, plaintext);
|
||||
|
||||
if (theirMac.byteLength !== length || ourMac.byteLength < length) {
|
||||
throw new Error('Bad MAC length');
|
||||
}
|
||||
const a = new Uint8Array(theirMac);
|
||||
const b = new Uint8Array(ourMac);
|
||||
let result = 0;
|
||||
|
||||
for (let i = 0; i < theirMac.byteLength; i += 1) {
|
||||
// eslint-disable-next-line no-bitwise
|
||||
result |= a[i] ^ b[i];
|
||||
}
|
||||
if (result !== 0) {
|
||||
throw new Error('Bad MAC');
|
||||
}
|
||||
}
|
||||
|
||||
export async function encryptAes256CbcPkcsPadding(
|
||||
key: ArrayBuffer,
|
||||
plaintext: ArrayBuffer,
|
||||
iv: ArrayBuffer
|
||||
): Promise<ArrayBuffer> {
|
||||
const algorithm = {
|
||||
name: 'AES-CBC',
|
||||
|
@ -369,10 +411,10 @@ export async function _encryptAes256CbcPkcsPadding(
|
|||
return window.crypto.subtle.encrypt(algorithm, cryptoKey, plaintext);
|
||||
}
|
||||
|
||||
export async function _decryptAes256CbcPkcsPadding(
|
||||
export async function decryptAes256CbcPkcsPadding(
|
||||
key: ArrayBuffer,
|
||||
iv: ArrayBuffer,
|
||||
plaintext: ArrayBuffer
|
||||
ciphertext: ArrayBuffer,
|
||||
iv: ArrayBuffer
|
||||
): Promise<ArrayBuffer> {
|
||||
const algorithm = {
|
||||
name: 'AES-CBC',
|
||||
|
@ -392,7 +434,7 @@ export async function _decryptAes256CbcPkcsPadding(
|
|||
['decrypt']
|
||||
);
|
||||
|
||||
return window.crypto.subtle.decrypt(algorithm, cryptoKey, plaintext);
|
||||
return window.crypto.subtle.decrypt(algorithm, cryptoKey, ciphertext);
|
||||
}
|
||||
|
||||
export async function encryptAesCtr(
|
||||
|
@ -531,7 +573,7 @@ export function getViewOfArrayBuffer(
|
|||
const source = new Uint8Array(buffer);
|
||||
const result = source.slice(start, finish);
|
||||
|
||||
return result.buffer;
|
||||
return window.Signal.Crypto.typedArrayToArrayBuffer(result);
|
||||
}
|
||||
|
||||
export function concatenateBytes(
|
||||
|
@ -627,7 +669,10 @@ export async function encryptCdsDiscoveryRequest(
|
|||
// Long.fromString handles numbers with or without a leading '+'
|
||||
numbersArray.writeLong(window.dcodeIO.ByteBuffer.Long.fromString(number));
|
||||
});
|
||||
const queryDataPlaintext = concatenateBytes(nonce, numbersArray.buffer);
|
||||
const queryDataPlaintext = concatenateBytes(
|
||||
nonce,
|
||||
numbersArray.toArrayBuffer()
|
||||
);
|
||||
const queryDataKey = getRandomBytes(32);
|
||||
const commitment = sha256(queryDataPlaintext);
|
||||
const iv = getRandomBytes(12);
|
||||
|
@ -680,9 +725,11 @@ export function uuidToArrayBuffer(uuid: string): ArrayBuffer {
|
|||
return new ArrayBuffer(0);
|
||||
}
|
||||
|
||||
return Uint8Array.from(
|
||||
chunk(uuid.replace(/-/g, ''), 2).map(pair => parseInt(pair.join(''), 16))
|
||||
).buffer;
|
||||
return typedArrayToArrayBuffer(
|
||||
Uint8Array.from(
|
||||
chunk(uuid.replace(/-/g, ''), 2).map(pair => parseInt(pair.join(''), 16))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function arrayBufferToUuid(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue