@ -36716,6 +36716,11 @@ Internal.SessionLock.queueJobForNumber = function queueJobForNumber(number, runJ
var calculateMAC = libsignal . crypto . calculateMAC ;
var calculateMAC = libsignal . crypto . calculateMAC ;
var verifyMAC = libsignal . crypto . verifyMAC ;
var verifyMAC = libsignal . crypto . verifyMAC ;
var PROFILE _IV _LENGTH = 12 ; // bytes
var PROFILE _KEY _LENGTH = 32 ; // bytes
var PROFILE _TAG _LENGTH = 128 ; // bits
var PROFILE _NAME _PADDED _LENGTH = 26 ; // bytes
function verifyDigest ( data , theirDigest ) {
function verifyDigest ( data , theirDigest ) {
return crypto . subtle . digest ( { name : 'SHA-256' } , data ) . then ( function ( ourDigest ) {
return crypto . subtle . digest ( { name : 'SHA-256' } , data ) . then ( function ( ourDigest ) {
var a = new Uint8Array ( ourDigest ) ;
var a = new Uint8Array ( ourDigest ) ;
@ -36812,6 +36817,68 @@ Internal.SessionLock.queueJobForNumber = function queueJobForNumber(number, runJ
} ) ;
} ) ;
} ) ;
} ) ;
} ,
} ,
encryptProfile : function ( data , key ) {
var iv = libsignal . crypto . getRandomBytes ( PROFILE _IV _LENGTH ) ;
if ( key . byteLength != PROFILE _KEY _LENGTH ) {
throw new Error ( "Got invalid length profile key" ) ;
}
if ( iv . byteLength != PROFILE _IV _LENGTH ) {
throw new Error ( "Got invalid length profile iv" ) ;
}
return crypto . subtle . importKey ( 'raw' , key , { name : 'AES-GCM' } , false , [ 'encrypt' ] ) . then ( function ( key ) {
return crypto . subtle . encrypt ( { name : 'AES-GCM' , iv : iv , tagLength : PROFILE _TAG _LENGTH } , key , data ) . then ( function ( ciphertext ) {
var ivAndCiphertext = new Uint8Array ( PROFILE _IV _LENGTH + ciphertext . byteLength ) ;
ivAndCiphertext . set ( new Uint8Array ( iv ) ) ;
ivAndCiphertext . set ( new Uint8Array ( ciphertext ) , PROFILE _IV _LENGTH ) ;
return ivAndCiphertext . buffer ;
} ) ;
} ) ;
} ,
decryptProfile : function ( data , key ) {
if ( data . byteLength < 12 + 16 + 1 ) {
throw new Error ( "Got too short input: " + data . byteLength ) ;
}
var iv = data . slice ( 0 , PROFILE _IV _LENGTH ) ;
var ciphertext = data . slice ( PROFILE _IV _LENGTH , data . byteLength ) ;
if ( key . byteLength != PROFILE _KEY _LENGTH ) {
throw new Error ( "Got invalid length profile key" ) ;
}
if ( iv . byteLength != PROFILE _IV _LENGTH ) {
throw new Error ( "Got invalid length profile iv" ) ;
}
var error = new Error ( ) ; // save stack
return crypto . subtle . importKey ( 'raw' , key , { name : 'AES-GCM' } , false , [ 'decrypt' ] ) . then ( function ( key ) {
return crypto . subtle . decrypt ( { name : 'AES-GCM' , iv : iv , tagLength : PROFILE _TAG _LENGTH } , key , ciphertext ) . catch ( function ( e ) {
if ( e . name === 'OperationError' ) {
// bad mac, basically.
error . message = 'Failed to decrypt profile data. Most likely the profile key has changed.' ;
error . name = 'ProfileDecryptError' ;
throw error ;
}
} ) ;
} ) ;
} ,
encryptProfileName : function ( name , key ) {
var padded = new Uint8Array ( PROFILE _NAME _PADDED _LENGTH ) ;
padded . set ( new Uint8Array ( name ) ) ;
return textsecure . crypto . encryptProfile ( padded . buffer , key ) ;
} ,
decryptProfileName : function ( encryptedProfileName , key ) {
var data = dcodeIO . ByteBuffer . wrap ( encryptedProfileName , 'base64' ) . toArrayBuffer ( ) ;
return textsecure . crypto . decryptProfile ( data , key ) . then ( function ( decrypted ) {
// unpad
var name = '' ;
var padded = new Uint8Array ( decrypted ) ;
for ( var i = padded . length ; i > 0 ; i -- ) {
if ( padded [ i - 1 ] !== 0x00 ) {
break ;
}
}
return dcodeIO . ByteBuffer . wrap ( padded ) . slice ( 0 , i ) . toArrayBuffer ( ) ;
} ) ;
} ,
getRandomBytes : function ( size ) {
getRandomBytes : function ( size ) {
return libsignal . crypto . getRandomBytes ( size ) ;
return libsignal . crypto . getRandomBytes ( size ) ;
@ -37709,11 +37776,12 @@ var TextSecureServer = (function() {
profile : "v1/profile"
profile : "v1/profile"
} ;
} ;
function TextSecureServer ( url , username , password ) {
function TextSecureServer ( url , username , password , cdn _url ) {
if ( typeof url !== 'string' ) {
if ( typeof url !== 'string' ) {
throw new Error ( 'Invalid server url' ) ;
throw new Error ( 'Invalid server url' ) ;
}
}
this . url = url ;
this . url = url ;
this . cdn _url = cdn _url ;
this . username = username ;
this . username = username ;
this . password = password ;
this . password = password ;
}
}
@ -37777,6 +37845,14 @@ var TextSecureServer = (function() {
urlParameters : '/' + number ,
urlParameters : '/' + number ,
} ) ;
} ) ;
} ,
} ,
getAvatar : function ( path ) {
return ajax ( this . cdn _url + '/' + path , {
type : "GET" ,
responseType : "arraybuffer" ,
contentType : "application/octet-stream" ,
certificateAuthorities : window . config . certificateAuthorities
} ) ;
} ,
requestVerificationSMS : function ( number ) {
requestVerificationSMS : function ( number ) {
return this . ajax ( {
return this . ajax ( {
call : 'accounts' ,
call : 'accounts' ,
@ -37994,7 +38070,8 @@ var TextSecureServer = (function() {
var registrationDone = this . registrationDone . bind ( this ) ;
var registrationDone = this . registrationDone . bind ( this ) ;
return this . queueTask ( function ( ) {
return this . queueTask ( function ( ) {
return libsignal . KeyHelper . generateIdentityKeyPair ( ) . then ( function ( identityKeyPair ) {
return libsignal . KeyHelper . generateIdentityKeyPair ( ) . then ( function ( identityKeyPair ) {
return createAccount ( number , verificationCode , identityKeyPair ) .
var profileKey = textsecure . crypto . getRandomBytes ( 32 ) ;
return createAccount ( number , verificationCode , identityKeyPair , profileKey ) .
then ( generateKeys ) .
then ( generateKeys ) .
then ( registerKeys ) .
then ( registerKeys ) .
then ( registrationDone ) ;
then ( registrationDone ) ;
@ -38047,6 +38124,7 @@ var TextSecureServer = (function() {
provisionMessage . number ,
provisionMessage . number ,
provisionMessage . provisioningCode ,
provisionMessage . provisioningCode ,
provisionMessage . identityKeyPair ,
provisionMessage . identityKeyPair ,
provisionMessage . profileKey ,
deviceName ,
deviceName ,
provisionMessage . userAgent
provisionMessage . userAgent
) . then ( generateKeys ) .
) . then ( generateKeys ) .
@ -38147,7 +38225,7 @@ var TextSecureServer = (function() {
} ) ;
} ) ;
} ) ;
} ) ;
} ,
} ,
createAccount : function ( number , verificationCode , identityKeyPair , deviceName, userAgent ) {
createAccount : function ( number , verificationCode , identityKeyPair , profileKey, deviceName, userAgent ) {
var signalingKey = libsignal . crypto . getRandomBytes ( 32 + 20 ) ;
var signalingKey = libsignal . crypto . getRandomBytes ( 32 + 20 ) ;
var password = btoa ( getString ( libsignal . crypto . getRandomBytes ( 16 ) ) ) ;
var password = btoa ( getString ( libsignal . crypto . getRandomBytes ( 16 ) ) ) ;
password = password . substring ( 0 , password . length - 2 ) ;
password = password . substring ( 0 , password . length - 2 ) ;
@ -38165,6 +38243,7 @@ var TextSecureServer = (function() {
textsecure . storage . remove ( 'device_name' ) ;
textsecure . storage . remove ( 'device_name' ) ;
textsecure . storage . remove ( 'regionCode' ) ;
textsecure . storage . remove ( 'regionCode' ) ;
textsecure . storage . remove ( 'userAgent' ) ;
textsecure . storage . remove ( 'userAgent' ) ;
textsecure . storage . remove ( 'profileKey' ) ;
// update our own identity key, which may have changed
// update our own identity key, which may have changed
// if we're relinking after a reinstall on the master device
// if we're relinking after a reinstall on the master device
@ -38181,6 +38260,7 @@ var TextSecureServer = (function() {
textsecure . storage . put ( 'signaling_key' , signalingKey ) ;
textsecure . storage . put ( 'signaling_key' , signalingKey ) ;
textsecure . storage . put ( 'password' , password ) ;
textsecure . storage . put ( 'password' , password ) ;
textsecure . storage . put ( 'registrationId' , registrationId ) ;
textsecure . storage . put ( 'registrationId' , registrationId ) ;
textsecure . storage . put ( 'profileKey' , profileKey ) ;
if ( userAgent ) {
if ( userAgent ) {
textsecure . storage . put ( 'userAgent' , userAgent ) ;
textsecure . storage . put ( 'userAgent' , userAgent ) ;
}
}
@ -39017,6 +39097,9 @@ MessageReceiver.prototype.extend({
} else if ( decrypted . flags & textsecure . protobuf . DataMessage . Flags . EXPIRATION _TIMER _UPDATE ) {
} else if ( decrypted . flags & textsecure . protobuf . DataMessage . Flags . EXPIRATION _TIMER _UPDATE ) {
decrypted . body = null ;
decrypted . body = null ;
decrypted . attachments = [ ] ;
decrypted . attachments = [ ] ;
} else if ( decrypted . flags & textsecure . protobuf . DataMessage . Flags . PROFILE _KEY _UPDATE ) {
decrypted . body = null ;
decrypted . attachments = [ ] ;
} else if ( decrypted . flags != 0 ) {
} else if ( decrypted . flags != 0 ) {
throw new Error ( "Unknown flags in message" ) ;
throw new Error ( "Unknown flags in message" ) ;
}
}
@ -39374,6 +39457,7 @@ function Message(options) {
this . timestamp = options . timestamp ;
this . timestamp = options . timestamp ;
this . needsSync = options . needsSync ;
this . needsSync = options . needsSync ;
this . expireTimer = options . expireTimer ;
this . expireTimer = options . expireTimer ;
this . profileKey = options . profileKey ;
if ( ! ( this . recipients instanceof Array ) || this . recipients . length < 1 ) {
if ( ! ( this . recipients instanceof Array ) || this . recipients . length < 1 ) {
throw new Error ( 'Invalid recipient list' ) ;
throw new Error ( 'Invalid recipient list' ) ;
@ -39447,6 +39531,10 @@ Message.prototype = {
proto . expireTimer = this . expireTimer ;
proto . expireTimer = this . expireTimer ;
}
}
if ( this . profileKey ) {
proto . profileKey = this . profileKey ;
}
this . dataMessage = proto ;
this . dataMessage = proto ;
return proto ;
return proto ;
} ,
} ,
@ -39455,8 +39543,8 @@ Message.prototype = {
}
}
} ;
} ;
function MessageSender ( url , username , password ) {
function MessageSender ( url , username , password , cdn _url ) {
this . server = new TextSecureServer ( url , username , password );
this . server = new TextSecureServer ( url , username , password , cdn _url );
this . pendingMessages = { } ;
this . pendingMessages = { } ;
}
}
@ -39670,6 +39758,9 @@ MessageSender.prototype = {
getProfile : function ( number ) {
getProfile : function ( number ) {
return this . server . getProfile ( number ) ;
return this . server . getProfile ( number ) ;
} ,
} ,
getAvatar : function ( path ) {
return this . server . getAvatar ( path ) ;
} ,
sendRequestGroupSyncMessage : function ( ) {
sendRequestGroupSyncMessage : function ( ) {
var myNumber = textsecure . storage . user . getNumber ( ) ;
var myNumber = textsecure . storage . user . getNumber ( ) ;
@ -39780,14 +39871,15 @@ MessageSender.prototype = {
} . bind ( this ) ) ;
} . bind ( this ) ) ;
} ,
} ,
sendMessageToNumber : function ( number , messageText , attachments , timestamp , expireTimer ) {
sendMessageToNumber : function ( number , messageText , attachments , timestamp , expireTimer , profileKey ) {
return this . sendMessage ( {
return this . sendMessage ( {
recipients : [ number ] ,
recipients : [ number ] ,
body : messageText ,
body : messageText ,
timestamp : timestamp ,
timestamp : timestamp ,
attachments : attachments ,
attachments : attachments ,
needsSync : true ,
needsSync : true ,
expireTimer : expireTimer
expireTimer : expireTimer ,
profileKey : profileKey
} ) ;
} ) ;
} ,
} ,
@ -39812,7 +39904,7 @@ MessageSender.prototype = {
} . bind ( this ) ) ;
} . bind ( this ) ) ;
} ,
} ,
sendMessageToGroup : function ( groupId , messageText , attachments , timestamp , expireTimer ) {
sendMessageToGroup : function ( groupId , messageText , attachments , timestamp , expireTimer , profileKey ) {
return textsecure . storage . groups . getNumbers ( groupId ) . then ( function ( numbers ) {
return textsecure . storage . groups . getNumbers ( groupId ) . then ( function ( numbers ) {
if ( numbers === undefined )
if ( numbers === undefined )
return Promise . reject ( new Error ( "Unknown Group" ) ) ;
return Promise . reject ( new Error ( "Unknown Group" ) ) ;
@ -39830,6 +39922,7 @@ MessageSender.prototype = {
attachments : attachments ,
attachments : attachments ,
needsSync : true ,
needsSync : true ,
expireTimer : expireTimer ,
expireTimer : expireTimer ,
profileKey : profileKey ,
group : {
group : {
id : groupId ,
id : groupId ,
type : textsecure . protobuf . GroupContext . Type . DELIVER
type : textsecure . protobuf . GroupContext . Type . DELIVER
@ -39945,7 +40038,7 @@ MessageSender.prototype = {
} . bind ( this ) ) ;
} . bind ( this ) ) ;
} ) ;
} ) ;
} ,
} ,
sendExpirationTimerUpdateToGroup : function ( groupId , expireTimer , timestamp ) {
sendExpirationTimerUpdateToGroup : function ( groupId , expireTimer , timestamp , profileKey ) {
return textsecure . storage . groups . getNumbers ( groupId ) . then ( function ( numbers ) {
return textsecure . storage . groups . getNumbers ( groupId ) . then ( function ( numbers ) {
if ( numbers === undefined )
if ( numbers === undefined )
return Promise . reject ( new Error ( "Unknown Group" ) ) ;
return Promise . reject ( new Error ( "Unknown Group" ) ) ;
@ -39960,6 +40053,7 @@ MessageSender.prototype = {
timestamp : timestamp ,
timestamp : timestamp ,
needsSync : true ,
needsSync : true ,
expireTimer : expireTimer ,
expireTimer : expireTimer ,
profileKey : profileKey ,
flags : textsecure . protobuf . DataMessage . Flags . EXPIRATION _TIMER _UPDATE ,
flags : textsecure . protobuf . DataMessage . Flags . EXPIRATION _TIMER _UPDATE ,
group : {
group : {
id : groupId ,
id : groupId ,
@ -39968,13 +40062,14 @@ MessageSender.prototype = {
} ) ;
} ) ;
} . bind ( this ) ) ;
} . bind ( this ) ) ;
} ,
} ,
sendExpirationTimerUpdateToNumber : function ( number , expireTimer , timestamp ) {
sendExpirationTimerUpdateToNumber : function ( number , expireTimer , timestamp , profileKey ) {
var proto = new textsecure . protobuf . DataMessage ( ) ;
var proto = new textsecure . protobuf . DataMessage ( ) ;
return this . sendMessage ( {
return this . sendMessage ( {
recipients : [ number ] ,
recipients : [ number ] ,
timestamp : timestamp ,
timestamp : timestamp ,
needsSync : true ,
needsSync : true ,
expireTimer : expireTimer ,
expireTimer : expireTimer ,
profileKey : profileKey ,
flags : textsecure . protobuf . DataMessage . Flags . EXPIRATION _TIMER _UPDATE
flags : textsecure . protobuf . DataMessage . Flags . EXPIRATION _TIMER _UPDATE
} ) ;
} ) ;
}
}
@ -39982,8 +40077,8 @@ MessageSender.prototype = {
window . textsecure = window . textsecure || { } ;
window . textsecure = window . textsecure || { } ;
textsecure . MessageSender = function ( url , username , password ) {
textsecure . MessageSender = function ( url , username , password , cdn _url ) {
var sender = new MessageSender ( url , username , password );
var sender = new MessageSender ( url , username , password , cdn _url );
textsecure . replay . registerFunction ( sender . tryMessageAgain . bind ( sender ) , textsecure . replay . Type . ENCRYPT _MESSAGE ) ;
textsecure . replay . registerFunction ( sender . tryMessageAgain . bind ( sender ) , textsecure . replay . Type . ENCRYPT _MESSAGE ) ;
textsecure . replay . registerFunction ( sender . retransmitMessage . bind ( sender ) , textsecure . replay . Type . TRANSMIT _MESSAGE ) ;
textsecure . replay . registerFunction ( sender . retransmitMessage . bind ( sender ) , textsecure . replay . Type . TRANSMIT _MESSAGE ) ;
textsecure . replay . registerFunction ( sender . sendMessage . bind ( sender ) , textsecure . replay . Type . REBUILD _MESSAGE ) ;
textsecure . replay . registerFunction ( sender . sendMessage . bind ( sender ) , textsecure . replay . Type . REBUILD _MESSAGE ) ;
@ -40004,6 +40099,7 @@ textsecure.MessageSender = function(url, username, password) {
this . leaveGroup = sender . leaveGroup . bind ( sender ) ;
this . leaveGroup = sender . leaveGroup . bind ( sender ) ;
this . sendSyncMessage = sender . sendSyncMessage . bind ( sender ) ;
this . sendSyncMessage = sender . sendSyncMessage . bind ( sender ) ;
this . getProfile = sender . getProfile . bind ( sender ) ;
this . getProfile = sender . getProfile . bind ( sender ) ;
this . getAvatar = sender . getAvatar . bind ( sender ) ;
this . syncReadMessages = sender . syncReadMessages . bind ( sender ) ;
this . syncReadMessages = sender . syncReadMessages . bind ( sender ) ;
this . syncVerification = sender . syncVerification . bind ( sender ) ;
this . syncVerification = sender . syncVerification . bind ( sender ) ;
} ;
} ;
@ -40128,6 +40224,10 @@ ProtoParser.prototype = {
this . buffer . skip ( attachmentLen ) ;
this . buffer . skip ( attachmentLen ) ;
}
}
if ( proto . profileKey ) {
proto . profileKey = proto . profileKey . toArrayBuffer ( ) ;
}
return proto ;
return proto ;
} catch ( e ) {
} catch ( e ) {
console . log ( e ) ;
console . log ( e ) ;
@ -40182,7 +40282,8 @@ ProvisioningCipher.prototype = {
identityKeyPair : keyPair ,
identityKeyPair : keyPair ,
number : provisionMessage . number ,
number : provisionMessage . number ,
provisioningCode : provisionMessage . provisioningCode ,
provisioningCode : provisionMessage . provisioningCode ,
userAgent : provisionMessage . userAgent
userAgent : provisionMessage . userAgent ,
profileKey : provisionMessage . profileKey . toArrayBuffer ( )
} ;
} ;
} ) ;
} ) ;
} ) ;
} ) ;