Skip to content

Commit

Permalink
#94 add support for AES-CFB and AES-CTR
Browse files Browse the repository at this point in the history
  • Loading branch information
totaam committed Sep 2, 2021
1 parent 9961b73 commit a28cd3a
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 14 deletions.
10 changes: 9 additions & 1 deletion html5/js/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ XpraClient.prototype.init_state = function(container) {
this.mediasource_codecs = {};
// encryption
this.encryption = false;
this.encryption_mode = null;
this.encryption_key = null;
this.cipher_in_caps = null;
this.cipher_out_caps = null;
Expand Down Expand Up @@ -1198,8 +1199,11 @@ XpraClient.prototype._make_hello_base = function() {
if(this.encryption) {
this.cipher_in_caps = {
"cipher" : this.encryption,
"cipher.mode" : this.encryption_mode || "CBC",
"cipher.iv" : Utilities.getSecureRandomString(16),
"cipher.key_salt" : Utilities.getSecureRandomString(32),
"cipher.key_size" : 32,
"cipher.key_hash" : "SHA1",
"cipher.key_stretch_iterations" : 1000,
"cipher.padding.options" : ["PKCS#7"],
};
Expand Down Expand Up @@ -1799,7 +1803,11 @@ XpraClient.prototype._process_hello = function(packet, ctx) {
// check for server encryption caps update
if(ctx.encryption) {
ctx.cipher_out_caps = {};
const CIPHER_CAPS = ["", ".iv", ".key_salt", ".key_stretch_iterations"];
const CIPHER_CAPS = [
"", ".mode", ".iv",
".key_salt", ".key_size", ".key_hash", ".key_stretch_iterations",
".padding", ".padding.options",
];
for (let i=0; i<CIPHER_CAPS.length; ++i) {
const cipher_key = "cipher"+CIPHER_CAPS[i];
ctx.cipher_out_caps[cipher_key] = hello[cipher_key];
Expand Down
90 changes: 77 additions & 13 deletions html5/js/Protocol.js
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ XpraProtocol.prototype.do_process_receive_queue = function() {

// work out padding if necessary
let padding = 0;
if (proto_crypto) {
if (proto_crypto && this.cipher_in_block_size>0) {
padding = (this.cipher_in_block_size - packet_size % this.cipher_in_block_size);
packet_size += padding;
}
Expand Down Expand Up @@ -356,11 +356,10 @@ XpraProtocol.prototype.do_process_receive_queue = function() {
if (proto_crypto) {
this.cipher_in.update(forge.util.createBuffer(uintToString(packet_data)));
const decrypted = this.cipher_in.output.getBytes();
packet_data = [];
for (i=0; i<decrypted.length; i++) {
packet_data.push(decrypted[i].charCodeAt(0));
packet_data = new Uint8Array(packet_size-padding);
for (i=0; i<packet_size-padding; i++) {
packet_data[i] = decrypted[i].charCodeAt(0);
}
packet_data = new Uint8Array(packet_data.slice(0, -1 * padding));
}

//decompress it if needed:
Expand Down Expand Up @@ -575,28 +574,93 @@ XpraProtocol.prototype.set_packet_handler = function(callback, ctx) {
};

XpraProtocol.prototype.set_cipher_in = function(caps, key) {
this.cipher_in_block_size = 32;
if (!key) {
throw "missing encryption key";
}
// stretch the password
const secret = forge.pkcs5.pbkdf2(key, caps['cipher.key_salt'], caps['cipher.key_stretch_iterations'], this.cipher_in_block_size);
const cipher = caps["cipher"];
if (cipher!="AES") {
throw "unsupported encryption specified: '"+cipher+"'";
}
const salt = caps["cipher.key_salt"];
const iterations = caps["cipher.key_stretch_iterations"];
if (iterations<0) {
throw "invalid number of iterations: "+iterations;
}
const DEFAULT_KEYSIZE = 32;
const key_size = caps["cipher.key_size"] || DEFAULT_KEYSIZE;
if (key_size<=16) {
throw "invalid key size '"+key_size+"'";
}
const DEFAULT_KEY_HASH = "SHA1";
const key_hash = (caps["cipher.key_hash"] || DEFAULT_KEY_HASH).toLowerCase();
const secret = forge.pkcs5.pbkdf2(key, salt, iterations, key_size, key_hash);
const DEFAULT_MODE = "CBC";
const mode = caps["cipher.mode"] || DEFAULT_MODE;
if (mode=="CBC") {
this.cipher_in_block_size = 32;
}
else if (["CFB", "CTR"].indexOf(mode)>=0){
this.cipher_in_block_size = 0;
}
else {
throw "unsupported AES mode '"+mode+"'";
}
// start the cipher
this.cipher_in = forge.cipher.createDecipher('AES-CBC', secret);
this.cipher_in.start({iv: caps['cipher.iv']});
this.cipher_in = forge.cipher.createDecipher(cipher+"-"+mode, secret);
const iv = caps["cipher.iv"];
if (!iv) {
throw "missing IV";
}
this.cipher_in.start({iv: iv, tagLength : 0, tag : ""});
};

XpraProtocol.prototype.set_cipher_out = function(caps, key) {
this.cipher_out_block_size = 32;
// stretch the password
if (!key) {
throw "missing encryption key";
}
function cipher_cap(k) {
var value = caps['cipher'+k];
if ((typeof value) === 'object' && value.constructor===Uint8Array) {
value = String.fromCharCode.apply(null, value);
}
return value;
}
const secret = forge.pkcs5.pbkdf2(key, cipher_cap('.key_salt'), cipher_cap('.key_stretch_iterations'), this.cipher_out_block_size);
const cipher = cipher_cap("") || "AES";
if (cipher!="AES") {
throw "unsupported encryption specified: '"+cipher+"'";
}
const key_salt = cipher_cap('.key_salt');
const iterations = cipher_cap('.key_stretch_iterations');
if (iterations<0) {
throw "invalid number of iterations: "+iterations;
}
const DEFAULT_KEYSIZE = 32;
const key_size = cipher_cap('.key_size') || DEFAULT_KEYSIZE;
if (key_size<=16) {
throw "invalid key size '"+key_size+"'";
}
// stretch the password
const DEFAULT_KEY_HASH = "SHA1";
const key_hash = (cipher_cap(".key_hash") || DEFAULT_KEY_HASH).toLowerCase();
const secret = forge.pkcs5.pbkdf2(key, key_salt, iterations, key_size, key_hash);
const DEFAULT_MODE = "CBC";
const mode = cipher_cap(".mode") || DEFAULT_MODE;
if (mode=="CBC") {
this.cipher_out_block_size = 32;
}
else if (["CFB", "CTR"].indexOf(mode)>=0){
this.cipher_out_block_size = 0;
}
else {
throw "unsupported AES mode '"+mode+"'";
}
// start the cipher
const iv = cipher_cap('.iv');
this.cipher_out = forge.cipher.createCipher('AES-CBC', secret);
if (!iv) {
throw "missing IV";
}
this.cipher_out = forge.cipher.createCipher(cipher+"-"+mode, secret);
this.cipher_out.start({iv: iv});
};

Expand Down

0 comments on commit a28cd3a

Please sign in to comment.