From 36762dbbf2edca3c390b5261c70c811322a3f73c Mon Sep 17 00:00:00 2001 From: Mikunj Date: Fri, 29 May 2020 16:30:28 +1000 Subject: [PATCH] Added libsignal-protocol typings. Added MessageEncrypter. --- libloki/crypto.js | 7 +- libtextsecure/libsignal-protocol.d.ts | 120 ++++++++++++++++++++++++++ ts/session/crypto/MessageEncrypter.ts | 57 ++++++++++-- ts/window.ts | 13 ++- 4 files changed, 185 insertions(+), 12 deletions(-) create mode 100644 libtextsecure/libsignal-protocol.d.ts diff --git a/libloki/crypto.js b/libloki/crypto.js index cbbaad074..e9a9988d2 100644 --- a/libloki/crypto.js +++ b/libloki/crypto.js @@ -149,10 +149,13 @@ myPrivateKey ); const ivAndCiphertext = await DHEncrypt(symmetricKey, plaintext); + const binaryIvAndCiphertext = dcodeIO.ByteBuffer.wrap( + ivAndCiphertext + ).toString('binary'); return { type: textsecure.protobuf.Envelope.Type.FRIEND_REQUEST, - body: ivAndCiphertext, - registrationId: null, + body: binaryIvAndCiphertext, + registrationId: undefined, }; } diff --git a/libtextsecure/libsignal-protocol.d.ts b/libtextsecure/libsignal-protocol.d.ts new file mode 100644 index 000000000..d8ae0db3e --- /dev/null +++ b/libtextsecure/libsignal-protocol.d.ts @@ -0,0 +1,120 @@ +import { SignalService } from '../ts/protobuf'; + +export type BinaryString = String; + +export type CipherTextObject = { + type: SignalService.Envelope.Type; + body: BinaryString; + registrationId?: number; +}; + +export declare class SignalProtocolAddress { + constructor(hexEncodedPublicKey: string, deviceId: number); + getName(): string; + getDeviceId(): number; + toString(): string; + equals(other: SignalProtocolAddress): boolean; + static fromString(encodedAddress: string): SignalProtocolAddress; +} + +export type KeyPair = { + pubKey: ArrayBuffer; + privKey: ArrayBuffer; +}; + +interface CurveSync { + generateKeyPair(): KeyPair; + createKeyPair(privKey: ArrayBuffer): KeyPair; + calculateAgreement(pubKey: ArrayBuffer, privKey: ArrayBuffer): ArrayBuffer; + verifySignature(pubKey: ArrayBuffer, msg: ArrayBuffer, sig: ArrayBuffer); + calculateSignature(privKey: ArrayBuffer, message: ArrayBuffer): ArrayBuffer; + validatePubKeyFormat(pubKey: ArrayBuffer): ArrayBuffer; +} + +interface CurveAsync { + generateKeyPair(): Promise; + createKeyPair(privKey: ArrayBuffer): Promise; + calculateAgreement( + pubKey: ArrayBuffer, + privKey: ArrayBuffer + ): Promise; + verifySignature( + pubKey: ArrayBuffer, + msg: ArrayBuffer, + sig: ArrayBuffer + ): Promise; + calculateSignature( + privKey: ArrayBuffer, + message: ArrayBuffer + ): Promise; + validatePubKeyFormat(pubKey: ArrayBuffer): Promise; +} + +export interface CurveInterface extends CurveSync { + async: CurveAsync; +} + +export interface CryptoInterface { + encrypt( + key: ArrayBuffer, + data: ArrayBuffer, + iv: ArrayBuffer + ): Promise; + decrypt( + key: ArrayBuffer, + data: ArrayBuffer, + iv: ArrayBuffer + ): Promise; + calculateMAC(key: ArrayBuffer, data: ArrayBuffer): Promise; + verifyMAC( + data: ArrayBuffer, + key: ArrayBuffer, + mac: ArrayBuffer, + length: number + ): Promise; + getRandomBytes(size: number): ArrayBuffer; +} + +export interface KeyHelperInterface { + generateIdentityKeyPair(): Promise; + generateRegistrationId(): number; + generateSignedPreKey( + identityKeyPair: KeyPair, + signedKeyId: number + ): Promise<{ + keyId: number; + keyPair: KeyPair; + signature: ArrayBuffer; + }>; + generatePreKey( + keyId: number + ): Promise<{ + keyId: number; + keyPair: KeyPair; + }>; +} + +export declare class SessionCipher { + constructor(storage: any, remoteAddress: SignalProtocolAddress); + /** + * @returns The envelope type, registration id and binary encoded encrypted body. + */ + encrypt(buffer: ArrayBuffer | Uint8Array): Promise; + decryptPreKeyWhisperMessage( + buffer: ArrayBuffer | Uint8Array + ): Promise; + decryptWhisperMessage(buffer: ArrayBuffer | Uint8Array): Promise; + getRecord(encodedNumber: string): Promise; + getRemoteRegistrationId(): Promise; + hasOpenSession(): Promise; + closeOpenSessionForDevice(): Promise; + deleteAllSessionsForDevice(): Promise; +} + +export interface LibsignalProtocol { + SignalProtocolAddress: typeof SignalProtocolAddress; + Curve: CurveInterface; + crypto: CryptoInterface; + KeyHelper: KeyHelperInterface; + SessionCipher: typeof SessionCipher; +} diff --git a/ts/session/crypto/MessageEncrypter.ts b/ts/session/crypto/MessageEncrypter.ts index 22ce64ccc..d58a11f2f 100644 --- a/ts/session/crypto/MessageEncrypter.ts +++ b/ts/session/crypto/MessageEncrypter.ts @@ -1,5 +1,10 @@ import { EncryptionType } from '../types/EncryptionType'; import { SignalService } from '../../protobuf'; +import { libloki, libsignal, textsecure } from '../../window'; +import { + CipherTextObject, + SignalProtocolAddress, +} from '../../../libtextsecure/libsignal-protocol'; function padPlainTextBuffer(messageBuffer: Uint8Array): Uint8Array { const plaintext = new Uint8Array( @@ -22,19 +27,57 @@ function getPaddedMessageLength(originalLength: number): number { return messagePartCount * 160; } -export function encrypt( +export type Base64String = String; + +/** + * Encrypt `plainTextBuffer` with given `encryptionType` for `device`. + * + * @param device The device 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: string, plainTextBuffer: Uint8Array, encryptionType: EncryptionType -): { +): Promise<{ envelopeType: SignalService.Envelope.Type; - cipherText: Uint8Array; -} { + cipherText: Base64String; +}> { const plainText = padPlainTextBuffer(plainTextBuffer); - // TODO: Do encryption here? + const address = new libsignal.SignalProtocolAddress(device, 1); + + if (encryptionType === EncryptionType.MediumGroup) { + // TODO: Do medium group stuff here + throw new Error('Encryption is not yet supported'); + } + + let cipherText: CipherTextObject; + if (encryptionType === EncryptionType.SessionReset) { + const cipher = new libloki.crypto.FallBackSessionCipher(address); + cipherText = await cipher.encrypt(plainText.buffer); + } else { + const cipher = new libsignal.SessionCipher( + textsecure.storage.protocol, + address + ); + cipherText = await cipher.encrypt(plainText.buffer); + } + + return encryptUsingSealedSender(address, cipherText); +} +async function encryptUsingSealedSender( + address: SignalProtocolAddress, + cipherText: CipherTextObject +): Promise<{ + envelopeType: SignalService.Envelope.Type; + cipherText: Base64String; +}> { + // TODO: Do stuff here return { - envelopeType: SignalService.Envelope.Type.CIPHERTEXT, - cipherText: new Uint8Array(), + envelopeType: SignalService.Envelope.Type.UNIDENTIFIED_SENDER, + cipherText: 'implement me!', }; } diff --git a/ts/window.ts b/ts/window.ts index aeb19ec5d..cc5a28be3 100644 --- a/ts/window.ts +++ b/ts/window.ts @@ -1,6 +1,7 @@ import { LocalizerType } from './types/Util'; +import { LibsignalProtocol } from '../libtextsecure/libsignal-protocol'; -interface Window { +interface WindowInterface extends Window { seedNodeList: any; WebAPI: any; @@ -32,7 +33,7 @@ interface Window { shortenPubkey: any; dcodeIO: any; - libsignal: any; + libsignal: LibsignalProtocol; libloki: any; displayNameRegex: any; @@ -72,7 +73,9 @@ interface Window { resetDatabase: any; } -declare const window: Window; +declare const window: WindowInterface; + +// TODO: Is there an easier way to dynamically export these? // Utilities export const WebAPI = window.WebAPI; @@ -118,3 +121,7 @@ export const clearLocalData = window.clearLocalData; export const deleteAccount = window.deleteAccount; export const resetDatabase = window.resetDatabase; export const attemptConnection = window.attemptConnection; + +export const libloki = window.libloki; +export const libsignal = window.libsignal; +export const textsecure = window.textsecure;