1179 lines
26 KiB
JavaScript
1179 lines
26 KiB
JavaScript
|
/*global require: false,
|
||
|
slowAES: false,
|
||
|
window: false,
|
||
|
exports: false,
|
||
|
rstr_hmac_sha256: false,
|
||
|
b64tohex: false,
|
||
|
hex2b64: false,
|
||
|
rstr_sha256: false,
|
||
|
Uint8Array: false,
|
||
|
escape: false,
|
||
|
unescape: false,
|
||
|
PBKDF2: false,
|
||
|
rstr_sha1: false,
|
||
|
Buffer: false,
|
||
|
RSAKey: false,
|
||
|
process: false */
|
||
|
/*jslint nomen: true */
|
||
|
|
||
|
// Simple symmetric and asymmetric crypto.
|
||
|
// Note: Keep an eye on http://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-02
|
||
|
|
||
|
var SHA1_SIZE = 20,
|
||
|
SHA256_SIZE = 32,
|
||
|
AES_BLOCK_SIZE = 16,
|
||
|
AES_128_KEY_SIZE = 16,
|
||
|
|
||
|
Crypt = function (parsed_key, options)
|
||
|
{
|
||
|
"use strict";
|
||
|
|
||
|
this.key = parsed_key;
|
||
|
|
||
|
options = options || {};
|
||
|
|
||
|
this.options = {};
|
||
|
this.options.json = options.json !== false;
|
||
|
this.options.check = options.check !== false;
|
||
|
this.options.pad = options.pad !== false;
|
||
|
this.options.custom = options.custom;
|
||
|
};
|
||
|
|
||
|
Crypt.make = function (key, options, cb)
|
||
|
{
|
||
|
"use strict";
|
||
|
|
||
|
if (!cb)
|
||
|
{
|
||
|
cb = options;
|
||
|
options = {};
|
||
|
}
|
||
|
|
||
|
if (!cb)
|
||
|
{
|
||
|
cb = key;
|
||
|
key = undefined;
|
||
|
}
|
||
|
|
||
|
if (typeof cb !== 'function')
|
||
|
{
|
||
|
options = cb;
|
||
|
cb = undefined;
|
||
|
}
|
||
|
|
||
|
var This = this,
|
||
|
crypt = new This(undefined, options);
|
||
|
|
||
|
This.parse_key(key, function (err, parsed_key)
|
||
|
{
|
||
|
if (err)
|
||
|
{
|
||
|
cb(err);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
crypt.key = parsed_key;
|
||
|
|
||
|
if (cb)
|
||
|
{
|
||
|
cb(null, crypt);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return crypt;
|
||
|
};
|
||
|
|
||
|
Crypt.get_version = function ()
|
||
|
{
|
||
|
"use strict";
|
||
|
return 1;
|
||
|
};
|
||
|
|
||
|
Crypt.get_key_size = function ()
|
||
|
{
|
||
|
"use strict";
|
||
|
return AES_128_KEY_SIZE;
|
||
|
};
|
||
|
|
||
|
Crypt.prototype.get_key = function ()
|
||
|
{
|
||
|
"use strict";
|
||
|
return this.key;
|
||
|
};
|
||
|
|
||
|
Crypt.prototype.check_version = function (data, f)
|
||
|
{
|
||
|
"use strict";
|
||
|
|
||
|
if (data.version > Crypt.get_version())
|
||
|
{
|
||
|
f.call(this, 'unsupported version');
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
};
|
||
|
|
||
|
Crypt.prototype.maybe_encrypt = function (arg_encrypt,
|
||
|
arg_data,
|
||
|
arg_f,
|
||
|
arg_get_key)
|
||
|
{
|
||
|
"use strict";
|
||
|
|
||
|
var ths = this, encrypt, data, f, get_key, get_key_data,
|
||
|
|
||
|
encrypted = function (err, edata, key_data)
|
||
|
{
|
||
|
if (err)
|
||
|
{
|
||
|
f.call(this, err);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
f.call(this, null, { encrypted: true, data: edata, key_data: key_data });
|
||
|
}
|
||
|
},
|
||
|
|
||
|
not_encrypted = function ()
|
||
|
{
|
||
|
f.call(this, null, { encrypted: false, data: data });
|
||
|
};
|
||
|
|
||
|
if (typeof arg_data === 'function')
|
||
|
{
|
||
|
get_key_data = Array.prototype.slice.call(arguments, 3);
|
||
|
get_key = arg_f;
|
||
|
f = arg_data;
|
||
|
data = arg_encrypt;
|
||
|
encrypt = (get_key !== undefined) || this.key;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
get_key_data = Array.prototype.slice.call(arguments, 4);
|
||
|
get_key = arg_get_key;
|
||
|
f = arg_f;
|
||
|
data = arg_data;
|
||
|
encrypt = arg_encrypt;
|
||
|
}
|
||
|
|
||
|
if (encrypt)
|
||
|
{
|
||
|
if (get_key !== undefined)
|
||
|
{
|
||
|
get_key_data.push(function (err, key, key_data, iv)
|
||
|
{
|
||
|
if (err)
|
||
|
{
|
||
|
f.call(ths, err);
|
||
|
}
|
||
|
else if (key)
|
||
|
{
|
||
|
Crypt.make(key, ths.options, function (err, crypt)
|
||
|
{
|
||
|
if (err)
|
||
|
{
|
||
|
f.call(ths, err);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
crypt.encrypt(data, iv, function (err, data)
|
||
|
{
|
||
|
encrypted.call(this, err, data, key_data);
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
not_encrypted.call(ths);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
get_key.apply(this, get_key_data);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
this.encrypt(data, encrypted);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
not_encrypted.call(this);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Crypt.prototype.maybe_decrypt = function (data, f, get_key)
|
||
|
{
|
||
|
"use strict";
|
||
|
|
||
|
if (data.encrypted)
|
||
|
{
|
||
|
if (get_key !== undefined)
|
||
|
{
|
||
|
var ths = this,
|
||
|
get_key_data = Array.prototype.slice.call(arguments, 3);
|
||
|
|
||
|
get_key_data.push(function (err, key)
|
||
|
{
|
||
|
if (err)
|
||
|
{
|
||
|
f.call(ths, err);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Crypt.make(key, ths.options, function (err, crypt)
|
||
|
{
|
||
|
if (err)
|
||
|
{
|
||
|
f.call(ths, err);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
crypt.decrypt(data.data, f);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
get_key_data.push(data.key_data);
|
||
|
|
||
|
get_key.apply(this, get_key_data);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
this.decrypt(data.data, f);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
f.call(this, null, data.data);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Crypt.prototype.maybe_sign = function (arg_sign, arg_data, arg_f, arg_get_key)
|
||
|
{
|
||
|
"use strict";
|
||
|
|
||
|
var ths = this, sign, data, f, get_key, get_key_data,
|
||
|
|
||
|
signed = function (err, sdata, key_data)
|
||
|
{
|
||
|
if (err)
|
||
|
{
|
||
|
f.call(this, err);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
f.call(this, null, { signed: true, data: sdata, key_data: key_data });
|
||
|
}
|
||
|
},
|
||
|
|
||
|
not_signed = function ()
|
||
|
{
|
||
|
f.call(this, null, { signed: false, data: data });
|
||
|
};
|
||
|
|
||
|
if (typeof arg_data === 'function')
|
||
|
{
|
||
|
get_key_data = Array.prototype.slice.call(arguments, 3);
|
||
|
get_key = arg_f;
|
||
|
f = arg_data;
|
||
|
data = arg_sign;
|
||
|
sign = (get_key !== undefined) || this.key;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
get_key_data = Array.prototype.slice.call(arguments, 4);
|
||
|
get_key = arg_get_key;
|
||
|
f = arg_f;
|
||
|
data = arg_data;
|
||
|
sign = arg_sign;
|
||
|
}
|
||
|
|
||
|
if (sign)
|
||
|
{
|
||
|
if (get_key !== undefined)
|
||
|
{
|
||
|
get_key_data.push(function (err, key, key_data)
|
||
|
{
|
||
|
if (err)
|
||
|
{
|
||
|
f.call(ths, err);
|
||
|
}
|
||
|
else if (key)
|
||
|
{
|
||
|
Crypt.make(key, ths.options, function (err, crypt)
|
||
|
{
|
||
|
if (err)
|
||
|
{
|
||
|
f.call(ths, err);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
crypt.sign(data, function (err, data)
|
||
|
{
|
||
|
signed.call(this, err, data, key_data);
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
not_signed.call(ths);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
get_key.apply(this, get_key_data);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
this.sign(data, signed);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
not_signed.call(this);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Crypt.prototype.maybe_verify = function (data, f, get_key)
|
||
|
{
|
||
|
"use strict";
|
||
|
|
||
|
if (data.signed)
|
||
|
{
|
||
|
if (get_key !== undefined)
|
||
|
{
|
||
|
var ths = this,
|
||
|
get_key_data = Array.prototype.slice.call(arguments, 3);
|
||
|
|
||
|
get_key_data.push(function (err, key)
|
||
|
{
|
||
|
if (err)
|
||
|
{
|
||
|
f.call(ths, err);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Crypt.make(key, ths.options, function (err, crypt)
|
||
|
{
|
||
|
if (err)
|
||
|
{
|
||
|
f.call(ths, err);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
crypt.verify(data.data, f);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
|
||
|
get_key_data.push(data.key_data);
|
||
|
|
||
|
get_key.apply(this, get_key_data);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
this.verify(data.data, f);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
f.call(this, null, data.data);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Crypt.sign_encrypt_sign = function (signing_key, encryption_key, data, iv, f)
|
||
|
{
|
||
|
"use strict";
|
||
|
|
||
|
if (!f)
|
||
|
{
|
||
|
f = iv;
|
||
|
iv = null;
|
||
|
}
|
||
|
|
||
|
var This = this;
|
||
|
|
||
|
This.make(signing_key, function (err, signer)
|
||
|
{
|
||
|
if (err)
|
||
|
{
|
||
|
f(err);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
This.make(encryption_key, function (err, encrypter)
|
||
|
{
|
||
|
if (err)
|
||
|
{
|
||
|
f(err);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
signer.sign(data, function (err, sv)
|
||
|
{
|
||
|
if (err)
|
||
|
{
|
||
|
f(err);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
encrypter.encrypt(sv, iv, function (err, ev)
|
||
|
{
|
||
|
if (err)
|
||
|
{
|
||
|
f(err);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
signer.sign(ev, f);
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
};
|
||
|
|
||
|
Crypt.verify_decrypt_verify = function (decryption_key, verifying_key, data, f)
|
||
|
{
|
||
|
"use strict";
|
||
|
|
||
|
var This = this;
|
||
|
|
||
|
This.make(verifying_key, function (err, verifier)
|
||
|
{
|
||
|
if (err)
|
||
|
{
|
||
|
f(err);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
This.make(decryption_key, function (err, decrypter)
|
||
|
{
|
||
|
if (err)
|
||
|
{
|
||
|
f(err);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
verifier.verify(data, function (err, vv)
|
||
|
{
|
||
|
if (err)
|
||
|
{
|
||
|
f(err);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
decrypter.decrypt(vv, function (err, dv)
|
||
|
{
|
||
|
if (err)
|
||
|
{
|
||
|
f(err);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
verifier.verify(dv, f);
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
};
|
||
|
|
||
|
var SlowCrypt;
|
||
|
|
||
|
if (typeof require === 'function')
|
||
|
{
|
||
|
var crypto = require('crypto'),
|
||
|
ursa = require('ursa'),
|
||
|
workaround = process.version.lastIndexOf('v0.8', 0) === 0;
|
||
|
|
||
|
Crypt.parse_key = function (key, cb)
|
||
|
{
|
||
|
"use strict";
|
||
|
|
||
|
if ((typeof key === 'string') &&
|
||
|
(key.lastIndexOf('-----BEGIN', 0) === 0))
|
||
|
{
|
||
|
if (key.indexOf('PUBLIC KEY') > 0)
|
||
|
{
|
||
|
key = ursa.createPublicKey(key, 'utf8');
|
||
|
}
|
||
|
else if (key.indexOf('PRIVATE KEY') > 0)
|
||
|
{
|
||
|
key = ursa.createPrivateKey(key, '', 'utf8');
|
||
|
}
|
||
|
|
||
|
cb(null, key);
|
||
|
}
|
||
|
else if (key && key.password)
|
||
|
{
|
||
|
var salt = key.salt || crypto.randomBytes(SHA1_SIZE),
|
||
|
hash;
|
||
|
|
||
|
if (salt.length < SHA1_SIZE)
|
||
|
{
|
||
|
hash = crypto.createHash('sha1');
|
||
|
hash.update(salt);
|
||
|
salt = hash.digest();
|
||
|
}
|
||
|
|
||
|
crypto.pbkdf2(key.password, salt, key.iterations, AES_128_KEY_SIZE,
|
||
|
function (err, derived_key)
|
||
|
{
|
||
|
if (err)
|
||
|
{
|
||
|
cb(err);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (key.progress)
|
||
|
{
|
||
|
key.progress(100);
|
||
|
}
|
||
|
|
||
|
cb(null,
|
||
|
{
|
||
|
key: derived_key,
|
||
|
salt: salt
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
cb(null, key);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Crypt.prototype.stringify = function (data)
|
||
|
{
|
||
|
"use strict";
|
||
|
return this.options.json ? new Buffer(JSON.stringify(data), 'utf8') : data;
|
||
|
};
|
||
|
|
||
|
Crypt.prototype.parse = function (data)
|
||
|
{
|
||
|
"use strict";
|
||
|
return this.options.json ? JSON.parse(data.toString('utf8')) : data;
|
||
|
};
|
||
|
|
||
|
Crypt.prototype.encrypt = function (data, iv, f)
|
||
|
{
|
||
|
"use strict";
|
||
|
|
||
|
var key, ekey, iv64, cipher, jdata, edata = '',
|
||
|
encoding = workaround ? 'hex' : 'base64';
|
||
|
|
||
|
try
|
||
|
{
|
||
|
if (!f)
|
||
|
{
|
||
|
f = iv;
|
||
|
iv = null;
|
||
|
}
|
||
|
|
||
|
if (this.key.privateEncrypt)
|
||
|
{
|
||
|
f.call(this, "can't encrypt using private key");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (this.key.encrypt)
|
||
|
{
|
||
|
key = crypto.randomBytes(AES_128_KEY_SIZE);
|
||
|
ekey = this.key.encrypt(key, undefined, 'base64', ursa.RSA_PKCS1_OAEP_PADDING);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
key = this.key.key || this.key;
|
||
|
}
|
||
|
|
||
|
iv = iv || crypto.randomBytes(AES_BLOCK_SIZE);
|
||
|
iv64 = iv.toString('base64');
|
||
|
|
||
|
cipher = crypto.createCipheriv('AES-128-CBC', key, iv);
|
||
|
cipher.setAutoPadding(this.options.pad);
|
||
|
|
||
|
jdata = this.stringify(data);
|
||
|
|
||
|
if (this.options.check)
|
||
|
{
|
||
|
edata = cipher.update(crypto.createHash('sha256')
|
||
|
.update(jdata)
|
||
|
.digest(), null, encoding);
|
||
|
}
|
||
|
|
||
|
edata += cipher.update(jdata, null, encoding);
|
||
|
|
||
|
edata += cipher.final(encoding);
|
||
|
|
||
|
if (workaround)
|
||
|
{
|
||
|
edata = new Buffer(edata, encoding).toString('base64');
|
||
|
}
|
||
|
}
|
||
|
catch (ex)
|
||
|
{
|
||
|
f.call(this, ex);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
f.call(this, null, { iv: iv64, data: edata, ekey: ekey, version: Crypt.get_version() });
|
||
|
};
|
||
|
|
||
|
Crypt.prototype.decrypt = function (data, f)
|
||
|
{
|
||
|
"use strict";
|
||
|
|
||
|
var key, decipher, ddata, jdata;
|
||
|
|
||
|
try
|
||
|
{
|
||
|
if (!this.check_version(data, f))
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (this.key.decrypt)
|
||
|
{
|
||
|
key = this.key.decrypt(data.ekey, 'base64', undefined, ursa.RSA_PKCS1_OAEP_PADDING);
|
||
|
}
|
||
|
else if (!this.key.publicDecrypt)
|
||
|
{
|
||
|
key = this.key.key || this.key;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
f.call(this, "can't decrypt using public key");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
decipher = crypto.createDecipheriv(
|
||
|
'AES-128-CBC',
|
||
|
key,
|
||
|
new Buffer(data.iv, 'base64'));
|
||
|
decipher.setAutoPadding(this.options.pad);
|
||
|
|
||
|
ddata = decipher.update(data.data, 'base64');
|
||
|
|
||
|
if (workaround)
|
||
|
{
|
||
|
ddata = new Buffer(ddata + decipher.final(), 'binary');
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ddata = Buffer.concat([ddata, decipher.final()]);
|
||
|
}
|
||
|
|
||
|
if (this.options.check)
|
||
|
{
|
||
|
jdata = ddata.slice(SHA256_SIZE);
|
||
|
|
||
|
if (crypto.createHash('sha256')
|
||
|
.update(jdata)
|
||
|
.digest('base64') !== ddata.toString('base64', 0, SHA256_SIZE))
|
||
|
{
|
||
|
f.call(this, 'digest mismatch');
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
jdata = ddata;
|
||
|
}
|
||
|
|
||
|
jdata = this.parse(jdata);
|
||
|
}
|
||
|
catch (ex)
|
||
|
{
|
||
|
f.call(this, ex);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
f.call(this, null, jdata);
|
||
|
};
|
||
|
|
||
|
Crypt.prototype.sign = function (data, f)
|
||
|
{
|
||
|
"use strict";
|
||
|
|
||
|
var jdata, signature;
|
||
|
|
||
|
try
|
||
|
{
|
||
|
jdata = this.stringify(data);
|
||
|
|
||
|
if (this.key.hashAndSign)
|
||
|
{
|
||
|
signature = this.key.hashAndSign('sha256', jdata, null, 'base64', ursa.RSA_PKCS1_PSS_PADDING);
|
||
|
}
|
||
|
else if (!this.key.encrypt)
|
||
|
{
|
||
|
signature = crypto.createHmac('sha256', this.key.key || this.key)
|
||
|
.update(jdata)
|
||
|
.digest('base64');
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
f.call(this, "can't sign using public key");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
jdata = jdata.toString('base64');
|
||
|
}
|
||
|
catch (ex)
|
||
|
{
|
||
|
f.call(this, ex);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
f.call(this, null, { data: jdata, signature: signature, version: Crypt.get_version() });
|
||
|
};
|
||
|
|
||
|
Crypt.prototype.verify = function (data, f)
|
||
|
{
|
||
|
"use strict";
|
||
|
|
||
|
var match, ddata, jdata;
|
||
|
|
||
|
try
|
||
|
{
|
||
|
if (!this.check_version(data, f))
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
ddata = new Buffer(data.data, 'base64');
|
||
|
|
||
|
if (this.key.decrypt)
|
||
|
{
|
||
|
f.call(this, "can't verify using private key");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (this.key.hashAndVerify)
|
||
|
{
|
||
|
match = this.key.hashAndVerify(
|
||
|
'sha256',
|
||
|
data.data,
|
||
|
data.signature,
|
||
|
'base64',
|
||
|
ursa.RSA_PKCS1_PSS_PADDING);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
match = crypto.createHmac('sha256', this.key.key || this.key)
|
||
|
.update(ddata)
|
||
|
.digest('base64') === data.signature;
|
||
|
}
|
||
|
|
||
|
if (match)
|
||
|
{
|
||
|
jdata = this.parse(ddata);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
f.call(this, 'digest mismatch');
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
catch (ex)
|
||
|
{
|
||
|
f.call(this, ex);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
f.call(this, null, jdata);
|
||
|
};
|
||
|
|
||
|
SlowCrypt = function ()
|
||
|
{
|
||
|
"use strict";
|
||
|
Crypt.apply(this, arguments);
|
||
|
};
|
||
|
|
||
|
SlowCrypt.make = Crypt.make;
|
||
|
SlowCrypt.get_version = Crypt.get_version;
|
||
|
SlowCrypt.get_key_size = Crypt.get_key_size;
|
||
|
SlowCrypt.sign_encrypt_sign = Crypt.sign_encrypt_sign;
|
||
|
SlowCrypt.verify_decrypt_verify = Crypt.verify_decrypt_verify;
|
||
|
|
||
|
SlowCrypt.prototype = Object.create(Crypt.prototype);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SlowCrypt = Crypt;
|
||
|
}
|
||
|
|
||
|
var get_char_codes = function(s)
|
||
|
{
|
||
|
"use strict";
|
||
|
|
||
|
var r = [], i;
|
||
|
|
||
|
for (i = 0; i < s.length; i += 1)
|
||
|
{
|
||
|
r.push(s.charCodeAt(i));
|
||
|
}
|
||
|
|
||
|
return r;
|
||
|
};
|
||
|
|
||
|
SlowCrypt.parse_key = function (key, cb)
|
||
|
{
|
||
|
"use strict";
|
||
|
|
||
|
var rsa_key, salt, pbkdf2;
|
||
|
|
||
|
if (typeof key === 'string')
|
||
|
{
|
||
|
if (key.lastIndexOf('-----BEGIN', 0) === 0)
|
||
|
{
|
||
|
if (key.indexOf('PUBLIC KEY') > 0)
|
||
|
{
|
||
|
rsa_key = new RSAKey();
|
||
|
rsa_key.readPublicKeyFromPEMString(key);
|
||
|
cb(null, rsa_key);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (key.indexOf('PRIVATE KEY') > 0)
|
||
|
{
|
||
|
rsa_key = new RSAKey();
|
||
|
rsa_key.readPrivateKeyFromPEMString(key);
|
||
|
cb(null, rsa_key);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cb(null, get_char_codes(key));
|
||
|
}
|
||
|
else if (key && key.password)
|
||
|
{
|
||
|
salt = key.salt;
|
||
|
|
||
|
if (!salt)
|
||
|
{
|
||
|
salt = new Uint8Array(SHA1_SIZE);
|
||
|
window.crypto.getRandomValues(salt);
|
||
|
salt = String.fromCharCode.apply(String, Array.prototype.slice.call(salt));
|
||
|
}
|
||
|
|
||
|
if (salt.length < SHA1_SIZE)
|
||
|
{
|
||
|
salt = rstr_sha1(salt);
|
||
|
}
|
||
|
|
||
|
pbkdf2 = new PBKDF2(key.password,
|
||
|
salt,
|
||
|
key.iterations,
|
||
|
AES_128_KEY_SIZE);
|
||
|
|
||
|
pbkdf2.deriveKey(
|
||
|
key.progress || function () { return undefined; },
|
||
|
function (derived_key)
|
||
|
{
|
||
|
var bytes = [], i;
|
||
|
|
||
|
for (i = 0; i < derived_key.length; i += 2)
|
||
|
{
|
||
|
bytes.push(parseInt(derived_key.substr(i, 2), 16));
|
||
|
}
|
||
|
|
||
|
cb(null,
|
||
|
{
|
||
|
key: bytes,
|
||
|
salt: salt
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
cb(null, key);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
SlowCrypt.prototype.stringify = function (data)
|
||
|
{
|
||
|
"use strict";
|
||
|
|
||
|
// http://ecmanaut.blogspot.co.uk/2006/07/encoding-decoding-utf8-in-javascript.html
|
||
|
return this.options.json ? unescape(encodeURIComponent(JSON.stringify(data))) : data;
|
||
|
};
|
||
|
|
||
|
SlowCrypt.prototype.parse = function (data)
|
||
|
{
|
||
|
"use strict";
|
||
|
return this.options.json ? JSON.parse(decodeURIComponent(escape(data))) : data;
|
||
|
};
|
||
|
|
||
|
SlowCrypt.prototype.encrypt = function (data, iv, f)
|
||
|
{
|
||
|
"use strict";
|
||
|
|
||
|
var key_arr, ekey, iv64, jdata, edata;
|
||
|
|
||
|
try
|
||
|
{
|
||
|
if (!f)
|
||
|
{
|
||
|
f = iv;
|
||
|
iv = null;
|
||
|
}
|
||
|
|
||
|
if (this.key.isPrivate)
|
||
|
{
|
||
|
f.call(this, "can't encrypt using private key");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (this.key.isPublic)
|
||
|
{
|
||
|
key_arr = new Uint8Array(AES_128_KEY_SIZE);
|
||
|
window.crypto.getRandomValues(key_arr);
|
||
|
ekey = hex2b64(this.key.encryptOAEP(String.fromCharCode.apply(String, Array.prototype.slice.call(key_arr))/*, rstr_sha256, 32*/));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
key_arr = this.key.key || this.key;
|
||
|
}
|
||
|
|
||
|
if (!iv)
|
||
|
{
|
||
|
iv = new Uint8Array(AES_BLOCK_SIZE);
|
||
|
window.crypto.getRandomValues(iv);
|
||
|
}
|
||
|
|
||
|
iv64 = window.btoa(String.fromCharCode.apply(String, Array.prototype.slice.call(iv)));
|
||
|
|
||
|
jdata = this.stringify(data);
|
||
|
|
||
|
if (!this.options.pad)
|
||
|
{
|
||
|
slowAES._padBytesIn_save = slowAES.padBytesIn;
|
||
|
slowAES.padBytesIn = function (data)
|
||
|
{
|
||
|
return data;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
if (this.options.check)
|
||
|
{
|
||
|
if (typeof jdata !== 'string')
|
||
|
{
|
||
|
jdata = String.fromCharCode.apply(String, jdata);
|
||
|
}
|
||
|
|
||
|
jdata = get_char_codes(rstr_sha256(jdata) + jdata);
|
||
|
}
|
||
|
else if (typeof jdata === 'string')
|
||
|
{
|
||
|
jdata = get_char_codes(jdata);
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
edata = slowAES.encrypt(
|
||
|
jdata,
|
||
|
slowAES.modeOfOperation.CBC,
|
||
|
key_arr,
|
||
|
iv);
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
if (!this.options.pad)
|
||
|
{
|
||
|
slowAES.padBytesIn = slowAES._padBytesIn_save;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
edata = window.btoa(String.fromCharCode.apply(String, edata));
|
||
|
}
|
||
|
catch (ex)
|
||
|
{
|
||
|
f.call(this, ex);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
f.call(this, null, { iv: iv64, data: edata, ekey: ekey, version: SlowCrypt.get_version() });
|
||
|
};
|
||
|
|
||
|
SlowCrypt.prototype.decrypt = function (data, f)
|
||
|
{
|
||
|
"use strict";
|
||
|
|
||
|
var key_arr, ddata, jdata;
|
||
|
|
||
|
try
|
||
|
{
|
||
|
if (!this.check_version(data, f))
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (this.key.isPublic)
|
||
|
{
|
||
|
f.call(this, "can't decrypt using public key");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (this.key.isPrivate)
|
||
|
{
|
||
|
key_arr = get_char_codes(this.key.decryptOAEP(b64tohex(data.ekey)/*, rstr_sha256, 32*/));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
key_arr = this.key.key || this.key;
|
||
|
}
|
||
|
|
||
|
if (!this.options.pad)
|
||
|
{
|
||
|
slowAES._unpadBytesOut_save = slowAES.unpadBytesOut;
|
||
|
slowAES.unpadBytesOut = function (data)
|
||
|
{
|
||
|
return data;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
ddata = String.fromCharCode.apply(String, slowAES.decrypt(
|
||
|
get_char_codes(window.atob(data.data)),
|
||
|
slowAES.modeOfOperation.CBC,
|
||
|
key_arr,
|
||
|
get_char_codes(window.atob(data.iv))));
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
if (!this.options.pad)
|
||
|
{
|
||
|
slowAES.unpadBytesOut = slowAES._unpadBytesOut_save;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (this.options.check)
|
||
|
{
|
||
|
jdata = ddata.substr(SHA256_SIZE);
|
||
|
|
||
|
if (rstr_sha256(jdata) !== ddata.substr(0, SHA256_SIZE))
|
||
|
{
|
||
|
f.call(this, 'digest mismatch');
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
jdata = ddata;
|
||
|
}
|
||
|
|
||
|
jdata = this.parse(jdata);
|
||
|
}
|
||
|
catch (ex)
|
||
|
{
|
||
|
f.call(this, ex);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
f.call(this, null, jdata);
|
||
|
};
|
||
|
|
||
|
SlowCrypt.prototype.sign = function (data, f)
|
||
|
{
|
||
|
"use strict";
|
||
|
|
||
|
var jdata, signature;
|
||
|
|
||
|
try
|
||
|
{
|
||
|
if (this.key.isPublic)
|
||
|
{
|
||
|
f.call(this, "can't sign using public key");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
jdata = this.stringify(data);
|
||
|
|
||
|
if (typeof jdata !== 'string')
|
||
|
{
|
||
|
jdata = String.fromCharCode.apply(String, jdata);
|
||
|
}
|
||
|
|
||
|
if (this.key.isPrivate)
|
||
|
{
|
||
|
signature = hex2b64(this.key.signPSS(jdata, 'sha256'));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
signature = window.btoa(rstr_hmac_sha256(String.fromCharCode.apply(
|
||
|
String, this.key.key || this.key), jdata));
|
||
|
}
|
||
|
|
||
|
jdata = window.btoa(jdata);
|
||
|
}
|
||
|
catch (ex)
|
||
|
{
|
||
|
f.call(this, ex);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
f.call(this, null, { data: jdata, signature: signature, version: SlowCrypt.get_version() });
|
||
|
};
|
||
|
|
||
|
SlowCrypt.prototype.verify = function (data, f)
|
||
|
{
|
||
|
"use strict";
|
||
|
|
||
|
var match, ddata, jdata;
|
||
|
|
||
|
try
|
||
|
{
|
||
|
if (!this.check_version(data, f))
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
ddata = window.atob(data.data);
|
||
|
|
||
|
if (this.key.isPrivate)
|
||
|
{
|
||
|
f.call(this, "can't verify using private key");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (this.key.isPublic)
|
||
|
{
|
||
|
match = this.key.verifyPSS(ddata, b64tohex(data.signature), 'sha256');
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
match = window.btoa(rstr_hmac_sha256(String.fromCharCode.apply(
|
||
|
String, this.key.key || this.key), ddata)) ===
|
||
|
data.signature;
|
||
|
}
|
||
|
|
||
|
if (match)
|
||
|
{
|
||
|
jdata = this.parse(ddata);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
f.call(this, 'digest mismatch');
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
catch (ex)
|
||
|
{
|
||
|
f.call(this, ex);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
f.call(this, null, jdata);
|
||
|
};
|
||
|
|
||
|
if (typeof exports === 'object')
|
||
|
{
|
||
|
exports.Crypt = Crypt;
|
||
|
exports.SlowCrypt = SlowCrypt;
|
||
|
}
|