You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
727 lines
20 KiB
727 lines
20 KiB
/* |
|
* Spreed WebRTC. |
|
* Copyright (C) 2013-2015 struktur AG |
|
* |
|
* This file is part of Spreed WebRTC. |
|
* |
|
* This program is free software: you can redistribute it and/or modify |
|
* it under the terms of the GNU Affero 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 Affero General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU Affero General Public License |
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
* |
|
*/ |
|
"use strict"; |
|
define([ |
|
'jquery', |
|
'underscore', |
|
'modernizr', |
|
'axolotl', |
|
'ByteBuffer' |
|
], function( |
|
$, |
|
_, |
|
modernizr, |
|
axolotl, |
|
ByteBuffer |
|
) { |
|
|
|
return [ |
|
"$window", |
|
"$q", |
|
function( |
|
$window, |
|
$q |
|
) { |
|
|
|
// Bitflags for the different components that need to be ready for |
|
// encryption to work. |
|
var READY_NONE = 0; |
|
var READY_IDENTITY_KEYPAIR = 1; |
|
var READY_REGISTRATION_ID = 2; |
|
var READY_LAST_RESORT_PRE_KEY = 4; |
|
|
|
var READY_ALL = READY_IDENTITY_KEYPAIR | |
|
READY_REGISTRATION_ID | |
|
READY_LAST_RESORT_PRE_KEY; |
|
|
|
var LAST_RESORT_PRE_KEY_ID = 0xffffffff; |
|
|
|
var deserializeKeypair = function(data) { |
|
if (typeof(data) === "string") { |
|
data = JSON.parse(data); |
|
} |
|
return { |
|
"private": ByteBuffer.fromBase64(data.private).toArrayBuffer(), |
|
"public": ByteBuffer.fromBase64(data.public).toArrayBuffer() |
|
}; |
|
}; |
|
|
|
var deserializePublicKey = function(data) { |
|
return ByteBuffer.fromBase64(data).toArrayBuffer(); |
|
}; |
|
|
|
var deserializeSignature = function(data) { |
|
return ByteBuffer.fromBase64(data).toArrayBuffer(); |
|
}; |
|
|
|
var deserializeSignedPreKey = function(data) { |
|
if (typeof(data) === "string") { |
|
data = JSON.parse(data); |
|
} |
|
return { |
|
"id": parseInt(data.id, 10), |
|
"keyPair": { |
|
"private": |
|
ByteBuffer.fromBase64(data.private).toArrayBuffer(), |
|
"public": |
|
ByteBuffer.fromBase64(data.public).toArrayBuffer() |
|
}, |
|
"signature": |
|
ByteBuffer.fromBase64(data.signature).toArrayBuffer() |
|
}; |
|
}; |
|
|
|
var serializeKeypair = function(keypair) { |
|
var data = { |
|
"private": ByteBuffer.wrap(keypair.private).toBase64(), |
|
"public": ByteBuffer.wrap(keypair.public).toBase64() |
|
}; |
|
return JSON.stringify(data); |
|
}; |
|
|
|
var serializePublicKey = function(keypair) { |
|
return ByteBuffer.wrap(keypair.public).toBase64(); |
|
}; |
|
|
|
var serializeSignature = function(signature) { |
|
return ByteBuffer.wrap(signature).toBase64(); |
|
}; |
|
|
|
var serializeSignedPreKey = function(key) { |
|
var data = { |
|
"id": key.id, |
|
"private": ByteBuffer.wrap(key.keyPair.private).toBase64(), |
|
"public": ByteBuffer.wrap(key.keyPair.public).toBase64(), |
|
"signature": ByteBuffer.wrap(key.signature).toBase64() |
|
}; |
|
return JSON.stringify(data); |
|
}; |
|
|
|
var LocalStorageDataStore = function(endToEndEncryption) { |
|
this.e = $({}); |
|
this.endToEndEncryption = endToEndEncryption; |
|
this.userid = null; |
|
this.id = endToEndEncryption.api.sid; |
|
this.anonymous_storage = {}; |
|
endToEndEncryption.api.e.on("received.self", _.bind(function(event, data) { |
|
var userid = data.Suserid || null; |
|
if (this.id === (userid || data.Sid) && this.userid === userid) { |
|
return; |
|
} |
|
var initial = (this.id === null); |
|
this.id = userid || data.Sid; |
|
if (this.userid !== userid) { |
|
this.userid = userid; |
|
this.anonymous_storage = {}; |
|
} |
|
this.endToEndEncryption.resetStore(); |
|
if (initial) { |
|
this.e.triggerHandler("ready"); |
|
} |
|
}, this)); |
|
this.storage = $window.localStorage; |
|
}; |
|
|
|
LocalStorageDataStore.prototype.isReady = function() { |
|
return !!this.id; |
|
}; |
|
|
|
LocalStorageDataStore.prototype.load = function(key) { |
|
if (!this.id) { |
|
// Not connected, unknown user state. |
|
return null; |
|
} else if (!this.userid) { |
|
// Connected, but not logged in. |
|
return this.anonymous_storage[key] || null; |
|
} else { |
|
// Connected, logged in. |
|
return this.storage.getItem(key); |
|
} |
|
}; |
|
|
|
LocalStorageDataStore.prototype.save = function(key, data) { |
|
if (!this.id) { |
|
return false; |
|
} else if (!this.userid) { |
|
this.anonymous_storage[key] = data; |
|
return true; |
|
} |
|
switch (typeof(data)) { |
|
case "string": |
|
break; |
|
case "number": |
|
data = data.toString(); |
|
break; |
|
default: |
|
data = JSON.stringify(data); |
|
} |
|
this.storage.setItem(key, data); |
|
return true; |
|
}; |
|
|
|
LocalStorageDataStore.prototype.loadKeypair = function() { |
|
var data = this.load("keypair_" + this.id); |
|
if (!data) { |
|
return null; |
|
} |
|
|
|
return deserializeKeypair(data); |
|
}; |
|
|
|
LocalStorageDataStore.prototype.loadRegistrationId = function() { |
|
var data = this.load("registration_id_" + this.id); |
|
if (!data) { |
|
return null; |
|
} |
|
|
|
return parseInt(data, 10); |
|
}; |
|
|
|
LocalStorageDataStore.prototype.loadNextSignedPreKeyId = function() { |
|
var data = this.load("next_signed_pre_key_id_" + this.id); |
|
if (!data) { |
|
return null; |
|
} |
|
|
|
return parseInt(data, 10); |
|
}; |
|
|
|
LocalStorageDataStore.prototype.loadSignedPreKey = function(id) { |
|
var data = this.load("signed_pre_key_" + this.id + "_" + id); |
|
if (!data) { |
|
return null; |
|
} |
|
|
|
return deserializeSignedPreKey(data); |
|
}; |
|
|
|
LocalStorageDataStore.prototype.saveKeypair = function(keypair) { |
|
var data = serializeKeypair(keypair); |
|
return this.save("keypair_" + this.id, data); |
|
}; |
|
|
|
LocalStorageDataStore.prototype.saveRegistrationId = function(id) { |
|
return this.save("registration_id_" + this.id, id); |
|
}; |
|
|
|
LocalStorageDataStore.prototype.saveNextSignedPreKeyId = function(id) { |
|
return this.save("next_pre_key_id_" + this.id, id); |
|
}; |
|
|
|
LocalStorageDataStore.prototype.saveSignedPreKey = function(key) { |
|
var data = serializeSignedPreKey(key); |
|
return this.save("signed_pre_key_" + this.id + "_" + key.id, data); |
|
}; |
|
|
|
var PeerIdentity = function(id, identity_key) { |
|
this.id = id; |
|
this.identity_key = identity_key; |
|
}; |
|
|
|
PeerIdentity.prototype.getFingerprint = function() { |
|
// TODO(jojo): Change this to be the SHA-1 hash of a three-byte key |
|
// id and the public key. |
|
// See https://github.com/WhisperSystems/TextSecure/blob/08ed90c5ece49c92e35c492afb4e60160983015a/src/org/thoughtcrime/securesms/crypto/PublicKey.java#L95 |
|
var fingerprint = ByteBuffer.wrap(this.identity_key).toHex(); |
|
var pos; |
|
for (pos = fingerprint.length - 4; pos >= 4; pos -= 4) { |
|
fingerprint = fingerprint.substr(0, pos) + "-" + |
|
fingerprint.substr(pos); |
|
} |
|
return fingerprint.substr(2); |
|
}; |
|
|
|
var EndToEndEncryption = function(api, events) { |
|
// Private events. |
|
this.e = $({}); |
|
// Public events. |
|
this.events = events; |
|
this.api = api; |
|
// TODO(fancycode): Look into using IndexedDB as storage backend. |
|
if (modernizr.localstorage) { |
|
this.store = new LocalStorageDataStore(this); |
|
} else { |
|
// Need a way to store data for encryption support. |
|
return; |
|
} |
|
|
|
this.axol = axolotl({ |
|
getLocalIdentityKeyPair: _.bind(function() { |
|
return this.getLocalIdentityKeyPair.apply(this, arguments); |
|
}, this), |
|
getLocalRegistrationId: _.bind(function() { |
|
return this.getLocalRegistrationId.apply(this, arguments); |
|
}, this), |
|
getLocalSignedPreKeyPair: _.bind(function() { |
|
return this.getLocalSignedPreKeyPair.apply(this, arguments); |
|
}, this), |
|
getLocalPreKeyPair: _.bind(function() { |
|
// Unsigned PreKeys are only used by protocol V2. |
|
console.warn("Local PreKeys are not supported.") |
|
return null; |
|
}, this) |
|
}); |
|
this.api.e.on("received.requestkeybundle", |
|
_.bind(function(event, peer) { |
|
this.requestKeyBundle(peer); |
|
}, this)) |
|
this.api.e.on("received.keybundle", |
|
_.bind(function(event, peer, data) { |
|
this.receivedKeyBundle(peer, data); |
|
}, this)) |
|
this.peer_identities = {}; |
|
this.sessions = {}; |
|
this.resetStore(); |
|
}; |
|
|
|
EndToEndEncryption.prototype.isSupported = function() { |
|
return !!this.store; |
|
}; |
|
|
|
EndToEndEncryption.prototype.resetStore = function() { |
|
// TODO(fancycode): We should process any pending messages before |
|
// resetting everything. |
|
this.identity_keypair = null; |
|
this.registration_id = null; |
|
this.last_resort_pre_key = null; |
|
this.next_signed_pre_key_id = 1; |
|
this.pending_messages = {}; |
|
this.is_processing_messages = {}; |
|
this.ready_promise = null; |
|
if (this.store.isReady()) { |
|
_.defer(_.bind(function() { |
|
this.register(); |
|
}, this)); |
|
} |
|
}; |
|
|
|
EndToEndEncryption.prototype.setOwnIdentity = function(public_key) { |
|
var identity = new PeerIdentity(null, public_key); |
|
this.own_identity = identity; |
|
this.events.triggerHandler("identity.own", [identity]); |
|
}; |
|
|
|
EndToEndEncryption.prototype.storePeerIdentity = function(peer, public_key) { |
|
var identity = new PeerIdentity(peer, public_key); |
|
var fingerprint = identity.getFingerprint(); |
|
var existing = this.peer_identities[peer]; |
|
if (existing) { |
|
if (existing.getFingerprint() === fingerprint) { |
|
// No change. |
|
return; |
|
} else { |
|
// Uh oh, remote peer has a new identity, this is something |
|
// the user should know about! |
|
this.events.triggerHandler("identity.changed", [ |
|
peer, |
|
existing, |
|
identity |
|
]); |
|
} |
|
} |
|
this.peer_identities[peer] = identity; |
|
this.events.triggerHandler("identity.received", [peer, identity]); |
|
}; |
|
|
|
EndToEndEncryption.prototype.getReadyPromise = function() { |
|
if (!this.ready_promise) { |
|
// Load/create initial objects |
|
this.ready_promise = this.loadData().then(_.bind(function() { |
|
var deferred = $q.defer(); |
|
|
|
_.defer(_.bind(function() { |
|
var ready_state = READY_NONE; |
|
var check_ready = _.bind(function() { |
|
if (ready_state === READY_ALL) { |
|
deferred.resolve(); |
|
} |
|
}, this); |
|
this.getLocalIdentityKeyPair().then(function() { |
|
ready_state |= READY_IDENTITY_KEYPAIR; |
|
check_ready(); |
|
}); |
|
this.getLocalRegistrationId().then(function() { |
|
ready_state |= READY_REGISTRATION_ID; |
|
check_ready(); |
|
}); |
|
this.getLastResortPreKey().then(function() { |
|
ready_state |= READY_LAST_RESORT_PRE_KEY; |
|
check_ready(); |
|
}); |
|
}, this)); |
|
return deferred.promise; |
|
}, this)); |
|
} |
|
return this.ready_promise; |
|
}; |
|
|
|
EndToEndEncryption.prototype.loadData = function() { |
|
var deferred = $q.defer(); |
|
var doLoadData = _.bind(function() { |
|
this.identity_keypair = this.store.loadKeypair(); |
|
if (this.identity_keypair) { |
|
this.setOwnIdentity(this.identity_keypair.public); |
|
} |
|
this.registration_id = this.store.loadRegistrationId(); |
|
this.last_resort_pre_key = |
|
this.store.loadSignedPreKey(LAST_RESORT_PRE_KEY_ID); |
|
this.next_signed_pre_key_id = |
|
this.store.loadNextSignedPreKeyId(); |
|
if (this.next_signed_pre_key_id === null) { |
|
this.next_signed_pre_key_id = 1; |
|
} |
|
deferred.resolve(); |
|
}, this); |
|
if (this.store.isReady()) { |
|
_.defer(doLoadData); |
|
} else { |
|
this.store.e.one("ready", doLoadData); |
|
} |
|
return deferred.promise; |
|
}; |
|
|
|
EndToEndEncryption.prototype.getLocalIdentityKeyPair = function() { |
|
var deferred = $q.defer(); |
|
_.defer(_.bind(function() { |
|
if (this.identity_keypair) { |
|
deferred.resolve(this.identity_keypair); |
|
return; |
|
} |
|
|
|
this.axol.generateIdentityKeyPair() |
|
.then(_.bind(function(keypair) { |
|
if (this.identity_keypair === null) { |
|
this.identity_keypair = keypair; |
|
this.setOwnIdentity(this.identity_keypair.public); |
|
this.store.saveKeypair(keypair); |
|
} |
|
deferred.resolve(this.identity_keypair); |
|
}, this)); |
|
}, this)); |
|
return deferred.promise; |
|
}; |
|
|
|
EndToEndEncryption.prototype.getLocalRegistrationId = function() { |
|
var deferred = $q.defer(); |
|
_.defer(_.bind(function() { |
|
if (this.registration_id) { |
|
deferred.resolve(this.registration_id); |
|
return; |
|
} |
|
|
|
this.axol.generateRegistrationId().then(_.bind(function(id) { |
|
if (this.registration_id === null) { |
|
this.registration_id = id; |
|
this.store.saveRegistrationId(id); |
|
} |
|
deferred.resolve(this.registration_id); |
|
}, this)); |
|
}, this)); |
|
return deferred.promise; |
|
}; |
|
|
|
EndToEndEncryption.prototype.getLastResortPreKey = function() { |
|
var deferred = $q.defer(); |
|
_.defer(_.bind(function() { |
|
if (this.last_resort_pre_key) { |
|
deferred.resolve(this.last_resort_pre_key); |
|
return; |
|
} |
|
|
|
this.getSignedPreKey(LAST_RESORT_PRE_KEY_ID) |
|
.then(_.bind(function(pre_key) { |
|
if (this.last_resort_pre_key === null) { |
|
this.last_resort_pre_key = pre_key; |
|
this.store.saveSignedPreKey(pre_key); |
|
} |
|
deferred.resolve(this.last_resort_pre_key); |
|
}, this)); |
|
}, this)); |
|
return deferred.promise; |
|
}; |
|
|
|
EndToEndEncryption.prototype.getLocalSignedPreKeyPair = function(id) { |
|
var deferred = $q.defer(); |
|
_.defer(_.bind(function() { |
|
var signed_pre_key; |
|
if (id === LAST_RESORT_PRE_KEY_ID) { |
|
signed_pre_key = this.last_resort_pre_key; |
|
} else { |
|
signed_pre_key = this.store.loadSignedPreKey(id); |
|
} |
|
deferred.resolve(signed_pre_key.keyPair || null); |
|
}, this)); |
|
return deferred.promise; |
|
}; |
|
|
|
EndToEndEncryption.prototype.getSignedPreKey = function(id) { |
|
if (!id) { |
|
id = this.next_signed_pre_key_id; |
|
this.next_signed_pre_key_id++; |
|
if (this.next_signed_pre_key_id === LAST_RESORT_PRE_KEY_ID) { |
|
this.next_signed_pre_key_id = 1; |
|
} |
|
this.store.saveNextSignedPreKeyId(this.next_signed_pre_key_id); |
|
} |
|
|
|
var deferred = $q.defer(); |
|
_.defer(_.bind(function() { |
|
_.defer(_.bind(function() { |
|
this.axol.generateSignedPreKey(this.identity_keypair, id) |
|
.then(_.bind(function(signed_pre_key) { |
|
// Need to remember so it can be looked up later. |
|
this.store.saveSignedPreKey(signed_pre_key); |
|
deferred.resolve(signed_pre_key); |
|
}, this)); |
|
}, this)); |
|
}, this)); |
|
return deferred.promise; |
|
}; |
|
|
|
EndToEndEncryption.prototype.apiSend = function(type, message) { |
|
var msg = { |
|
"Type": type |
|
}; |
|
msg[type] = message; |
|
this.api.send(type, message); |
|
}; |
|
|
|
EndToEndEncryption.prototype.register = function() { |
|
this.getReadyPromise().then(_.bind(function() { |
|
var message = { |
|
"RegistrationId": this.registration_id, |
|
"Identity": serializePublicKey(this.identity_keypair), |
|
"LastResortSignedPreKey": { |
|
"Id": this.last_resort_pre_key.id, |
|
"Key": serializePublicKey( |
|
this.last_resort_pre_key.keyPair), |
|
"Signature": serializeSignature( |
|
this.last_resort_pre_key.signature) |
|
} |
|
}; |
|
this.apiSend("EncryptionRegister", message); |
|
}, this)); |
|
}; |
|
|
|
EndToEndEncryption.prototype.processPendingMessages = function(peer) { |
|
var pending = this.pending_messages[peer]; |
|
if (pending && pending.length > 0) { |
|
var msg = pending.shift(); |
|
switch (msg.type) { |
|
case "encrypt": |
|
this.encryptWithSession( |
|
peer, |
|
this.sessions[peer], |
|
msg.message, |
|
msg.callback); |
|
break; |
|
case "decrypt": |
|
this.decryptWithSession( |
|
peer, |
|
this.sessions[peer], |
|
msg.message, |
|
msg.callback); |
|
break; |
|
} |
|
} else { |
|
delete this.is_processing_messages[peer]; |
|
delete this.pending_messages[peer]; |
|
} |
|
}; |
|
|
|
EndToEndEncryption.prototype.requestKeyBundle = function(peer) { |
|
this.getSignedPreKey().then(_.bind(function(signed_pre_key) { |
|
var bundle = { |
|
"To": peer, |
|
"Identity": serializePublicKey(this.identity_keypair), |
|
"PreKeyId": null, |
|
"PreKey": null, |
|
"SignedPreKeyId": signed_pre_key.id, |
|
"SignedPreKey": |
|
serializePublicKey(signed_pre_key.keyPair), |
|
"SignedPreKeySignature": |
|
serializeSignature(signed_pre_key.signature) |
|
}; |
|
this.apiSend("EncryptionKeyBundle", bundle); |
|
}, this)); |
|
}; |
|
|
|
EndToEndEncryption.prototype.receivedKeyBundle = function(peer, data) { |
|
var bundle = { |
|
"identityKey": deserializePublicKey(data.Identity), |
|
"preKeyId": null, |
|
"preKey": null, |
|
"signedPreKeyId": null, |
|
"signedPreKey": null, |
|
"signedPreKeySignature": null |
|
}; |
|
if (data.PreKey) { |
|
bundle.preKeyId = data.PreKeyId; |
|
bundle.preKey = deserializePublicKey(data.PreKey); |
|
} |
|
if (data.SignedPreKey) { |
|
bundle.signedPreKeyId = data.SignedPreKeyId; |
|
bundle.signedPreKey = deserializePublicKey(data.SignedPreKey); |
|
bundle.signedPreKeySignature = |
|
deserializeSignature(data.SignedPreKeySignature); |
|
} |
|
this.storePeerIdentity(peer, bundle.identityKey); |
|
// TODO(fancycode): Add error handling. |
|
this.axol.createSessionFromPreKeyBundle(bundle) |
|
.then(_.bind(function(session) { |
|
this.sessions[peer] = session; |
|
this.processPendingMessages(peer); |
|
}, this)); |
|
}; |
|
|
|
EndToEndEncryption.prototype.encrypt = function(peer, type, message, callback) { |
|
var msg = {"Type": type}; |
|
msg[type] = message; |
|
message = JSON.stringify(msg); |
|
if (this.is_processing_messages[peer]) { |
|
this.pending_messages[peer].push({ |
|
"type": "encrypt", |
|
"message": message, |
|
"callback": callback |
|
}); |
|
return; |
|
} |
|
|
|
this.is_processing_messages[peer] = true; |
|
this.pending_messages[peer] = []; |
|
var session = this.sessions[peer]; |
|
if (!session) { |
|
this.pending_messages[peer].push({ |
|
"type": "encrypt", |
|
"message": message, |
|
"callback": callback |
|
}); |
|
this.events.triggerHandler("identity.request", [peer]); |
|
this.apiSend("EncryptionRequestKeyBundle", {"To": peer}); |
|
return; |
|
} |
|
this.encryptWithSession(peer, session, message, callback); |
|
}; |
|
|
|
EndToEndEncryption.prototype.encryptWithSession = function( |
|
peer, session, message, callback) { |
|
var buffer = ByteBuffer.fromUTF8(message).toArrayBuffer(); |
|
// TODO(fancycode): Add error handling. |
|
this.axol.encryptMessage(session, buffer) |
|
.then(_.bind(function(encrypted) { |
|
this.sessions[peer] = encrypted.session; |
|
var msg = { |
|
"Message": encrypted.isPreKeyWhisperMessage ? |
|
"PreKeyWhisper" : |
|
"Whisper", |
|
"Data": ByteBuffer.wrap(encrypted.body).toBase64() |
|
}; |
|
callback("Encrypted", msg); |
|
this.processPendingMessages(peer); |
|
}, this)); |
|
}; |
|
|
|
EndToEndEncryption.prototype.decrypt = function(peer, message, callback) { |
|
if (this.is_processing_messages[peer]) { |
|
this.pending_messages[peer].push({ |
|
"type": "decrypt", |
|
"message": message, |
|
"callback": callback |
|
}); |
|
return; |
|
} |
|
this.is_processing_messages[peer] = true; |
|
this.pending_messages[peer] = []; |
|
var session = this.sessions[peer] || null; |
|
this.decryptWithSession(peer, session, message, callback); |
|
}; |
|
|
|
EndToEndEncryption.prototype.decryptWithSession = function( |
|
peer, session, message, callback) { |
|
var handle_decrypted = _.bind(function(decrypted) { |
|
if (decrypted.identityKey) { |
|
this.storePeerIdentity(peer, decrypted.identityKey); |
|
} |
|
this.sessions[peer] = decrypted.session; |
|
var msg = ByteBuffer.wrap(decrypted.message).toUTF8(); |
|
callback(JSON.parse(msg)); |
|
this.processPendingMessages(peer); |
|
}, this); |
|
|
|
if (typeof(message) === "string") { |
|
message = JSON.parse(message); |
|
} |
|
var buffer = ByteBuffer.fromBase64(message.Data).toArrayBuffer(); |
|
switch (message.Message) { |
|
case "PreKeyWhisper": |
|
// TODO(fancycode): Add error handling. |
|
this.axol.decryptPreKeyWhisperMessage(session, buffer) |
|
.then(handle_decrypted); |
|
break; |
|
case "Whisper": |
|
// TODO(fancycode): Add error handling. |
|
this.axol.decryptWhisperMessage(session, buffer) |
|
.then(handle_decrypted); |
|
break; |
|
} |
|
}; |
|
|
|
var endToEndEncryption; |
|
|
|
var events = $({}); |
|
|
|
// Only export limited public encryption API. |
|
var endToEndEncryptionApi = { |
|
"events": events, |
|
"initialize": function(api) { |
|
if (endToEndEncryption) { |
|
return endToEndEncryption; |
|
} |
|
endToEndEncryption = new EndToEndEncryption(api, events); |
|
if (!endToEndEncryption.isSupported()) { |
|
console.warn("EndToEnd encryption services not supported"); |
|
endToEndEncryption = null; |
|
return null; |
|
} |
|
console.log("EndToEnd encryption services initialized"); |
|
return endToEndEncryption; |
|
}, |
|
"encrypt": function(peer, type, message, callback) { |
|
if (!endToEndEncryption) { |
|
callback(type, message); |
|
return; |
|
} |
|
endToEndEncryption.encrypt(peer, type, message, callback); |
|
}, |
|
"decrypt": function(peer, message, callback) { |
|
if (!endToEndEncryption) { |
|
callback(message); |
|
return; |
|
} |
|
endToEndEncryption.decrypt(peer, message, callback); |
|
} |
|
}; |
|
|
|
return endToEndEncryptionApi; |
|
|
|
}]; |
|
|
|
});
|
|
|