diff --git a/.eslintignore b/.eslintignore index ff968ce0e..6ef379a1c 100644 --- a/.eslintignore +++ b/.eslintignore @@ -12,6 +12,7 @@ test/views/*.js # Generated files js/components.js js/libtextsecure.js +js/libloki.js js/util_worker.js js/libsignal-protocol-worker.js libtextsecure/components.js diff --git a/.gitignore b/.gitignore index b1b7020d1..586046d86 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ sql/ js/components.js js/util_worker.js js/libtextsecure.js +js/libloki.js libtextsecure/components.js libtextsecure/test/test.js stylesheets/*.css diff --git a/.prettierignore b/.prettierignore index 756580e19..e6db8458c 100644 --- a/.prettierignore +++ b/.prettierignore @@ -8,6 +8,7 @@ dist/** js/components.js js/util_worker.js js/libtextsecure.js +js/libloki.js libtextsecure/components.js libtextsecure/test/test.js stylesheets/*.css diff --git a/Gruntfile.js b/Gruntfile.js index 77815ff86..b05512dac 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -86,6 +86,12 @@ module.exports = grunt => { ], dest: 'js/libtextsecure.js', }, + libloki: { + src: [ + 'libloki/libloki-protocol.js', + ], + dest: 'js/libloki.js', + }, libtextsecuretest: { src: [ 'node_modules/jquery/dist/jquery.js', @@ -128,6 +134,10 @@ module.exports = grunt => { files: ['./libtextsecure/*.js', './libtextsecure/storage/*.js'], tasks: ['concat:libtextsecure'], }, + libloki: { + files: ['./libloki/*.js'], + tasks: ['concat:libloki'], + }, protobuf: { files: ['./protos/SignalService.proto'], tasks: ['exec:build-protobuf'], diff --git a/background.html b/background.html index ac4c1869c..8af81e984 100644 --- a/background.html +++ b/background.html @@ -584,6 +584,7 @@ + diff --git a/libloki/libloki-protocol.js b/libloki/libloki-protocol.js new file mode 100644 index 000000000..5cf9e5cfe --- /dev/null +++ b/libloki/libloki-protocol.js @@ -0,0 +1,47 @@ +/* global window, libsignal, textsecure */ + +// eslint-disable-next-line func-names +(function() { + window.libloki = window.libloki || {}; + + const IV_LENGTH = 16; + + FallBackSessionCipher = function (address) { + this.pubKey = StringView.hexToArrayBuffer(address.getName()); + + this.encrypt = async (plaintext) => { + const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair(); + const myPrivateKey = myKeyPair.privKey; + const symmetricKey = libsignal.Curve.calculateAgreement(this.pubKey, myPrivateKey); + const iv = libsignal.crypto.getRandomBytes(IV_LENGTH); + const ciphertext = await libsignal.crypto.encrypt(symmetricKey, plaintext, iv); + const ivAndCiphertext = new Uint8Array( + iv.byteLength + ciphertext.byteLength + ); + ivAndCiphertext.set(new Uint8Array(iv)); + ivAndCiphertext.set( + new Uint8Array(ciphertext), + iv.byteLength + ); + + return { + type : 6, //friend request + body : new dcodeIO.ByteBuffer.wrap(ivAndCiphertext).toString('binary'), + registrationId : null + }; + }, + + this.decrypt = async (ivAndCiphertext) => { + const iv = ivAndCiphertext.slice(0, IV_LENGTH); + const cipherText = ivAndCiphertext.slice(IV_LENGTH); + const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair(); + const myPrivateKey = myKeyPair.privKey; + const symmetricKey = libsignal.Curve.calculateAgreement(this.pubKey, myPrivateKey); + const plaintext = await libsignal.crypto.decrypt(symmetricKey, cipherText, iv); + return plaintext; + } + } + + window.libloki.FallBackSessionCipher = FallBackSessionCipher; + +})(); \ No newline at end of file diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index 7217c0f36..6381a173c 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -646,10 +646,17 @@ MessageReceiver.prototype.extend({ case textsecure.protobuf.Envelope.Type.CIPHERTEXT: window.log.info('message from', this.getEnvelopeId(envelope)); promise = Promise.resolve(ciphertext.toArrayBuffer())//;sessionCipher - // TODO: restore decryption & unpadding + // TODO: restore decryption & unpadding (?) //.decryptWhisperMessage(ciphertext) //.then(this.unpad); break; + case textsecure.protobuf.Envelope.Type.FRIEND_REQUEST: + window.log.info('friend-request message from ', envelope.source) + const fallBackSessionCipher = new libloki.FallBackSessionCipher( + address + ); + promise = fallBackSessionCipher.decrypt(ciphertext.toArrayBuffer()); + break; case textsecure.protobuf.Envelope.Type.PREKEY_BUNDLE: window.log.info('prekey message from', this.getEnvelopeId(envelope)); promise = this.decryptPreKeyWhisperMessage( diff --git a/libtextsecure/outgoing_message.js b/libtextsecure/outgoing_message.js index 25915c630..723e8c7bd 100644 --- a/libtextsecure/outgoing_message.js +++ b/libtextsecure/outgoing_message.js @@ -1,4 +1,4 @@ -/* global textsecure, libsignal, window, btoa */ +/* global textsecure, libsignal, window, btoa, libloki */ /* eslint-disable more/no-then */ @@ -209,7 +209,7 @@ OutgoingMessage.prototype = { content: outgoingObject.content, }); const requestMessage = new textsecure.protobuf.WebSocketRequestMessage({ - id: new Uint8Array(libsignal.crypto.getRandomBytes(1))[0], + id: new Uint8Array(libsignal.crypto.getRandomBytes(1))[0], // random ID for now verb: 'PUT', path: '/api/v1/message', body: messageEnvelope.encode().toArrayBuffer() @@ -219,6 +219,7 @@ OutgoingMessage.prototype = { request: requestMessage }); const bytes = new Uint8Array(websocketMessage.encode().toArrayBuffer()) + bytes.toString(); // print bytes for debugging purposes: can be injected in mock socket server return bytes; }, doSendMessage(number, deviceIds, recurse) { @@ -239,32 +240,7 @@ OutgoingMessage.prototype = { let sessionCipher; if (this.fallBackEncryption) { - // TODO: move to own file? - FallBackSessionCipher = function (address) { - this.recipientPubKey = StringView.hexToArrayBuffer(address.getName()); - this.encrypt = async (plaintext) => { - const myKeyPair = await textsecure.storage.protocol.getIdentityKeyPair(); - const myPrivateKey = myKeyPair.privKey; - const symmetricKey = libsignal.Curve.calculateAgreement(this.recipientPubKey, myPrivateKey); - const iv = libsignal.crypto.getRandomBytes(16); - const ciphertext = await libsignal.crypto.encrypt(symmetricKey, plaintext, iv); - const ivAndCiphertext = new Uint8Array( - iv.byteLength + ciphertext.byteLength - ); - ivAndCiphertext.set(new Uint8Array(iv)); - ivAndCiphertext.set( - new Uint8Array(ciphertext), - iv.byteLength - ); - - return { - type : 6, //friend request - body : new dcodeIO.ByteBuffer.wrap(ivAndCiphertext).toString('binary'), - registrationId : null - }; - } - } - sessionCipher = new FallBackSessionCipher( + sessionCipher = new libloki.FallBackSessionCipher( address ); } else { diff --git a/mockup_servers/socket_server.py b/mockup_servers/socket_server.py index e35fd7861..47a0da0b6 100644 --- a/mockup_servers/socket_server.py +++ b/mockup_servers/socket_server.py @@ -13,9 +13,11 @@ async def hello(websocket, path): # protomessage = new textsecure.protobuf.WebSocketMessage({type: textsecure.protobuf.WebSocketMessage.Type.REQUEST, request: {id:99, verb:'PUT', path:'/api/v1/queue/empty', body:null }}) # new Uint8Array(protomessage.encode().toArrayBuffer()) message = bytes( - #[8, 1, 18, 70, 10, 3, 80, 85, 84, 18, 15, 47, 97, 112, 105, 47, 118, 49, 47, 109, 101, 115, 115, 97, 103, 101, 26, 44, 8, 1, 18, 15, 109, 121, 115, 111, 117, 114, 99, 101, 97, 100, 100, 114, 101, 115, 115, 56, 1, 40, 184, 151, 213, 221, 5, 66, 15, 10, 13, 10, 11, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 32, 99] [ - 8,1,18,117,10,3,80,85,84,18,15,47,97,112,105,47,118,49,47,109,101,115,115,97,103,101,26,91,8,1,18,66,48,53,55,51,57,102,51,54,55,50,100,55,57,52,51,56,101,57,53,53,97,55,99,99,55,55,56,52,100,98,97,53,101,97,52,98,102,56,50,55,52,54,54,53,55,55,51,99,97,102,51,101,97,98,55,48,97,50,98,57,100,98,102,101,50,99,56,1,40,0,66,15,10,13,10,11,104,101,108,108,111,32,119,111,114,108,100,32,99 + # "hello world" - unencrypted + #8,1,18,117,10,3,80,85,84,18,15,47,97,112,105,47,118,49,47,109,101,115,115,97,103,101,26,91,8,1,18,66,48,53,55,51,57,102,51,54,55,50,100,55,57,52,51,56,101,57,53,53,97,55,99,99,55,55,56,52,100,98,97,53,101,97,52,98,102,56,50,55,52,54,54,53,55,55,51,99,97,102,51,101,97,98,55,48,97,50,98,57,100,98,102,101,50,99,56,1,40,0,66,15,10,13,10,11,104,101,108,108,111,32,119,111,114,108,100,32,99 + # "test" - fall back encrypted + 8,1,18,140,1,10,3,80,85,84,18,15,47,97,112,105,47,118,49,47,109,101,115,115,97,103,101,26,113,8,6,18,66,48,53,51,102,48,101,57,56,54,53,97,100,101,54,97,100,57,48,48,97,54,99,101,51,98,98,54,101,102,97,99,102,102,102,97,98,99,50,56,49,101,53,97,50,102,100,102,54,101,97,49,51,57,98,51,48,51,50,49,55,57,57,97,97,50,99,56,1,40,181,202,171,141,229,44,66,32,147,127,63,203,38,142,133,120,28,115,7,150,230,26,166,28,182,199,199,182,11,101,80,48,252,232,108,164,8,236,98,50,32,150,1 ]) # created by executing in js: # dataMessage = new textsecure.protobuf.DataMessage({body: "hello world", attachments:[], contact:[]}) diff --git a/ts/util/lint/linter.ts b/ts/util/lint/linter.ts index c274aadb9..29bc5f939 100644 --- a/ts/util/lint/linter.ts +++ b/ts/util/lint/linter.ts @@ -52,6 +52,7 @@ const excludedFiles = [ // Generated files '^js/components.js', '^js/libtextsecure.js', + '^js/libloki.js', '^js/util_worker.js', '^libtextsecure/components.js', '^libtextsecure/test/test.js',