import { EncryptionType } from '../types/EncryptionType'; import { SignalService } from '../../protobuf'; import { UserUtil } from '../../util'; import { CipherTextObject } from '../../../libtextsecure/libsignal-protocol'; import { encryptWithSenderKey } from '../../session/medium_group/ratchet'; import { PubKey } from '../types'; import { StringUtils } from '../utils'; /** * Add padding to a message buffer * @param messageBuffer The buffer to add padding to. */ export function padPlainTextBuffer(messageBuffer: Uint8Array): Uint8Array { const plaintext = new Uint8Array( getPaddedMessageLength(messageBuffer.byteLength + 1) - 1 ); plaintext.set(new Uint8Array(messageBuffer)); plaintext[messageBuffer.byteLength] = 0x80; return plaintext; } function getPaddedMessageLength(originalLength: number): number { const messageLengthWithTerminator = originalLength + 1; let messagePartCount = Math.floor(messageLengthWithTerminator / 160); if (messageLengthWithTerminator % 160 !== 0) { messagePartCount += 1; } return messagePartCount * 160; } type EncryptResult = { envelopeType: SignalService.Envelope.Type; cipherText: Uint8Array; }; /** * Encrypt `plainTextBuffer` with given `encryptionType` for `device`. * * @param device The device `PubKey` to encrypt for. * @param plainTextBuffer The unpadded plaintext buffer. * @param encryptionType The type of encryption. * @returns The envelope type and the base64 encoded cipher text */ export async function encrypt( device: PubKey, plainTextBuffer: Uint8Array, encryptionType: EncryptionType ): Promise { const plainText = padPlainTextBuffer(plainTextBuffer); if (encryptionType === EncryptionType.MediumGroup) { return encryptForMediumGroup(device, plainText); } const address = new window.libsignal.SignalProtocolAddress(device.key, 1); let innerCipherText: CipherTextObject; if (encryptionType === EncryptionType.Fallback) { const cipher = new window.libloki.crypto.FallBackSessionCipher(address); innerCipherText = await cipher.encrypt(plainText.buffer); } else { const cipher = new window.libsignal.SessionCipher( window.textsecure.storage.protocol, address ); innerCipherText = await cipher.encrypt(plainText.buffer); } return encryptUsingSealedSender(device, innerCipherText); } export async function encryptForMediumGroup( device: PubKey, plainTextBuffer: Uint8Array ): Promise { const ourKey = (await UserUtil.getCurrentDevicePubKey()) as string; // "Device" does not really make sense for medium groups, but // that's where the group pubkey is currently stored const groupId = device.key; const { ciphertext, keyIdx } = await encryptWithSenderKey( plainTextBuffer, groupId, ourKey ); // We should include ciphertext idx in the message const content = SignalService.MediumGroupCiphertext.encode({ ciphertext, source: new Uint8Array(StringUtils.encode(ourKey, 'hex')), keyIdx, }).finish(); // Encrypt for the group's identity key to hide source and key idx: const { ciphertext: ciphertextOuter, ephemeralKey, } = await window.libloki.crypto.encryptForPubkey(groupId, content); const contentOuter = SignalService.MediumGroupContent.encode({ ciphertext: ciphertextOuter, ephemeralKey: new Uint8Array(ephemeralKey), }).finish(); const envelopeType = SignalService.Envelope.Type.MEDIUM_GROUP_CIPHERTEXT; return { envelopeType, cipherText: contentOuter }; } async function encryptUsingSealedSender( device: PubKey, innerCipherText: CipherTextObject ): Promise<{ envelopeType: SignalService.Envelope.Type; cipherText: Uint8Array; }> { const ourNumber = await UserUtil.getCurrentDevicePubKey(); if (!ourNumber) { throw new Error('Failed to fetch current device public key.'); } const certificate = SignalService.SenderCertificate.create({ sender: ourNumber, senderDevice: 1, }); const cipher = new window.Signal.Metadata.SecretSessionCipher( window.textsecure.storage.protocol ); const cipherTextBuffer = await cipher.encrypt( device.key, certificate, innerCipherText ); return { envelopeType: SignalService.Envelope.Type.UNIDENTIFIED_SENDER, cipherText: new Uint8Array(cipherTextBuffer), }; }