2014-05-14 18:58:12 +00:00
/ * v i m : t s = 4 : s w = 4
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
* /
2014-05-17 05:53:58 +00:00
window . textsecure = window . textsecure || { } ;
2014-05-09 06:00:49 +00:00
2014-05-28 01:53:43 +00:00
window . textsecure . crypto = function ( ) {
2014-06-10 00:14:52 +00:00
'use strict' ;
2014-05-17 05:53:58 +00:00
var self = { } ;
2014-05-17 04:54:12 +00:00
// functions exposed for replacement and direct calling in test code
var testing _only = { } ;
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * Random constants / utils * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
2014-05-14 09:10:05 +00:00
// We consider messages lost after a week and might throw away keys at that point
2014-07-20 20:28:44 +00:00
// (also the time between signedPreKey regenerations)
2014-05-14 09:10:05 +00:00
var MESSAGE _LOST _THRESHOLD _MS = 1000 * 60 * 60 * 24 * 7 ;
2014-05-17 04:54:12 +00:00
var getRandomBytes = function ( size ) {
2014-06-10 00:14:52 +00:00
// At some point we might consider XORing in hashes of random
// UI events to strengthen ourselves against RNG flaws in crypto.getRandomValues
// ie maybe take a look at how Gibson does it at https://www.grc.com/r&d/js.htm
var array = new Uint8Array ( size ) ;
window . crypto . getRandomValues ( array ) ;
return array . buffer ;
2014-05-09 06:00:49 +00:00
}
2014-05-17 05:53:58 +00:00
self . getRandomBytes = getRandomBytes ;
2014-05-17 04:54:12 +00:00
function intToArrayBuffer ( nInt ) {
var res = new ArrayBuffer ( 16 ) ;
var thing = new Uint8Array ( res ) ;
thing [ 0 ] = ( nInt >> 24 ) & 0xff ;
thing [ 1 ] = ( nInt >> 16 ) & 0xff ;
thing [ 2 ] = ( nInt >> 8 ) & 0xff ;
thing [ 3 ] = ( nInt >> 0 ) & 0xff ;
return res ;
}
2014-05-09 06:00:49 +00:00
2014-05-21 02:21:07 +00:00
function objectContainsKeys ( object ) {
var count = 0 ;
2014-07-23 01:33:35 +00:00
for ( var key in object ) {
2014-05-21 02:21:07 +00:00
count ++ ;
break ;
}
return count != 0 ;
}
2014-05-09 07:43:23 +00:00
function HmacSHA256 ( key , input ) {
return window . crypto . subtle . sign ( { name : "HMAC" , hash : "SHA-256" } , key , input ) ;
}
2014-05-17 04:54:12 +00:00
testing _only . privToPub = function ( privKey , isIdentity ) {
2014-05-09 06:00:49 +00:00
if ( privKey . byteLength != 32 )
throw new Error ( "Invalid private key" ) ;
var prependVersion = function ( pubKey ) {
var origPub = new Uint8Array ( pubKey ) ;
var pub = new ArrayBuffer ( 33 ) ;
var pubWithPrefix = new Uint8Array ( pub ) ;
2014-05-13 05:51:46 +00:00
pubWithPrefix . set ( origPub , 1 ) ;
2014-05-09 06:00:49 +00:00
pubWithPrefix [ 0 ] = 5 ;
return pub ;
}
2014-05-17 05:53:58 +00:00
if ( textsecure . nacl . USE _NACL ) {
2014-05-17 05:55:32 +00:00
return textsecure . nacl . postNaclMessage ( { command : "bytesToPriv" , priv : privKey } ) . then ( function ( message ) {
2014-07-20 08:54:00 +00:00
var priv = message . res . slice ( 0 , 32 ) ;
2014-05-09 07:20:54 +00:00
if ( ! isIdentity )
new Uint8Array ( priv ) [ 0 ] |= 0x01 ;
2014-05-17 05:55:32 +00:00
return textsecure . nacl . postNaclMessage ( { command : "privToPub" , priv : priv } ) . then ( function ( message ) {
2014-07-20 08:54:00 +00:00
return { pubKey : prependVersion ( message . res . slice ( 0 , 32 ) ) , privKey : priv } ;
2014-05-09 06:00:49 +00:00
} ) ;
} ) ;
} else {
privKey = privKey . slice ( 0 ) ;
var priv = new Uint16Array ( privKey ) ;
priv [ 0 ] &= 0xFFF8 ;
priv [ 15 ] = ( priv [ 15 ] & 0x7FFF ) | 0x4000 ;
if ( ! isIdentity )
priv [ 0 ] |= 0x0001 ;
//TODO: fscking type conversion
2014-05-18 19:58:53 +00:00
return Promise . resolve ( { pubKey : prependVersion ( toArrayBuffer ( curve25519 ( priv ) ) ) , privKey : privKey } ) ;
2014-05-09 06:00:49 +00:00
}
}
2014-05-17 04:54:12 +00:00
var privToPub = function ( privKey , isIdentity ) { return testing _only . privToPub ( privKey , isIdentity ) ; }
2014-05-09 06:00:49 +00:00
2014-05-17 04:54:12 +00:00
testing _only . createNewKeyPair = function ( isIdentity ) {
return privToPub ( getRandomBytes ( 32 ) , isIdentity ) ;
2014-05-09 06:00:49 +00:00
}
2014-05-17 04:54:12 +00:00
var createNewKeyPair = function ( isIdentity ) { return testing _only . createNewKeyPair ( isIdentity ) ; }
2014-05-09 06:00:49 +00:00
2014-05-17 04:54:12 +00:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * Key / session storage * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * /
2014-05-09 06:00:49 +00:00
var crypto _storage = { } ;
2014-07-20 20:28:44 +00:00
crypto _storage . getNewStoredKeyPair = function ( keyName , isIdentity ) {
2014-05-09 06:00:49 +00:00
return createNewKeyPair ( isIdentity ) . then ( function ( keyPair ) {
2014-05-21 02:21:07 +00:00
textsecure . storage . putEncrypted ( "25519Key" + keyName , keyPair ) ;
2014-07-20 20:28:44 +00:00
return keyPair ;
2014-05-09 06:00:49 +00:00
} ) ;
}
crypto _storage . getStoredPubKey = function ( keyName ) {
2014-05-21 02:21:07 +00:00
return toArrayBuffer ( textsecure . storage . getEncrypted ( "25519Key" + keyName , { pubKey : undefined } ) . pubKey ) ;
2014-05-09 06:00:49 +00:00
}
crypto _storage . getStoredKeyPair = function ( keyName ) {
2014-05-21 02:21:07 +00:00
var res = textsecure . storage . getEncrypted ( "25519Key" + keyName ) ;
2014-05-09 06:00:49 +00:00
if ( res === undefined )
return undefined ;
return { pubKey : toArrayBuffer ( res . pubKey ) , privKey : toArrayBuffer ( res . privKey ) } ;
}
2014-07-21 02:55:07 +00:00
crypto _storage . removeStoredKeyPair = function ( keyName ) {
2014-05-21 02:21:07 +00:00
textsecure . storage . removeEncrypted ( "25519Key" + keyName ) ;
2014-05-09 06:00:49 +00:00
}
2014-07-23 06:49:47 +00:00
crypto _storage . getIdentityKey = function ( ) {
return this . getStoredKeyPair ( "identityKey" ) ;
2014-05-09 06:00:49 +00:00
}
2014-05-31 17:28:46 +00:00
crypto _storage . saveSession = function ( encodedNumber , session , registrationId ) {
var device = textsecure . storage . devices . getDeviceObject ( encodedNumber ) ;
if ( device === undefined )
device = { sessions : { } , encodedNumber : encodedNumber } ;
if ( device . sessions === undefined )
device . sessions = { } ;
if ( registrationId !== undefined )
device . registrationId = registrationId ;
var sessions = device . sessions ;
2014-05-14 09:10:05 +00:00
var doDeleteSession = false ;
if ( session . indexInfo . closed == - 1 )
2014-05-31 17:28:46 +00:00
device . identityKey = session . indexInfo . remoteIdentityKey ;
2014-05-14 09:10:05 +00:00
else {
doDeleteSession = ( session . indexInfo . closed < ( new Date ( ) . getTime ( ) - MESSAGE _LOST _THRESHOLD _MS ) ) ;
if ( ! doDeleteSession ) {
var keysLeft = false ;
2014-07-23 01:33:35 +00:00
for ( var key in session ) {
2014-05-14 09:10:05 +00:00
if ( key != "indexInfo" && key != "indexInfo" && key != "oldRatchetList" ) {
keysLeft = true ;
break ;
}
}
doDeleteSession = ! keysLeft ;
}
}
2014-05-09 06:00:49 +00:00
2014-05-14 09:10:05 +00:00
if ( doDeleteSession )
delete sessions [ getString ( session . indexInfo . baseKey ) ] ;
else
sessions [ getString ( session . indexInfo . baseKey ) ] = session ;
2014-05-31 17:28:46 +00:00
textsecure . storage . devices . saveDeviceObject ( device ) ;
}
var getSessions = function ( encodedNumber ) {
var device = textsecure . storage . devices . getDeviceObject ( encodedNumber ) ;
if ( device === undefined || device . sessions === undefined )
return undefined ;
return device . sessions ;
2014-05-09 06:00:49 +00:00
}
2014-05-14 21:20:49 +00:00
crypto _storage . getOpenSession = function ( encodedNumber ) {
2014-05-31 17:28:46 +00:00
var sessions = getSessions ( encodedNumber ) ;
2014-05-14 09:10:05 +00:00
if ( sessions === undefined )
return undefined ;
2014-07-23 01:33:35 +00:00
for ( var key in sessions )
2014-05-14 21:20:49 +00:00
if ( sessions [ key ] . indexInfo . closed == - 1 )
return sessions [ key ] ;
return undefined ;
}
crypto _storage . getSessionByRemoteEphemeralKey = function ( encodedNumber , remoteEphemeralKey ) {
2014-05-31 17:28:46 +00:00
var sessions = getSessions ( encodedNumber ) ;
2014-05-14 21:20:49 +00:00
if ( sessions === undefined )
return undefined ;
var searchKey = getString ( remoteEphemeralKey ) ;
2014-05-14 09:10:05 +00:00
var openSession = undefined ;
2014-07-23 01:33:35 +00:00
for ( var key in sessions ) {
2014-05-14 09:10:05 +00:00
if ( sessions [ key ] . indexInfo . closed == - 1 ) {
if ( openSession !== undefined )
throw new Error ( "Datastore inconsistensy: multiple open sessions for " + encodedNumber ) ;
openSession = sessions [ key ] ;
}
if ( sessions [ key ] [ searchKey ] !== undefined )
return sessions [ key ] ;
}
if ( openSession !== undefined )
return openSession ;
2014-05-14 21:20:49 +00:00
return undefined ;
}
crypto _storage . getSessionOrIdentityKeyByBaseKey = function ( encodedNumber , baseKey ) {
2014-05-31 17:28:46 +00:00
var sessions = getSessions ( encodedNumber ) ;
var device = textsecure . storage . devices . getDeviceObject ( encodedNumber ) ;
if ( device === undefined )
2014-05-14 21:20:49 +00:00
return undefined ;
2014-05-31 17:28:46 +00:00
var preferredSession = device . sessions && device . sessions [ getString ( baseKey ) ] ;
2014-05-14 21:20:49 +00:00
if ( preferredSession !== undefined )
return preferredSession ;
2014-05-31 17:28:46 +00:00
if ( device . identityKey !== undefined )
return { indexInfo : { remoteIdentityKey : device . identityKey } } ;
2014-05-14 09:10:05 +00:00
2014-05-31 17:28:46 +00:00
throw new Error ( "Datastore inconsistency: device was stored without identity key" ) ;
2014-05-14 09:10:05 +00:00
}
2014-05-09 06:00:49 +00:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * Internal Crypto stuff * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
2014-07-21 02:55:07 +00:00
var validatePubKeyFormat = function ( pubKey , needVersionByte ) {
2014-05-13 08:40:29 +00:00
if ( pubKey === undefined || ( ( pubKey . byteLength != 33 || new Uint8Array ( pubKey ) [ 0 ] != 5 ) && pubKey . byteLength != 32 ) )
2014-05-13 05:51:46 +00:00
throw new Error ( "Invalid public key" ) ;
2014-07-21 02:55:07 +00:00
if ( pubKey . byteLength == 33 ) {
if ( ! needVersionByte )
return pubKey . slice ( 1 ) ;
else
return pubKey ;
} else {
2014-05-13 08:40:29 +00:00
console . error ( "WARNING: Expected pubkey of length 33, please report the ST and client that generated the pubkey" ) ;
2014-07-21 02:55:07 +00:00
if ( ! needVersionByte )
return pubKey ;
var res = new Uint8Array ( 33 ) ;
res [ 0 ] = 5 ;
res . set ( new Uint8Array ( pubKey ) , 1 ) ;
return res . buffer ;
}
2014-07-20 08:54:00 +00:00
}
testing _only . ECDHE = function ( pubKey , privKey ) {
if ( privKey === undefined || privKey . byteLength != 32 )
throw new Error ( "Invalid private key" ) ;
2014-07-21 02:55:07 +00:00
pubKey = validatePubKeyFormat ( pubKey , false ) ;
2014-05-09 06:00:49 +00:00
return new Promise ( function ( resolve ) {
2014-05-17 05:53:58 +00:00
if ( textsecure . nacl . USE _NACL ) {
2014-05-17 05:55:32 +00:00
textsecure . nacl . postNaclMessage ( { command : "ECDHE" , priv : privKey , pub : pubKey } ) . then ( function ( message ) {
2014-07-20 08:54:00 +00:00
resolve ( message . res . slice ( 0 , 32 ) ) ;
2014-05-09 06:00:49 +00:00
} ) ;
} else {
2014-05-13 08:40:29 +00:00
resolve ( toArrayBuffer ( curve25519 ( new Uint16Array ( privKey ) , new Uint16Array ( pubKey ) ) ) ) ;
2014-05-09 06:00:49 +00:00
}
} ) ;
}
2014-05-17 04:54:12 +00:00
var ECDHE = function ( pubKey , privKey ) { return testing _only . ECDHE ( pubKey , privKey ) ; }
2014-05-09 06:00:49 +00:00
2014-07-20 08:54:00 +00:00
testing _only . Ed25519Sign = function ( privKey , message ) {
if ( privKey === undefined || privKey . byteLength != 32 )
throw new Error ( "Invalid private key" ) ;
if ( message === undefined )
throw new Error ( "Invalid message" ) ;
if ( textsecure . nacl . USE _NACL ) {
return textsecure . nacl . postNaclMessage ( { command : "Ed25519Sign" , priv : privKey , msg : message } ) . then ( function ( message ) {
return message . res ;
} ) ;
} else {
throw new Error ( "Ed25519 in JS not yet supported" ) ;
}
}
var Ed25519Sign = function ( privKey , pubKeyToSign ) {
2014-07-21 02:55:07 +00:00
pubKeyToSign = validatePubKeyFormat ( pubKeyToSign , true ) ;
2014-07-20 08:54:00 +00:00
return testing _only . Ed25519Sign ( privKey , pubKeyToSign ) ;
}
testing _only . Ed25519Verify = function ( pubKey , msg , sig ) {
2014-07-21 02:55:07 +00:00
pubKey = validatePubKeyFormat ( pubKey , false ) ;
2014-07-20 08:54:00 +00:00
if ( msg === undefined )
throw new Error ( "Invalid message" ) ;
if ( sig === undefined || sig . byteLength != 64 )
throw new Error ( "Invalid signature" ) ;
if ( textsecure . nacl . USE _NACL ) {
return textsecure . nacl . postNaclMessage ( { command : "Ed25519Verify" , pub : pubKey , msg : msg , sig : sig } ) . then ( function ( message ) {
if ( ! message . res )
throw new Error ( "Invalid signature" ) ;
} ) ;
} else {
throw new Error ( "Ed25519 in JS not yet supported" ) ;
}
}
var Ed25519Verify = function ( pubKey , signedPubKey , sig ) {
2014-07-21 02:55:07 +00:00
signedPubKey = validatePubKeyFormat ( signedPubKey , true ) ;
2014-07-20 08:54:00 +00:00
return testing _only . Ed25519Verify ( pubKey , signedPubKey , sig ) ;
}
2014-05-17 04:54:12 +00:00
testing _only . HKDF = function ( input , salt , info ) {
2014-05-09 06:00:49 +00:00
// Specific implementation of RFC 5869 that only returns exactly 64 bytes
2014-05-13 05:51:46 +00:00
return HmacSHA256 ( salt , input ) . then ( function ( PRK ) {
var infoBuffer = new ArrayBuffer ( info . byteLength + 1 + 32 ) ;
var infoArray = new Uint8Array ( infoBuffer ) ;
infoArray . set ( new Uint8Array ( info ) , 32 ) ;
2014-07-20 20:31:47 +00:00
infoArray [ infoArray . length - 1 ] = 1 ;
2014-05-09 06:00:49 +00:00
// TextSecure implements a slightly tweaked version of RFC 5869: the 0 and 1 should be 1 and 2 here
2014-05-13 05:51:46 +00:00
return HmacSHA256 ( PRK , infoBuffer . slice ( 32 ) ) . then ( function ( T1 ) {
infoArray . set ( new Uint8Array ( T1 ) ) ;
2014-07-20 20:31:47 +00:00
infoArray [ infoArray . length - 1 ] = 2 ;
2014-05-13 05:51:46 +00:00
return HmacSHA256 ( PRK , infoBuffer ) . then ( function ( T2 ) {
2014-07-24 09:14:38 +00:00
return [ T1 , T2 ] ;
2014-05-09 06:00:49 +00:00
} ) ;
} ) ;
} ) ;
}
var HKDF = function ( input , salt , info ) {
// HKDF for TextSecure has a bit of additional handling - salts always end up being 32 bytes
2014-05-13 05:51:46 +00:00
if ( salt == '' )
2014-05-09 06:00:49 +00:00
salt = new ArrayBuffer ( 32 ) ;
if ( salt . byteLength != 32 )
throw new Error ( "Got salt of incorrect length" ) ;
2014-05-13 05:51:46 +00:00
info = toArrayBuffer ( info ) ; // TODO: maybe convert calls?
2014-05-17 04:54:12 +00:00
return testing _only . HKDF ( input , salt , info ) ;
2014-05-09 06:00:49 +00:00
}
2014-05-13 05:51:46 +00:00
var calculateMACWithVersionByte = function ( data , key , version ) {
2014-05-09 06:00:49 +00:00
if ( version === undefined )
version = 1 ;
2014-05-13 05:51:46 +00:00
var prependedData = new Uint8Array ( data . byteLength + 1 ) ;
prependedData [ 0 ] = version ;
prependedData . set ( new Uint8Array ( data ) , 1 ) ;
2014-05-09 06:00:49 +00:00
2014-05-13 05:51:46 +00:00
return HmacSHA256 ( key , prependedData . buffer ) ;
2014-05-09 06:00:49 +00:00
}
2014-05-13 05:51:46 +00:00
var verifyMACWithVersionByte = function ( data , key , mac , version ) {
return calculateMACWithVersionByte ( data , key , version ) . then ( function ( calculated _mac ) {
2014-07-23 05:49:13 +00:00
if ( ! isEqual ( calculated _mac , mac , true ) )
2014-05-13 05:51:46 +00:00
throw new Error ( "Bad MAC" ) ;
} ) ;
2014-05-09 06:00:49 +00:00
}
2014-05-15 05:02:15 +00:00
var verifyMAC = function ( data , key , mac ) {
2014-07-17 22:13:32 +00:00
return HmacSHA256 ( key , data ) . then ( function ( calculated _mac ) {
2014-07-23 05:49:13 +00:00
if ( ! isEqual ( calculated _mac , mac , true ) )
2014-05-15 05:02:15 +00:00
throw new Error ( "Bad MAC" ) ;
} ) ;
}
2014-05-09 06:00:49 +00:00
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * Ratchet implementation * * *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
var calculateRatchet = function ( session , remoteKey , sending ) {
var ratchet = session . currentRatchet ;
2014-05-13 05:51:46 +00:00
return ECDHE ( remoteKey , toArrayBuffer ( ratchet . ephemeralKeyPair . privKey ) ) . then ( function ( sharedSecret ) {
return HKDF ( sharedSecret , toArrayBuffer ( ratchet . rootKey ) , "WhisperRatchet" ) . then ( function ( masterKey ) {
2014-05-09 06:00:49 +00:00
if ( sending )
session [ getString ( ratchet . ephemeralKeyPair . pubKey ) ] = { messageKeys : { } , chainKey : { counter : - 1 , key : masterKey [ 1 ] } } ;
else
session [ getString ( remoteKey ) ] = { messageKeys : { } , chainKey : { counter : - 1 , key : masterKey [ 1 ] } } ;
ratchet . rootKey = masterKey [ 0 ] ;
} ) ;
} ) ;
}
2014-07-21 02:55:07 +00:00
var initSession = function ( isInitiator , ourEphemeralKey , ourSignedKey , encodedNumber , theirIdentityPubKey , theirEphemeralPubKey , theirSignedPubKey ) {
2014-07-23 06:49:47 +00:00
var ourIdentityKey = crypto _storage . getIdentityKey ( ) ;
2014-05-09 06:00:49 +00:00
2014-07-21 02:55:07 +00:00
if ( isInitiator ) {
if ( ourSignedKey !== undefined )
throw new Error ( "Invalid call to initSession" ) ;
ourSignedKey = ourEphemeralKey ;
} else {
if ( theirSignedPubKey !== undefined )
throw new Error ( "Invalid call to initSession" ) ;
theirSignedPubKey = theirEphemeralPubKey ;
}
2014-07-24 09:14:38 +00:00
var sharedSecret ;
if ( ourEphemeralKey === undefined || theirEphemeralPubKey === undefined )
2014-07-21 02:55:07 +00:00
sharedSecret = new Uint8Array ( 32 * 4 ) ;
2014-07-24 09:14:38 +00:00
else
2014-07-21 02:55:07 +00:00
sharedSecret = new Uint8Array ( 32 * 5 ) ;
for ( var i = 0 ; i < 32 ; i ++ )
sharedSecret [ i ] = 0xff ;
2014-07-23 06:49:47 +00:00
return ECDHE ( theirSignedPubKey , ourIdentityKey . privKey ) . then ( function ( ecRes1 ) {
2014-05-09 06:00:49 +00:00
function finishInit ( ) {
2014-07-21 02:55:07 +00:00
return ECDHE ( theirSignedPubKey , ourSignedKey . privKey ) . then ( function ( ecRes ) {
sharedSecret . set ( new Uint8Array ( ecRes ) , 32 * 3 ) ;
2014-05-09 06:00:49 +00:00
2014-05-13 05:51:46 +00:00
return HKDF ( sharedSecret . buffer , '' , "WhisperText" ) . then ( function ( masterKey ) {
2014-07-24 09:14:38 +00:00
var session = { currentRatchet : { rootKey : masterKey [ 0 ] , lastRemoteEphemeralKey : theirSignedPubKey } ,
indexInfo : { remoteIdentityKey : theirIdentityPubKey , closed : - 1 } ,
oldRatchetList : [ ]
} ;
if ( ! isInitiator )
session . indexInfo . baseKey = theirEphemeralPubKey ;
else
session . indexInfo . baseKey = ourEphemeralKey . pubKey ;
// If we're initiating we go ahead and set our first sending ephemeral key now,
// otherwise we figure it out when we first maybeStepRatchet with the remote's ephemeral key
if ( isInitiator ) {
return createNewKeyPair ( false ) . then ( function ( ourSendingEphemeralKey ) {
session . currentRatchet . ephemeralKeyPair = ourSendingEphemeralKey ;
return calculateRatchet ( session , theirSignedPubKey , true ) . then ( function ( ) {
return session ;
2014-07-23 06:49:47 +00:00
} ) ;
2014-07-24 09:14:38 +00:00
} ) ;
} else {
session . currentRatchet . ephemeralKeyPair = ourSignedKey ;
return session ;
}
2014-05-09 06:00:49 +00:00
} ) ;
} ) ;
}
2014-07-21 02:55:07 +00:00
var promise ;
if ( ourEphemeralKey === undefined || theirEphemeralPubKey === undefined )
promise = Promise . resolve ( new ArrayBuffer ( 0 ) ) ;
2014-05-09 06:00:49 +00:00
else
2014-07-21 02:55:07 +00:00
promise = ECDHE ( theirEphemeralPubKey , ourEphemeralKey . privKey ) ;
return promise . then ( function ( ecRes4 ) {
sharedSecret . set ( new Uint8Array ( ecRes4 ) , 32 * 4 ) ;
2014-07-23 06:49:47 +00:00
2014-07-21 02:55:07 +00:00
if ( isInitiator )
return ECDHE ( theirIdentityPubKey , ourSignedKey . privKey ) . then ( function ( ecRes2 ) {
sharedSecret . set ( new Uint8Array ( ecRes1 ) , 32 ) ;
sharedSecret . set ( new Uint8Array ( ecRes2 ) , 32 * 2 ) ;
} ) . then ( finishInit ) ;
else
return ECDHE ( theirIdentityPubKey , ourSignedKey . privKey ) . then ( function ( ecRes2 ) {
sharedSecret . set ( new Uint8Array ( ecRes1 ) , 32 * 2 ) ;
sharedSecret . set ( new Uint8Array ( ecRes2 ) , 32 )
} ) . then ( finishInit ) ;
} ) ;
2014-05-09 06:00:49 +00:00
} ) ;
}
2014-05-14 09:10:05 +00:00
var closeSession = function ( session ) {
// Clear any data which would allow session continuation:
// Lock down current receive ratchet
2014-07-23 01:33:35 +00:00
for ( var key in session )
2014-06-10 00:14:52 +00:00
if ( key . chainKey !== undefined && key . chainKey . key !== undefined )
delete key . chainKey . key ;
2014-05-14 09:10:05 +00:00
// Delete current sending ratchet
2014-05-17 04:54:12 +00:00
delete session [ getString ( session . currentRatchet . ephemeralKeyPair . pubKey ) ] ;
2014-05-14 09:10:05 +00:00
// Delete current root key and our ephemeral key pair
delete session . currentRatchet [ 'rootKey' ] ;
delete session . currentRatchet [ 'ephemeralKeyPair' ] ;
session . indexInfo . closed = new Date ( ) . getTime ( ) ;
}
2014-05-09 06:00:49 +00:00
2014-06-03 21:44:30 +00:00
self . closeOpenSessionForDevice = function ( encodedNumber ) {
var session = crypto _storage . getOpenSession ( encodedNumber ) ;
if ( session === undefined )
return ;
closeSession ( session ) ;
crypto _storage . saveSession ( encodedNumber , session ) ;
}
2014-06-01 17:39:35 +00:00
var initSessionFromPreKeyWhisperMessage ;
var decryptWhisperMessage ;
var handlePreKeyWhisperMessage = function ( from , encodedMessage ) {
var preKeyProto = textsecure . protos . decodePreKeyWhisperMessageProtobuf ( encodedMessage ) ;
return initSessionFromPreKeyWhisperMessage ( from , preKeyProto ) . then ( function ( sessions ) {
return decryptWhisperMessage ( from , getString ( preKeyProto . message ) , sessions [ 0 ] , preKeyProto . registrationId ) . then ( function ( result ) {
if ( sessions [ 1 ] !== undefined )
2014-07-21 02:55:07 +00:00
sessions [ 1 ] ( ) ;
2014-06-01 17:39:35 +00:00
return result ;
} ) ;
} ) ;
}
var wipeIdentityAndTryMessageAgain = function ( from , encodedMessage ) {
//TODO: Wipe identity key!
return handlePreKeyWhisperMessage ( from , encodedMessage ) ;
}
textsecure . replay . registerReplayFunction ( wipeIdentityAndTryMessageAgain , textsecure . replay . REPLAY _FUNCS . INIT _SESSION ) ;
initSessionFromPreKeyWhisperMessage = function ( encodedNumber , message ) {
2014-07-21 02:55:07 +00:00
var preKeyPair = crypto _storage . getStoredKeyPair ( "preKey" + message . preKeyId ) ;
var signedPreKeyPair = crypto _storage . getStoredKeyPair ( "signedKey" + message . signedPreKeyId ) ;
2014-05-14 22:15:46 +00:00
2014-05-14 21:20:49 +00:00
var session = crypto _storage . getSessionOrIdentityKeyByBaseKey ( encodedNumber , toArrayBuffer ( message . baseKey ) ) ;
var open _session = crypto _storage . getOpenSession ( encodedNumber ) ;
2014-07-23 07:36:11 +00:00
if ( preKeyPair === undefined || signedPreKeyPair === undefined ) {
// Session may or may not be the right one, but if its not, we can't do anything about it
2014-05-14 09:10:05 +00:00
// ...fall through and let decryptWhisperMessage handle that case
if ( session !== undefined && session . currentRatchet !== undefined )
2014-05-14 18:27:08 +00:00
return Promise . resolve ( [ session , undefined ] ) ;
2014-07-23 07:36:11 +00:00
else if ( preKeyPair === undefined )
throw new Error ( "Missing PreKey for PreKeyWhisperMessage" ) ;
2014-05-09 06:00:49 +00:00
else
2014-07-23 07:36:11 +00:00
throw new Error ( "Missing Signed PreKey for PreKeyWhisperMessage" ) ;
2014-05-14 09:10:05 +00:00
}
if ( session !== undefined ) {
2014-07-23 07:36:11 +00:00
// Duplicate PreKeyMessage for session:
if ( isEqual ( session . indexInfo . baseKey , message . baseKey , false ) )
return Promise . resolve ( [ session , undefined ] ) ;
2014-05-14 21:20:49 +00:00
// We already had a session/known identity key:
2014-07-23 05:49:13 +00:00
if ( isEqual ( session . indexInfo . remoteIdentityKey , message . identityKey , false ) ) {
2014-05-14 09:10:05 +00:00
// If the identity key matches the previous one, close the previous one and use the new one
2014-05-14 21:20:49 +00:00
if ( open _session !== undefined )
closeSession ( open _session ) ; // To be returned and saved later
2014-05-14 09:10:05 +00:00
} else {
// ...otherwise create an error that the UI will pick up and ask the user if they want to re-negotiate
2014-06-01 17:39:35 +00:00
throw textsecure . createTryAgainError ( "Received message with unknown identity key" , "The identity of the sender has changed. This may be malicious, or the sender may have simply reinstalled TextSecure." , textsecure . replay . REPLAY _FUNCS . INIT _SESSION , [ encodedNumber , getString ( message . encode ( ) ) ] ) ;
2014-05-14 09:10:05 +00:00
}
}
2014-07-21 02:55:07 +00:00
return initSession ( false , preKeyPair , signedPreKeyPair , encodedNumber , toArrayBuffer ( message . identityKey ) , toArrayBuffer ( message . baseKey ) , undefined )
2014-07-24 09:14:38 +00:00
. then ( function ( new _sesion ) {
2014-05-14 09:10:05 +00:00
// Note that the session is not actually saved until the very end of decryptWhisperMessage
// ... to ensure that the sender actually holds the private keys for all reported pubkeys
2014-07-24 09:14:38 +00:00
return [ new _session , function ( ) {
2014-07-21 02:55:07 +00:00
if ( open _session !== undefined )
crypto _storage . saveSession ( encodedNumber , open _session ) ;
crypto _storage . removeStoredKeyPair ( "preKey" + message . preKeyId ) ;
} ] ;
2014-05-14 09:10:05 +00:00
} ) ; ;
2014-05-09 06:00:49 +00:00
}
var fillMessageKeys = function ( chain , counter ) {
if ( chain . chainKey . counter + 1000 < counter ) //TODO: maybe 1000 is too low/high in some cases?
2014-05-14 07:33:24 +00:00
return Promise . resolve ( ) ; // Stalker, much?
if ( chain . chainKey . counter >= counter )
return Promise . resolve ( ) ; // Already calculated
if ( chain . chainKey . key === undefined )
throw new Error ( "Got invalid request to extend chain after it was already closed" ) ;
var key = toArrayBuffer ( chain . chainKey . key ) ;
var byteArray = new Uint8Array ( 1 ) ;
byteArray [ 0 ] = 1 ;
return HmacSHA256 ( key , byteArray . buffer ) . then ( function ( mac ) {
byteArray [ 0 ] = 2 ;
return HmacSHA256 ( key , byteArray . buffer ) . then ( function ( key ) {
chain . messageKeys [ chain . chainKey . counter + 1 ] = mac ;
chain . chainKey . key = key
chain . chainKey . counter += 1 ;
return fillMessageKeys ( chain , counter ) ;
2014-05-09 06:00:49 +00:00
} ) ;
2014-05-14 07:33:24 +00:00
} ) ;
2014-05-09 06:00:49 +00:00
}
2014-05-14 07:02:47 +00:00
var removeOldChains = function ( session ) {
// Sending ratchets are always removed when we step because we never need them again
// Receiving ratchets are either removed if we step with all keys used up to previousCounter
// and are otherwise added to the oldRatchetList, which we parse here and remove ratchets
// older than a week (we assume the message was lost and move on with our lives at that point)
var newList = [ ] ;
for ( var i = 0 ; i < session . oldRatchetList . length ; i ++ ) {
var entry = session . oldRatchetList [ i ] ;
var ratchet = getString ( entry . ephemeralKey ) ;
console . log ( "Checking old chain with added time " + ( entry . added / 1000 ) ) ;
2014-05-14 09:10:05 +00:00
if ( ! objectContainsKeys ( session [ ratchet ] . messageKeys ) || entry . added < new Date ( ) . getTime ( ) - MESSAGE _LOST _THRESHOLD _MS ) {
2014-05-14 07:02:47 +00:00
delete session [ ratchet ] ;
console . log ( "...deleted" ) ;
} else
newList [ newList . length ] = entry ;
}
session . oldRatchetList = newList ;
}
2014-05-09 06:00:49 +00:00
var maybeStepRatchet = function ( session , remoteKey , previousCounter ) {
if ( session [ getString ( remoteKey ) ] !== undefined )
2014-05-14 07:33:24 +00:00
return Promise . resolve ( ) ;
2014-05-09 06:00:49 +00:00
var ratchet = session . currentRatchet ;
var finish = function ( ) {
return calculateRatchet ( session , remoteKey , false ) . then ( function ( ) {
// Now swap the ephemeral key and calculate the new sending chain
var previousRatchet = getString ( ratchet . ephemeralKeyPair . pubKey ) ;
if ( session [ previousRatchet ] !== undefined ) {
ratchet . previousCounter = session [ previousRatchet ] . chainKey . counter ;
2014-05-14 07:02:47 +00:00
delete session [ previousRatchet ] ;
2014-05-09 06:00:49 +00:00
} else
// TODO: This is just an idiosyncrasy upstream, which we match for testing
// it should be changed upstream to something more reasonable.
ratchet . previousCounter = 4294967295 ;
return createNewKeyPair ( false ) . then ( function ( keyPair ) {
ratchet . ephemeralKeyPair = keyPair ;
return calculateRatchet ( session , remoteKey , true ) . then ( function ( ) {
ratchet . lastRemoteEphemeralKey = remoteKey ;
} ) ;
} ) ;
} ) ;
}
var previousRatchet = session [ getString ( ratchet . lastRemoteEphemeralKey ) ] ;
if ( previousRatchet !== undefined ) {
return fillMessageKeys ( previousRatchet , previousCounter ) . then ( function ( ) {
2014-06-10 00:14:52 +00:00
delete previousRatchet . chainKey . key ;
2014-05-09 06:00:49 +00:00
if ( ! objectContainsKeys ( previousRatchet . messageKeys ) )
delete session [ getString ( ratchet . lastRemoteEphemeralKey ) ] ;
else
session . oldRatchetList [ session . oldRatchetList . length ] = { added : new Date ( ) . getTime ( ) , ephemeralKey : ratchet . lastRemoteEphemeralKey } ;
} ) . then ( finish ) ;
} else
return finish ( ) ;
}
// returns decrypted protobuf
2014-06-01 17:39:35 +00:00
decryptWhisperMessage = function ( encodedNumber , messageBytes , session , registrationId ) {
2014-07-21 02:55:07 +00:00
if ( messageBytes [ 0 ] != String . fromCharCode ( ( 3 << 4 ) | 3 ) )
2014-05-09 06:00:49 +00:00
throw new Error ( "Bad version number on WhisperMessage" ) ;
var messageProto = messageBytes . substring ( 1 , messageBytes . length - 8 ) ;
var mac = messageBytes . substring ( messageBytes . length - 8 , messageBytes . length ) ;
2014-05-21 19:04:05 +00:00
var message = textsecure . protos . decodeWhisperMessageProtobuf ( messageProto ) ;
2014-05-14 09:10:05 +00:00
var remoteEphemeralKey = toArrayBuffer ( message . ephemeralKey ) ;
2014-05-09 06:00:49 +00:00
2014-05-14 09:10:05 +00:00
if ( session === undefined ) {
2014-05-14 21:20:49 +00:00
var session = crypto _storage . getSessionByRemoteEphemeralKey ( encodedNumber , remoteEphemeralKey ) ;
2014-05-14 09:10:05 +00:00
if ( session === undefined )
throw new Error ( "No session found to decrypt message from " + encodedNumber ) ;
}
return maybeStepRatchet ( session , remoteEphemeralKey , message . previousCounter ) . then ( function ( ) {
2014-05-09 06:00:49 +00:00
var chain = session [ getString ( message . ephemeralKey ) ] ;
return fillMessageKeys ( chain , message . counter ) . then ( function ( ) {
2014-05-13 05:51:46 +00:00
return HKDF ( toArrayBuffer ( chain . messageKeys [ message . counter ] ) , '' , "WhisperMessageKeys" ) . then ( function ( keys ) {
2014-05-09 06:00:49 +00:00
delete chain . messageKeys [ message . counter ] ;
2014-07-24 10:29:11 +00:00
var messageProtoArray = toArrayBuffer ( messageProto ) ;
var macInput = new Uint8Array ( messageProtoArray . byteLength + 33 * 2 + 1 ) ;
macInput . set ( new Uint8Array ( toArrayBuffer ( session . indexInfo . remoteIdentityKey ) ) ) ;
macInput . set ( new Uint8Array ( toArrayBuffer ( crypto _storage . getIdentityKey ( ) . pubKey ) ) , 33 ) ;
macInput [ 33 * 2 ] = ( 3 << 4 ) | 3 ;
macInput . set ( new Uint8Array ( messageProtoArray ) , 33 * 2 + 1 ) ;
return verifyMAC ( macInput . buffer , keys [ 1 ] , mac ) . then ( function ( ) {
2014-05-13 05:51:46 +00:00
var counter = intToArrayBuffer ( message . counter ) ;
return window . crypto . subtle . decrypt ( { name : "AES-CTR" , counter : counter } , keys [ 0 ] , toArrayBuffer ( message . ciphertext ) )
2014-07-23 05:49:13 +00:00
. then ( function ( paddedPlaintext ) {
paddedPlaintext = new Uint8Array ( paddedPlaintext ) ;
var plaintext ;
for ( var i = paddedPlaintext . length - 1 ; i >= 0 ; i -- ) {
if ( paddedPlaintext [ i ] == 0x80 ) {
plaintext = new Uint8Array ( i ) ;
plaintext . set ( paddedPlaintext . subarray ( 0 , i ) ) ;
plaintext = plaintext . buffer ;
break ;
} else if ( paddedPlaintext [ i ] != 0x00 )
throw new Error ( 'Invalid padding' ) ;
}
2014-05-09 07:43:23 +00:00
2014-05-14 07:02:47 +00:00
removeOldChains ( session ) ;
2014-05-13 05:51:46 +00:00
delete session [ 'pendingPreKey' ] ;
2014-05-09 06:00:49 +00:00
2014-05-21 19:04:05 +00:00
var finalMessage = textsecure . protos . decodePushMessageContentProtobuf ( getString ( plaintext ) ) ;
2014-05-14 09:10:05 +00:00
2014-06-03 21:44:30 +00:00
if ( ( finalMessage . flags & textsecure . protos . PushMessageContentProtobuf . Flags . END _SESSION )
== textsecure . protos . PushMessageContentProtobuf . Flags . END _SESSION )
2014-05-14 09:10:05 +00:00
closeSession ( session ) ;
2014-05-31 17:28:46 +00:00
crypto _storage . saveSession ( encodedNumber , session , registrationId ) ;
2014-05-14 09:10:05 +00:00
return finalMessage ;
2014-05-13 05:51:46 +00:00
} ) ;
2014-05-09 06:00:49 +00:00
} ) ;
} ) ;
} ) ;
} ) ;
}
/ * * * * * * * * * * * * * * * * * * * * * * * * *
* * * Public crypto API * * *
* * * * * * * * * * * * * * * * * * * * * * * * * /
// Decrypts message into a raw string
2014-05-17 05:53:58 +00:00
self . decryptWebsocketMessage = function ( message ) {
2014-05-21 02:21:07 +00:00
var signaling _key = textsecure . storage . getEncrypted ( "signaling_key" ) ; //TODO: in crypto_storage
2014-05-13 19:15:45 +00:00
var aes _key = toArrayBuffer ( signaling _key . substring ( 0 , 32 ) ) ;
var mac _key = toArrayBuffer ( signaling _key . substring ( 32 , 32 + 20 ) ) ;
2014-05-09 06:00:49 +00:00
2014-05-13 19:15:45 +00:00
var decodedMessage = base64DecToArr ( getString ( message ) ) ;
if ( new Uint8Array ( decodedMessage ) [ 0 ] != 1 )
2014-05-09 06:00:49 +00:00
throw new Error ( "Got bad version number: " + decodedMessage [ 0 ] ) ;
2014-05-13 19:15:45 +00:00
var iv = decodedMessage . slice ( 1 , 1 + 16 ) ;
var ciphertext = decodedMessage . slice ( 1 + 16 , decodedMessage . byteLength - 10 ) ;
2014-05-15 04:26:37 +00:00
var ivAndCiphertext = decodedMessage . slice ( 1 , decodedMessage . byteLength - 10 ) ;
2014-05-13 19:15:45 +00:00
var mac = decodedMessage . slice ( decodedMessage . byteLength - 10 , decodedMessage . byteLength ) ;
2014-05-09 06:00:49 +00:00
2014-05-15 04:26:37 +00:00
return verifyMACWithVersionByte ( ivAndCiphertext , mac _key , mac ) . then ( function ( ) {
return window . crypto . subtle . decrypt ( { name : "AES-CBC" , iv : iv } , aes _key , ciphertext ) ;
} ) ;
} ;
2014-05-17 05:53:58 +00:00
self . decryptAttachment = function ( encryptedBin , keys ) {
2014-05-15 05:02:15 +00:00
var aes _key = keys . slice ( 0 , 32 ) ;
var mac _key = keys . slice ( 32 , 64 ) ;
2014-05-15 04:26:37 +00:00
2014-05-19 07:06:28 +00:00
var iv = encryptedBin . slice ( 0 , 16 ) ;
var ciphertext = encryptedBin . slice ( 16 , encryptedBin . byteLength - 32 ) ;
2014-05-15 04:26:37 +00:00
var ivAndCiphertext = encryptedBin . slice ( 0 , encryptedBin . byteLength - 32 ) ;
var mac = encryptedBin . slice ( encryptedBin . byteLength - 32 , encryptedBin . byteLength ) ;
return verifyMAC ( ivAndCiphertext , mac _key , mac ) . then ( function ( ) {
2014-05-09 07:43:23 +00:00
return window . crypto . subtle . decrypt ( { name : "AES-CBC" , iv : iv } , aes _key , ciphertext ) ;
2014-05-09 06:00:49 +00:00
} ) ;
} ;
2014-06-03 16:39:29 +00:00
self . encryptAttachment = function ( plaintext , keys , iv ) {
var aes _key = keys . slice ( 0 , 32 ) ;
var mac _key = keys . slice ( 32 , 64 ) ;
return window . crypto . subtle . encrypt ( { name : "AES-CBC" , iv : iv } , aes _key , plaintext ) . then ( function ( ciphertext ) {
var ivAndCiphertext = new Uint8Array ( 16 + ciphertext . byteLength ) ;
ivAndCiphertext . set ( iv ) ;
ivAndCiphertext . set ( ciphertext , 16 ) ;
2014-07-17 22:13:32 +00:00
return HmacSHA256 ( mac _key , ivAndCiphertext . buffer ) . then ( function ( mac ) {
2014-06-03 16:39:29 +00:00
var encryptedBin = new Uint8Array ( 16 + ciphertext . byteLength + 32 ) ;
encryptedBin . set ( ivAndCiphertext . buffer ) ;
encryptedBin . set ( mac , 16 + ciphertext . byteLength ) ;
return encryptedBin . buffer ;
} ) ;
} ) ;
} ;
2014-05-17 05:53:58 +00:00
self . handleIncomingPushMessageProto = function ( proto ) {
2014-05-09 06:00:49 +00:00
switch ( proto . type ) {
2014-06-03 16:39:29 +00:00
case textsecure . protos . IncomingPushMessageProtobuf . Type . PLAINTEXT :
2014-05-21 19:04:05 +00:00
return Promise . resolve ( textsecure . protos . decodePushMessageContentProtobuf ( getString ( proto . message ) ) ) ;
2014-06-03 16:39:29 +00:00
case textsecure . protos . IncomingPushMessageProtobuf . Type . CIPHERTEXT :
2014-05-25 23:48:41 +00:00
var from = proto . source + "." + ( proto . sourceDevice == null ? 0 : proto . sourceDevice ) ;
return decryptWhisperMessage ( from , getString ( proto . message ) ) ;
2014-06-03 16:39:29 +00:00
case textsecure . protos . IncomingPushMessageProtobuf . Type . PREKEY _BUNDLE :
2014-07-21 02:55:07 +00:00
if ( proto . message . readUint8 ( ) != ( ( 3 << 4 ) | 3 ) )
2014-05-09 06:00:49 +00:00
throw new Error ( "Bad version byte" ) ;
2014-05-25 23:48:41 +00:00
var from = proto . source + "." + ( proto . sourceDevice == null ? 0 : proto . sourceDevice ) ;
2014-06-01 17:39:35 +00:00
return handlePreKeyWhisperMessage ( from , getString ( proto . message ) ) ;
2014-06-03 16:39:29 +00:00
default :
return new Promise ( function ( resolve , reject ) { reject ( new Error ( "Unknown message type" ) ) ; } ) ;
2014-05-09 06:00:49 +00:00
}
}
2014-05-09 07:20:54 +00:00
// return Promise(encoded [PreKey]WhisperMessage)
2014-05-17 05:53:58 +00:00
self . encryptMessageFor = function ( deviceObject , pushMessageContent ) {
2014-05-14 21:20:49 +00:00
var session = crypto _storage . getOpenSession ( deviceObject . encodedNumber ) ;
2014-05-09 06:00:49 +00:00
var doEncryptPushMessageContent = function ( ) {
2014-05-21 19:04:05 +00:00
var msg = new textsecure . protos . WhisperMessageProtobuf ( ) ;
2014-05-09 06:00:49 +00:00
var plaintext = toArrayBuffer ( pushMessageContent . encode ( ) ) ;
2014-07-23 05:49:13 +00:00
var paddedPlaintext = new Uint8Array ( Math . ceil ( ( plaintext . byteLength + 1 ) / 160.0 ) * 160 ) ;
paddedPlaintext . set ( new Uint8Array ( plaintext ) ) ;
paddedPlaintext [ plaintext . byteLength ] = 0x80 ;
2014-05-09 06:00:49 +00:00
msg . ephemeralKey = toArrayBuffer ( session . currentRatchet . ephemeralKeyPair . pubKey ) ;
var chain = session [ getString ( msg . ephemeralKey ) ] ;
return fillMessageKeys ( chain , chain . chainKey . counter + 1 ) . then ( function ( ) {
2014-05-13 05:51:46 +00:00
return HKDF ( toArrayBuffer ( chain . messageKeys [ chain . chainKey . counter ] ) , '' , "WhisperMessageKeys" ) . then ( function ( keys ) {
2014-05-09 06:00:49 +00:00
delete chain . messageKeys [ chain . chainKey . counter ] ;
msg . counter = chain . chainKey . counter ;
msg . previousCounter = session . currentRatchet . previousCounter ;
2014-05-09 07:43:23 +00:00
var counter = intToArrayBuffer ( chain . chainKey . counter ) ;
2014-07-23 05:49:13 +00:00
return window . crypto . subtle . encrypt ( { name : "AES-CTR" , counter : counter } , keys [ 0 ] , paddedPlaintext . buffer ) . then ( function ( ciphertext ) {
2014-05-09 06:00:49 +00:00
msg . ciphertext = ciphertext ;
2014-05-13 05:51:46 +00:00
var encodedMsg = toArrayBuffer ( msg . encode ( ) ) ;
2014-05-09 06:00:49 +00:00
2014-07-24 10:29:11 +00:00
var macInput = new Uint8Array ( encodedMsg . byteLength + 33 * 2 + 1 ) ;
macInput . set ( new Uint8Array ( toArrayBuffer ( crypto _storage . getIdentityKey ( ) . pubKey ) ) ) ;
macInput . set ( new Uint8Array ( toArrayBuffer ( session . indexInfo . remoteIdentityKey ) ) , 33 ) ;
macInput [ 33 * 2 ] = ( 3 << 4 ) | 3 ;
macInput . set ( new Uint8Array ( encodedMsg ) , 33 * 2 + 1 ) ;
return HmacSHA256 ( keys [ 1 ] , macInput . buffer ) . then ( function ( mac ) {
2014-05-13 05:51:46 +00:00
var result = new Uint8Array ( encodedMsg . byteLength + 9 ) ;
2014-07-21 02:55:07 +00:00
result [ 0 ] = ( 3 << 4 ) | 3 ;
2014-05-13 05:51:46 +00:00
result . set ( new Uint8Array ( encodedMsg ) , 1 ) ;
result . set ( new Uint8Array ( mac , 0 , 8 ) , encodedMsg . byteLength + 1 ) ;
2014-05-09 06:00:49 +00:00
crypto _storage . saveSession ( deviceObject . encodedNumber , session ) ;
return result ;
} ) ;
} ) ;
} ) ;
} ) ;
}
2014-05-21 19:04:05 +00:00
var preKeyMsg = new textsecure . protos . PreKeyWhisperMessageProtobuf ( ) ;
2014-07-23 06:49:47 +00:00
preKeyMsg . identityKey = toArrayBuffer ( crypto _storage . getIdentityKey ( ) . pubKey ) ;
2014-05-09 06:00:49 +00:00
preKeyMsg . preKeyId = deviceObject . preKeyId ;
2014-07-21 02:55:07 +00:00
preKeyMsg . signedPreKeyId = deviceObject . signedKeyId ;
2014-05-21 02:21:07 +00:00
preKeyMsg . registrationId = textsecure . storage . getUnencrypted ( "registrationId" ) ;
2014-05-09 06:00:49 +00:00
if ( session === undefined ) {
2014-05-09 07:20:54 +00:00
return createNewKeyPair ( false ) . then ( function ( baseKey ) {
2014-05-09 06:00:49 +00:00
preKeyMsg . baseKey = toArrayBuffer ( baseKey . pubKey ) ;
2014-07-21 02:55:07 +00:00
return initSession ( true , baseKey , undefined , deviceObject . encodedNumber ,
toArrayBuffer ( deviceObject . identityKey ) , toArrayBuffer ( deviceObject . preKey ) , toArrayBuffer ( deviceObject . signedKey ) )
2014-07-24 10:29:11 +00:00
. then ( function ( new _session ) {
session = new _session ;
2014-05-09 06:00:49 +00:00
session . pendingPreKey = baseKey . pubKey ;
2014-05-09 07:20:54 +00:00
return doEncryptPushMessageContent ( ) . then ( function ( message ) {
2014-05-13 05:51:46 +00:00
preKeyMsg . message = message ;
2014-07-21 02:55:07 +00:00
var result = String . fromCharCode ( ( 3 << 4 ) | 3 ) + getString ( preKeyMsg . encode ( ) ) ;
2014-05-09 07:20:54 +00:00
return { type : 3 , body : result } ;
2014-05-09 06:00:49 +00:00
} ) ;
} ) ;
} ) ;
} else
2014-05-09 07:20:54 +00:00
return doEncryptPushMessageContent ( ) . then ( function ( message ) {
2014-05-09 06:00:49 +00:00
if ( session . pendingPreKey !== undefined ) {
preKeyMsg . baseKey = toArrayBuffer ( session . pendingPreKey ) ;
2014-05-13 05:51:46 +00:00
preKeyMsg . message = message ;
2014-07-21 02:55:07 +00:00
var result = String . fromCharCode ( ( 3 << 4 ) | 3 ) + getString ( preKeyMsg . encode ( ) ) ;
2014-05-09 07:20:54 +00:00
return { type : 3 , body : result } ;
2014-05-09 06:00:49 +00:00
} else
2014-05-09 07:20:54 +00:00
return { type : 1 , body : getString ( message ) } ;
2014-05-09 06:00:49 +00:00
} ) ;
}
var GENERATE _KEYS _KEYS _GENERATED = 100 ;
2014-05-17 05:53:58 +00:00
self . generateKeys = function ( ) {
2014-07-23 06:49:47 +00:00
var identityKeyPair = crypto _storage . getIdentityKey ( ) ;
2014-07-20 20:28:44 +00:00
var identityKeyCalculated = function ( identityKeyPair ) {
var firstPreKeyId = textsecure . storage . getEncrypted ( "maxPreKeyId" , 0 ) ;
textsecure . storage . putEncrypted ( "maxPreKeyId" , firstPreKeyId + GENERATE _KEYS _KEYS _GENERATED ) ;
2014-05-09 06:00:49 +00:00
2014-07-20 20:28:44 +00:00
var signedKeyId = textsecure . storage . getEncrypted ( "signedKeyId" , 0 ) ;
textsecure . storage . putEncrypted ( "signedKeyId" , signedKeyId + 1 ) ;
2014-05-09 06:00:49 +00:00
var keys = { } ;
2014-07-20 20:28:44 +00:00
keys . identityKey = identityKeyPair . pubKey ;
2014-07-20 21:11:19 +00:00
keys . preKeys = [ ] ;
2014-05-13 08:40:29 +00:00
var generateKey = function ( keyId ) {
2014-07-20 20:28:44 +00:00
return crypto _storage . getNewStoredKeyPair ( "preKey" + keyId , false ) . then ( function ( keyPair ) {
2014-07-20 21:11:19 +00:00
keys . preKeys [ keyId ] = { keyId : keyId , publicKey : keyPair . pubKey } ;
2014-05-13 08:40:29 +00:00
} ) ;
} ;
var promises = [ ] ;
2014-07-20 20:28:44 +00:00
for ( var i = firstPreKeyId ; i < firstPreKeyId + GENERATE _KEYS _KEYS _GENERATED ; i ++ )
2014-05-13 08:40:29 +00:00
promises [ i ] = generateKey ( i ) ;
2014-07-20 20:28:44 +00:00
promises [ firstPreKeyId + GENERATE _KEYS _KEYS _GENERATED ] = crypto _storage . getNewStoredKeyPair ( "signedKey" + signedKeyId ) . then ( function ( keyPair ) {
return Ed25519Sign ( identityKeyPair . privKey , keyPair . pubKey ) . then ( function ( sig ) {
2014-07-20 21:11:19 +00:00
keys . signedPreKey = { keyId : signedKeyId , publicKey : keyPair . pubKey , signature : sig } ;
2014-07-20 20:28:44 +00:00
} ) ;
} ) ;
2014-07-21 02:55:07 +00:00
//TODO: Process by date added and agressively call generateKeys when we get near maxPreKeyId in a message
crypto _storage . removeStoredKeyPair ( "signedKey" + ( signedKeyId - 2 ) ) ;
2014-07-20 20:28:44 +00:00
2014-05-13 08:40:29 +00:00
return Promise . all ( promises ) . then ( function ( ) {
2014-07-20 20:28:44 +00:00
return keys ;
2014-05-09 06:00:49 +00:00
} ) ;
}
2014-07-20 20:28:44 +00:00
if ( identityKeyPair === undefined )
return crypto _storage . getNewStoredKeyPair ( "identityKey" , true ) . then ( function ( keyPair ) { return identityKeyCalculated ( keyPair ) ; } ) ;
2014-05-09 06:00:49 +00:00
else
2014-07-20 20:28:44 +00:00
return identityKeyCalculated ( identityKeyPair ) ;
2014-05-09 06:00:49 +00:00
}
2014-05-17 04:54:12 +00:00
2014-07-20 20:28:44 +00:00
window . textsecure . registerOnLoadFunction ( function ( ) {
2014-07-20 21:11:19 +00:00
//TODO: Dont always update prekeys here
2014-07-20 20:28:44 +00:00
if ( textsecure . storage . getEncrypted ( "lastSignedKeyUpdate" , Date . now ( ) ) < Date . now ( ) - MESSAGE _LOST _THRESHOLD _MS )
self . generateKeys ( ) ;
} ) ;
2014-07-21 02:55:07 +00:00
self . Ed25519Verify = Ed25519Verify ;
2014-05-17 05:53:58 +00:00
self . testing _only = testing _only ;
return self ;
2014-05-17 04:54:12 +00:00
} ( ) ;