"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "ts/Crypto.ts" between
Signal-Desktop-1.35.2.tar.gz and Signal-Desktop-1.36.1.tar.gz

About: Signal-Desktop is a cross-platform encrypted messaging service (also available for mobile devices).

Crypto.ts  (Signal-Desktop-1.35.2):Crypto.ts  (Signal-Desktop-1.36.1)
import pProps from 'p-props';
// Yep, we're doing some bitwise stuff in an encryption-related file // Yep, we're doing some bitwise stuff in an encryption-related file
// tslint:disable no-bitwise // tslint:disable no-bitwise
// We want some extra variables to make the decrption algorithm easier to unders tand // We want some extra variables to make the decrption algorithm easier to unders tand
// tslint:disable no-unnecessary-local-variable // tslint:disable no-unnecessary-local-variable
// Seems that tslint doesn't understand that crypto.subtle.importKey does return a Promise // Seems that tslint doesn't understand that crypto.subtle.importKey does return a Promise
// tslint:disable await-promise // tslint:disable await-promise
export function typedArrayToArrayBuffer(typedArray: Uint8Array): ArrayBuffer { export function typedArrayToArrayBuffer(typedArray: Uint8Array): ArrayBuffer {
const { buffer, byteOffset, byteLength } = typedArray; const { buffer, byteOffset, byteLength } = typedArray;
// tslint:disable-next-line no-unnecessary-type-assertion // tslint:disable-next-line no-unnecessary-type-assertion
return buffer.slice(byteOffset, byteLength + byteOffset) as ArrayBuffer; return buffer.slice(byteOffset, byteLength + byteOffset) as typeof typedArray;
} }
export function arrayBufferToBase64(arrayBuffer: ArrayBuffer) { export function arrayBufferToBase64(arrayBuffer: ArrayBuffer) {
return window.dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('base64'); return window.dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('base64');
} }
export function arrayBufferToHex(arrayBuffer: ArrayBuffer) { export function arrayBufferToHex(arrayBuffer: ArrayBuffer) {
return window.dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('hex'); return window.dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('hex');
} }
skipping to change at line 63 skipping to change at line 65
const [part1, part2] = await window.libsignal.HKDF.deriveSecrets( const [part1, part2] = await window.libsignal.HKDF.deriveSecrets(
packKey, packKey,
salt, salt,
info info
); );
return concatenateBytes(part1, part2); return concatenateBytes(part1, part2);
} }
export async function computeHash(data: ArrayBuffer): Promise<string> {
const hash = await crypto.subtle.digest({ name: 'SHA-512' }, data);
return arrayBufferToBase64(hash);
}
// High-level Operations // High-level Operations
export async function encryptDeviceName( export async function encryptDeviceName(
deviceName: string, deviceName: string,
identityPublic: ArrayBuffer identityPublic: ArrayBuffer
) { ) {
const plaintext = bytesFromString(deviceName); const plaintext = bytesFromString(deviceName);
const ephemeralKeyPair = await window.libsignal.KeyHelper.generateIdentityKeyP air(); const ephemeralKeyPair = await window.libsignal.KeyHelper.generateIdentityKeyP air();
const masterSecret = await window.libsignal.Curve.async.calculateAgreement( const masterSecret = await window.libsignal.Curve.async.calculateAgreement(
identityPublic, identityPublic,
skipping to change at line 176 skipping to change at line 183
return concatenateBytes(prefix, await encryptSymmetric(key, plaintext)); return concatenateBytes(prefix, await encryptSymmetric(key, plaintext));
} }
export async function decryptFile( export async function decryptFile(
staticPrivateKey: ArrayBuffer, staticPrivateKey: ArrayBuffer,
uniqueId: ArrayBuffer, uniqueId: ArrayBuffer,
data: ArrayBuffer data: ArrayBuffer
) { ) {
const ephemeralPublicKey = getFirstBytes(data, PUB_KEY_LENGTH); const ephemeralPublicKey = getFirstBytes(data, PUB_KEY_LENGTH);
const ciphertext = _getBytes(data, PUB_KEY_LENGTH, data.byteLength); const ciphertext = getBytes(data, PUB_KEY_LENGTH, data.byteLength);
const agreement = await window.libsignal.Curve.async.calculateAgreement( const agreement = await window.libsignal.Curve.async.calculateAgreement(
ephemeralPublicKey, ephemeralPublicKey,
staticPrivateKey staticPrivateKey
); );
const key = await hmacSha256(agreement, uniqueId); const key = await hmacSha256(agreement, uniqueId);
return decryptSymmetric(key, ciphertext); return decryptSymmetric(key, ciphertext);
} }
skipping to change at line 204 skipping to change at line 211
export async function deriveStorageItemKey( export async function deriveStorageItemKey(
storageServiceKey: ArrayBuffer, storageServiceKey: ArrayBuffer,
itemID: string itemID: string
) { ) {
return hmacSha256(storageServiceKey, bytesFromString(`Item_${itemID}`)); return hmacSha256(storageServiceKey, bytesFromString(`Item_${itemID}`));
} }
export async function deriveAccessKey(profileKey: ArrayBuffer) { export async function deriveAccessKey(profileKey: ArrayBuffer) {
const iv = getZeroes(12); const iv = getZeroes(12);
const plaintext = getZeroes(16); const plaintext = getZeroes(16);
const accessKey = await _encrypt_aes_gcm(profileKey, iv, plaintext); const accessKey = await encryptAesGcm(profileKey, iv, plaintext);
return getFirstBytes(accessKey, 16); return getFirstBytes(accessKey, 16);
} }
export async function getAccessKeyVerifier(accessKey: ArrayBuffer) { export async function getAccessKeyVerifier(accessKey: ArrayBuffer) {
const plaintext = getZeroes(32); const plaintext = getZeroes(32);
return hmacSha256(accessKey, plaintext); return hmacSha256(accessKey, plaintext);
} }
skipping to change at line 256 skipping to change at line 263
); );
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(key: ArrayBuffer, data: ArrayBuffer) { export async function decryptSymmetric(key: ArrayBuffer, data: ArrayBuffer) {
const iv = getZeroes(IV_LENGTH); const iv = getZeroes(IV_LENGTH);
const nonce = getFirstBytes(data, NONCE_LENGTH); const nonce = getFirstBytes(data, NONCE_LENGTH);
const cipherText = _getBytes( const cipherText = getBytes(
data, data,
NONCE_LENGTH, NONCE_LENGTH,
data.byteLength - NONCE_LENGTH - MAC_LENGTH data.byteLength - NONCE_LENGTH - MAC_LENGTH
); );
const theirMac = _getBytes(data, data.byteLength - MAC_LENGTH, MAC_LENGTH); const theirMac = getBytes(data, data.byteLength - MAC_LENGTH, MAC_LENGTH);
const cipherKey = await hmacSha256(key, nonce); const cipherKey = await hmacSha256(key, nonce);
const macKey = await hmacSha256(key, cipherKey); const macKey = await hmacSha256(key, cipherKey);
const ourMac = getFirstBytes( const ourMac = getFirstBytes(
await hmacSha256(macKey, cipherText), await hmacSha256(macKey, cipherText),
MAC_LENGTH MAC_LENGTH
); );
if (!constantTimeEqual(theirMac, ourMac)) { if (!constantTimeEqual(theirMac, ourMac)) {
throw new Error( throw new Error(
skipping to change at line 416 skipping to change at line 423
); );
const plaintext = await crypto.subtle.decrypt( const plaintext = await crypto.subtle.decrypt(
algorithm, algorithm,
cryptoKey, cryptoKey,
ciphertext ciphertext
); );
return plaintext; return plaintext;
} }
export async function _encrypt_aes_gcm( export async function encryptAesGcm(
key: ArrayBuffer, key: ArrayBuffer,
iv: ArrayBuffer, iv: ArrayBuffer,
plaintext: ArrayBuffer plaintext: ArrayBuffer,
additionalData?: ArrayBuffer
) { ) {
const algorithm = { const algorithm = {
name: 'AES-GCM', name: 'AES-GCM',
iv, iv,
...(additionalData ? { additionalData } : {}),
}; };
const extractable = false; const extractable = false;
const cryptoKey = await crypto.subtle.importKey( const cryptoKey = await crypto.subtle.importKey(
'raw', 'raw',
key, key,
algorithm as any, algorithm as any,
extractable, extractable,
['encrypt'] ['encrypt']
); );
return crypto.subtle.encrypt(algorithm, cryptoKey, plaintext); return crypto.subtle.encrypt(algorithm, cryptoKey, plaintext);
} }
export async function decryptAesGcm(
key: ArrayBuffer,
iv: ArrayBuffer,
ciphertext: ArrayBuffer,
additionalData?: ArrayBuffer
) {
const algorithm = {
name: 'AES-GCM',
iv,
...(additionalData ? { additionalData } : {}),
tagLength: 128,
};
const extractable = false;
const cryptoKey = await crypto.subtle.importKey(
'raw',
key,
algorithm as any,
extractable,
['decrypt']
);
return crypto.subtle.decrypt(algorithm, cryptoKey, ciphertext);
}
// Hashing
export async function sha256(data: ArrayBuffer) {
return crypto.subtle.digest('SHA-256', data);
}
// Utility // Utility
export function getRandomBytes(n: number) { export function getRandomBytes(n: number) {
const bytes = new Uint8Array(n); const bytes = new Uint8Array(n);
window.crypto.getRandomValues(bytes); window.crypto.getRandomValues(bytes);
return typedArrayToArrayBuffer(bytes); return typedArrayToArrayBuffer(bytes);
} }
export function getRandomValue(low: number, high: number): number { export function getRandomValue(low: number, high: number): number {
skipping to change at line 553 skipping to change at line 594
return results; return results;
} }
export function getFirstBytes(data: ArrayBuffer, n: number) { export function getFirstBytes(data: ArrayBuffer, n: number) {
const source = new Uint8Array(data); const source = new Uint8Array(data);
return typedArrayToArrayBuffer(source.subarray(0, n)); return typedArrayToArrayBuffer(source.subarray(0, n));
} }
// Internal-only export function getBytes(
export function _getBytes(
data: ArrayBuffer | Uint8Array, data: ArrayBuffer | Uint8Array,
start: number, start: number,
n: number n: number
) { ) {
const source = new Uint8Array(data); const source = new Uint8Array(data);
return typedArrayToArrayBuffer(source.subarray(start, start + n)); return typedArrayToArrayBuffer(source.subarray(start, start + n));
} }
function _getMacAndData(ciphertext: ArrayBuffer) {
const dataLength = ciphertext.byteLength - MAC_LENGTH;
const data = getBytes(ciphertext, 0, dataLength);
const mac = getBytes(ciphertext, dataLength, MAC_LENGTH);
return { data, mac };
}
export async function encryptCdsDiscoveryRequest(
attestations: {
[key: string]: { clientKey: ArrayBuffer; requestId: ArrayBuffer };
},
phoneNumbers: ReadonlyArray<string>
) {
const nonce = getRandomBytes(32);
const numbersArray = new window.dcodeIO.ByteBuffer(
phoneNumbers.length * 8,
window.dcodeIO.ByteBuffer.BIG_ENDIAN
);
phoneNumbers.forEach(number => {
// Long.fromString handles numbers with or without a leading '+'
numbersArray.writeLong(window.dcodeIO.ByteBuffer.Long.fromString(number));
});
const queryDataPlaintext = concatenateBytes(nonce, numbersArray.buffer);
const queryDataKey = getRandomBytes(32);
const commitment = await sha256(queryDataPlaintext);
const iv = getRandomBytes(12);
const queryDataCiphertext = await encryptAesGcm(
queryDataKey,
iv,
queryDataPlaintext
);
const {
data: queryDataCiphertextData,
mac: queryDataCiphertextMac,
} = _getMacAndData(queryDataCiphertext);
const envelopes = await pProps(
attestations,
async ({ clientKey, requestId }) => {
const envelopeIv = getRandomBytes(12);
const ciphertext = await encryptAesGcm(
clientKey,
envelopeIv,
queryDataKey,
requestId
);
const { data, mac } = _getMacAndData(ciphertext);
return {
requestId: arrayBufferToBase64(requestId),
data: arrayBufferToBase64(data),
iv: arrayBufferToBase64(envelopeIv),
mac: arrayBufferToBase64(mac),
};
}
);
return {
addressCount: phoneNumbers.length,
commitment: arrayBufferToBase64(commitment),
data: arrayBufferToBase64(queryDataCiphertextData),
iv: arrayBufferToBase64(iv),
mac: arrayBufferToBase64(queryDataCiphertextMac),
envelopes,
};
}
export function splitUuids(arrayBuffer: ArrayBuffer) {
const uuids = [];
for (let i = 0; i < arrayBuffer.byteLength; i += 16) {
const bytes = getBytes(arrayBuffer, i, 16);
const hex = arrayBufferToHex(bytes);
const chunks = [
hex.substring(0, 8),
hex.substring(8, 12),
hex.substring(12, 16),
hex.substring(16, 20),
hex.substring(20),
];
const uuid = chunks.join('-');
if (uuid !== '00000000-0000-0000-0000-000000000000') {
uuids.push(uuid);
} else {
uuids.push(null);
}
}
return uuids;
}
 End of changes. 14 change blocks. 
10 lines changed or deleted 49 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)