Merge pull request #661 from openpgpjs/random_workers

Eliminate running out of random bytes in workers, allow N workers
This commit is contained in:
Bart Butler 2018-03-06 10:13:18 -08:00 committed by GitHub
commit 3c420312a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 700 additions and 692 deletions

View File

@ -107,9 +107,12 @@ module.exports = function(grunt) {
transform: [ transform: [
["babelify", { ["babelify", {
global: true, global: true,
// Only babelify chai-as-promised in node_modules
only: /^(?:.*\/node_modules\/chai-as-promised\/|(?!.*\/node_modules\/)).*$/,
plugins: ["transform-async-to-generator", plugins: ["transform-async-to-generator",
"syntax-async-functions", "syntax-async-functions",
"transform-regenerator", "transform-regenerator",
"transform-runtime",
"transform-remove-strict-mode"], "transform-remove-strict-mode"],
ignore: ['*.min.js'], ignore: ['*.min.js'],
presets: ["env"] presets: ["env"]

View File

@ -50,13 +50,15 @@ hash_headers[11] = [0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
* @param {Integer} length Length of the padding in bytes * @param {Integer} length Length of the padding in bytes
* @return {String} Padding as string * @return {String} Padding as string
*/ */
function getPkcs1Padding(length) { async function getPkcs1Padding(length) {
let result = ''; let result = '';
let randomByte;
while (result.length < length) { while (result.length < length) {
randomByte = random.getSecureRandomOctet(); // eslint-disable-next-line no-await-in-loop
if (randomByte !== 0) { const randomBytes = await random.getRandomBytes(length - result.length);
result += String.fromCharCode(randomByte); for (let i = 0; i < randomBytes.length; i++) {
if (randomBytes[i] !== 0) {
result += String.fromCharCode(randomBytes[i]);
}
} }
} }
return result; return result;
@ -69,9 +71,9 @@ export default {
* create a EME-PKCS1-v1_5 padding (See {@link https://tools.ietf.org/html/rfc4880#section-13.1.1|RFC 4880 13.1.1}) * create a EME-PKCS1-v1_5 padding (See {@link https://tools.ietf.org/html/rfc4880#section-13.1.1|RFC 4880 13.1.1})
* @param {String} M message to be encoded * @param {String} M message to be encoded
* @param {Integer} k the length in octets of the key modulus * @param {Integer} k the length in octets of the key modulus
* @return {String} EME-PKCS1 padded message * @return {Promise<String>} EME-PKCS1 padded message
*/ */
encode: function(M, k) { encode: async function(M, k) {
const mLen = M.length; const mLen = M.length;
// length checking // length checking
if (mLen > k - 11) { if (mLen > k - 11) {
@ -79,15 +81,14 @@ export default {
} }
// Generate an octet string PS of length k - mLen - 3 consisting of // Generate an octet string PS of length k - mLen - 3 consisting of
// pseudo-randomly generated nonzero octets // pseudo-randomly generated nonzero octets
const PS = getPkcs1Padding(k - mLen - 3); const PS = await getPkcs1Padding(k - mLen - 3);
// Concatenate PS, the message M, and other padding to form an // Concatenate PS, the message M, and other padding to form an
// encoded message EM of length k octets as EM = 0x00 || 0x02 || PS || 0x00 || M. // encoded message EM of length k octets as EM = 0x00 || 0x02 || PS || 0x00 || M.
const EM = String.fromCharCode(0) + return String.fromCharCode(0) +
String.fromCharCode(2) + String.fromCharCode(2) +
PS + PS +
String.fromCharCode(0) + String.fromCharCode(0) +
M; M;
return EM;
}, },
/** /**
* decodes a EME-PKCS1-v1_5 padding (See {@link https://tools.ietf.org/html/rfc4880#section-13.1.2|RFC 4880 13.1.2}) * decodes a EME-PKCS1-v1_5 padding (See {@link https://tools.ietf.org/html/rfc4880#section-13.1.2|RFC 4880 13.1.2})

View File

@ -48,7 +48,7 @@ export default {
* g, p, q, x are all BN * g, p, q, x are all BN
* returns { r: BN, s: BN } * returns { r: BN, s: BN }
*/ */
sign: function(hash_algo, m, g, p, q, x) { sign: async function(hash_algo, m, g, p, q, x) {
let k; let k;
let r; let r;
let s; let s;
@ -73,7 +73,8 @@ export default {
// or s = 0 if signatures are generated properly. // or s = 0 if signatures are generated properly.
while (true) { while (true) {
// See Appendix B here: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf // See Appendix B here: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
k = random.getRandomBN(one, q); // returns in [1, q-1] // eslint-disable-next-line no-await-in-loop
k = await random.getRandomBN(one, q); // returns in [1, q-1]
r = gred.redPow(k).fromRed().toRed(redq); // (g**k mod p) mod q r = gred.redPow(k).fromRed().toRed(redq); // (g**k mod p) mod q
if (zero.cmp(r) === 0) { if (zero.cmp(r) === 0) {
continue; continue;
@ -96,7 +97,7 @@ export default {
* p, q, g, y are all BN * p, q, g, y are all BN
* returns BN * returns BN
*/ */
verify: function(hash_algo, r, s, m, p, q, g, y) { verify: async function(hash_algo, r, s, m, p, q, g, y) {
if (zero.ucmp(r) >= 0 || r.ucmp(q) >= 0 || if (zero.ucmp(r) >= 0 || r.ucmp(q) >= 0 ||
zero.ucmp(s) >= 0 || s.ucmp(q) >= 0) { zero.ucmp(s) >= 0 || s.ucmp(q) >= 0) {
util.print_debug("invalid DSA Signature"); util.print_debug("invalid DSA Signature");

View File

@ -33,13 +33,13 @@ export default {
* m, p, g, y are all BN * m, p, g, y are all BN
* returns { c1: BN, c2: BN } * returns { c1: BN, c2: BN }
*/ */
encrypt: function(m, p, g, y) { encrypt: async function(m, p, g, y) {
const redp = new BN.red(p); const redp = new BN.red(p);
const mred = m.toRed(redp); const mred = m.toRed(redp);
const gred = g.toRed(redp); const gred = g.toRed(redp);
const yred = y.toRed(redp); const yred = y.toRed(redp);
// See Section 11.5 here: https://crypto.stanford.edu/~dabo/cryptobook/BonehShoup_0_4.pdf // See Section 11.5 here: https://crypto.stanford.edu/~dabo/cryptobook/BonehShoup_0_4.pdf
const k = random.getRandomBN(zero, p); // returns in [0, p-1] const k = await random.getRandomBN(zero, p); // returns in [0, p-1]
return { return {
c1: gred.redPow(k).fromRed(), c1: gred.redPow(k).fromRed(),
c2: yred.redPow(k).redMul(mred).fromRed() c2: yred.redPow(k).redMul(mred).fromRed()
@ -50,7 +50,7 @@ export default {
* c1, c2, p, x are all BN * c1, c2, p, x are all BN
* returns BN * returns BN
*/ */
decrypt: function(c1, c2, p, x) { decrypt: async function(c1, c2, p, x) {
const redp = new BN.red(p); const redp = new BN.red(p);
const c1red = c1.toRed(redp); const c1red = c1.toRed(redp);
const c2red = c2.toRed(redp); const c2red = c2.toRed(redp);

View File

@ -178,7 +178,7 @@ Curve.prototype.genKeyPair = async function () {
if (!keyPair || !keyPair.priv) { if (!keyPair || !keyPair.priv) {
// elliptic fallback // elliptic fallback
const r = await this.curve.genKeyPair({ const r = await this.curve.genKeyPair({
entropy: util.Uint8Array_to_str(random.getRandomBytes(32)) entropy: util.Uint8Array_to_str(await random.getRandomBytes(32))
}); });
const compact = this.curve.curve.type === 'edwards' || this.curve.curve.type === 'mont'; const compact = this.curve.curve.type === 'edwards' || this.curve.curve.type === 'mont';
if (this.keyType === enums.publicKey.eddsa) { if (this.keyType === enums.publicKey.eddsa) {

View File

@ -37,15 +37,16 @@ export default {
* @param {Integer} k Optional number of iterations of Miller-Rabin test * @param {Integer} k Optional number of iterations of Miller-Rabin test
* @return BN * @return BN
*/ */
function randomProbablePrime(bits, e, k) { async function randomProbablePrime(bits, e, k) {
const min = new BN(1).shln(bits - 1); const min = new BN(1).shln(bits - 1);
let n = random.getRandomBN(min, min.shln(1)); let n = await random.getRandomBN(min, min.shln(1));
if (n.isEven()) { if (n.isEven()) {
n.iaddn(1); // force odd n.iaddn(1); // force odd
} }
while (!isProbablePrime(n, e, k)) { // eslint-disable-next-line no-await-in-loop
while (!await isProbablePrime(n, e, k)) {
n.iaddn(2); n.iaddn(2);
// If reached the maximum, go back to the minimum. // If reached the maximum, go back to the minimum.
if (n.bitLength() > bits) { if (n.bitLength() > bits) {
@ -62,17 +63,17 @@ function randomProbablePrime(bits, e, k) {
* @param {Integer} k Optional number of iterations of Miller-Rabin test * @param {Integer} k Optional number of iterations of Miller-Rabin test
* @return {boolean} * @return {boolean}
*/ */
function isProbablePrime(n, e, k) { async function isProbablePrime(n, e, k) {
if (e && !n.subn(1).gcd(e).eqn(1)) { if (e && !n.subn(1).gcd(e).eqn(1)) {
return false; return false;
} }
if (!fermat(n)) { if (!fermat(n)) {
return false; return false;
} }
if (!millerRabin(n, k, () => new BN(lowprimes[Math.random() * lowprimes.length | 0]))) { if (!await millerRabin(n, k, () => new BN(lowprimes[Math.random() * lowprimes.length | 0]))) {
return false; return false;
} }
if (!millerRabin(n, k)) { if (!await millerRabin(n, k)) {
return false; return false;
} }
// TODO implement the Lucas test // TODO implement the Lucas test
@ -138,7 +139,7 @@ const lowprimes = [
* @param {Function} rand Optional function to generate potential witnesses * @param {Function} rand Optional function to generate potential witnesses
* @return {boolean} * @return {boolean}
*/ */
function millerRabin(n, k, rand) { async function millerRabin(n, k, rand) {
const len = n.bitLength(); const len = n.bitLength();
const red = BN.mont(n); const red = BN.mont(n);
const rone = new BN(1).toRed(red); const rone = new BN(1).toRed(red);
@ -155,7 +156,8 @@ function millerRabin(n, k, rand) {
const d = n.shrn(s); const d = n.shrn(s);
for (; k > 0; k--) { for (; k > 0; k--) {
let a = rand ? rand() : random.getRandomBN(new BN(2), n1); // eslint-disable-next-line no-await-in-loop
let a = rand ? rand() : await random.getRandomBN(new BN(2), n1);
let x = a.toRed(red).redPow(d); let x = a.toRed(red).redPow(d);
if (x.eq(rone) || x.eq(rn1)) if (x.eq(rone) || x.eq(rn1))

View File

@ -55,7 +55,7 @@ export default {
* @param d private MPI part as BN * @param d private MPI part as BN
* @return BN * @return BN
*/ */
sign: function(m, n, e, d) { sign: async function(m, n, e, d) {
if (n.cmp(m) <= 0) { if (n.cmp(m) <= 0) {
throw new Error('Data too large.'); throw new Error('Data too large.');
} }
@ -70,7 +70,7 @@ export default {
* @param e public MPI part as BN * @param e public MPI part as BN
* @return BN * @return BN
*/ */
verify: function(s, n, e) { verify: async function(s, n, e) {
if (n.cmp(s) <= 0) { if (n.cmp(s) <= 0) {
throw new Error('Data too large.'); throw new Error('Data too large.');
} }
@ -85,7 +85,7 @@ export default {
* @param e public MPI part as BN * @param e public MPI part as BN
* @return BN * @return BN
*/ */
encrypt: function(m, n, e) { encrypt: async function(m, n, e) {
if (n.cmp(m) <= 0) { if (n.cmp(m) <= 0) {
throw new Error('Data too large.'); throw new Error('Data too large.');
} }
@ -104,7 +104,7 @@ export default {
* @param u RSA u as BN * @param u RSA u as BN
* @return {BN} The decrypted value of the message * @return {BN} The decrypted value of the message
*/ */
decrypt: function(m, n, e, d, p, q, u) { decrypt: async function(m, n, e, d, p, q, u) {
if (n.cmp(m) <= 0) { if (n.cmp(m) <= 0) {
throw new Error('Data too large.'); throw new Error('Data too large.');
} }
@ -117,7 +117,7 @@ export default {
let blinder; let blinder;
let unblinder; let unblinder;
if (config.rsa_blinding) { if (config.rsa_blinding) {
unblinder = random.getRandomBN(new BN(2), n).toRed(nred); unblinder = (await random.getRandomBN(new BN(2), n)).toRed(nred);
blinder = unblinder.redInvm().redPow(e); blinder = unblinder.redInvm().redPow(e);
m = m.toRed(nred).redMul(blinder).fromRed(); m = m.toRed(nred).redMul(blinder).fromRed();
} }
@ -204,8 +204,8 @@ export default {
// RSA keygen fallback using 40 iterations of the Miller-Rabin test // RSA keygen fallback using 40 iterations of the Miller-Rabin test
// See https://stackoverflow.com/a/6330138 for justification // See https://stackoverflow.com/a/6330138 for justification
// Also see section C.3 here: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST // Also see section C.3 here: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST
let p = prime.randomProbablePrime(B - (B >> 1), E, 40); let p = await prime.randomProbablePrime(B - (B >> 1), E, 40);
let q = prime.randomProbablePrime(B >> 1, E, 40); let q = await prime.randomProbablePrime(B >> 1, E, 40);
if (p.cmp(q) < 0) { if (p.cmp(q) < 0) {
[p, q] = [q, p]; [p, q] = [q, p];

View File

@ -37,50 +37,8 @@ export default {
* @param {Integer} length Length in bytes to generate * @param {Integer} length Length in bytes to generate
* @return {Uint8Array} Random byte array * @return {Uint8Array} Random byte array
*/ */
getRandomBytes: function(length) { getRandomBytes: async function(length) {
const result = new Uint8Array(length); const buf = new Uint8Array(length);
for (let i = 0; i < length; i++) {
result[i] = this.getSecureRandomOctet();
}
return result;
},
/**
* Return a secure random number in the specified range
* @param {Integer} from Min of the random number
* @param {Integer} to Max of the random number (max 32bit)
* @return {Integer} A secure random number
*/
getSecureRandom: function(from, to) {
let randUint = this.getSecureRandomUint();
const bits = ((to - from)).toString(2).length;
while ((randUint & ((2 ** bits) - 1)) > (to - from)) {
randUint = this.getSecureRandomUint();
}
return from + (Math.abs(randUint & ((2 ** bits) - 1)));
},
getSecureRandomOctet: function() {
const buf = new Uint8Array(1);
this.getRandomValues(buf);
return buf[0];
},
getSecureRandomUint: function() {
const buf = new Uint8Array(4);
const dv = new DataView(buf.buffer);
this.getRandomValues(buf);
return dv.getUint32(0);
},
/**
* Helper routine which calls platform specific crypto random generator
* @param {Uint8Array} buf
*/
getRandomValues: function(buf) {
if (!(buf instanceof Uint8Array)) {
throw new Error('Invalid type: buf not an Uint8Array');
}
if (typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues) { if (typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues) {
window.crypto.getRandomValues(buf); window.crypto.getRandomValues(buf);
} else if (typeof window !== 'undefined' && typeof window.msCrypto === 'object' && typeof window.msCrypto.getRandomValues === 'function') { } else if (typeof window !== 'undefined' && typeof window.msCrypto === 'object' && typeof window.msCrypto.getRandomValues === 'function') {
@ -89,7 +47,7 @@ export default {
const bytes = nodeCrypto.randomBytes(buf.length); const bytes = nodeCrypto.randomBytes(buf.length);
buf.set(bytes); buf.set(bytes);
} else if (this.randomBuffer.buffer) { } else if (this.randomBuffer.buffer) {
this.randomBuffer.get(buf); await this.randomBuffer.get(buf);
} else { } else {
throw new Error('No secure random number generator available.'); throw new Error('No secure random number generator available.');
} }
@ -102,7 +60,7 @@ export default {
* @param {module:type/mpi} max Upper bound, excluded * @param {module:type/mpi} max Upper bound, excluded
* @return {module:BN} Random MPI * @return {module:BN} Random MPI
*/ */
getRandomBN: function(min, max) { getRandomBN: async function(min, max) {
if (max.cmp(min) <= 0) { if (max.cmp(min) <= 0) {
throw new Error('Illegal parameter value: max <= min'); throw new Error('Illegal parameter value: max <= min');
} }
@ -113,7 +71,7 @@ export default {
// Using a while loop is necessary to avoid bias introduced by the mod operation. // Using a while loop is necessary to avoid bias introduced by the mod operation.
// However, we request 64 extra random bits so that the bias is negligible. // However, we request 64 extra random bits so that the bias is negligible.
// Section B.1.1 here: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf // Section B.1.1 here: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
const r = new BN(this.getRandomBytes(bytes + 8)); const r = new BN(await this.getRandomBytes(bytes + 8));
return r.mod(modulus).add(min); return r.mod(modulus).add(min);
}, },
@ -126,15 +84,17 @@ export default {
function RandomBuffer() { function RandomBuffer() {
this.buffer = null; this.buffer = null;
this.size = null; this.size = null;
this.callback = null;
} }
/** /**
* Initialize buffer * Initialize buffer
* @param {Integer} size size of buffer * @param {Integer} size size of buffer
*/ */
RandomBuffer.prototype.init = function(size) { RandomBuffer.prototype.init = function(size, callback) {
this.buffer = new Uint8Array(size); this.buffer = new Uint8Array(size);
this.size = 0; this.size = 0;
this.callback = callback;
}; };
/** /**
@ -161,7 +121,7 @@ RandomBuffer.prototype.set = function(buf) {
* Take numbers out of buffer and copy to array * Take numbers out of buffer and copy to array
* @param {Uint8Array} buf the destination array * @param {Uint8Array} buf the destination array
*/ */
RandomBuffer.prototype.get = function(buf) { RandomBuffer.prototype.get = async function(buf) {
if (!this.buffer) { if (!this.buffer) {
throw new Error('RandomBuffer is not initialized'); throw new Error('RandomBuffer is not initialized');
} }
@ -169,7 +129,12 @@ RandomBuffer.prototype.get = function(buf) {
throw new Error('Invalid type: buf not an Uint8Array'); throw new Error('Invalid type: buf not an Uint8Array');
} }
if (this.size < buf.length) { if (this.size < buf.length) {
throw new Error('Random number buffer depleted'); if (!this.callback) {
throw new Error('Random number buffer depleted');
}
// Wait for random bytes from main context, then try again
await this.callback();
return this.get(buf);
} }
for (let i = 0; i < buf.length; i++) { for (let i = 0; i < buf.length; i++) {
buf[i] = this.buffer[--this.size]; buf[i] = this.buffer[--this.size];

View File

@ -34,7 +34,7 @@ export default {
const m = msg_MPIs[0].toBN(); const m = msg_MPIs[0].toBN();
const n = pub_MPIs[0].toBN(); const n = pub_MPIs[0].toBN();
const e = pub_MPIs[1].toBN(); const e = pub_MPIs[1].toBN();
const EM = publicKey.rsa.verify(m, n, e); const EM = await publicKey.rsa.verify(m, n, e);
const EM2 = pkcs1.emsa.encode(hash_algo, util.Uint8Array_to_str(data), n.byteLength()); const EM2 = pkcs1.emsa.encode(hash_algo, util.Uint8Array_to_str(data), n.byteLength());
return util.Uint8Array_to_hex(EM) === EM2; return util.Uint8Array_to_hex(EM) === EM2;
} }
@ -88,7 +88,7 @@ export default {
const d = key_params[2].toBN(); const d = key_params[2].toBN();
data = util.Uint8Array_to_str(data); data = util.Uint8Array_to_str(data);
const m = new BN(pkcs1.emsa.encode(hash_algo, data, n.byteLength()), 16); const m = new BN(pkcs1.emsa.encode(hash_algo, data, n.byteLength()), 16);
const signature = publicKey.rsa.sign(m, n, e, d); const signature = await publicKey.rsa.sign(m, n, e, d);
return util.Uint8Array_to_MPI(signature); return util.Uint8Array_to_MPI(signature);
} }
case enums.publicKey.dsa: { case enums.publicKey.dsa: {
@ -96,7 +96,7 @@ export default {
const q = key_params[1].toBN(); const q = key_params[1].toBN();
const g = key_params[2].toBN(); const g = key_params[2].toBN();
const x = key_params[4].toBN(); const x = key_params[4].toBN();
const signature = publicKey.dsa.sign(hash_algo, data, g, p, q, x); const signature = await publicKey.dsa.sign(hash_algo, data, g, p, q, x);
return util.concatUint8Array([ return util.concatUint8Array([
util.Uint8Array_to_MPI(signature.r), util.Uint8Array_to_MPI(signature.r),
util.Uint8Array_to_MPI(signature.s) util.Uint8Array_to_MPI(signature.s)

View File

@ -153,7 +153,7 @@ Key.prototype.packetlist2structure = function(packetlist) {
/** /**
* Transforms structured key data to packetlist * Transforms structured key data to packetlist
* @return {module:packet/packetlist} The packets that form a key * @returns {module:packet/packetlist} The packets that form a key
*/ */
Key.prototype.toPacketlist = function() { Key.prototype.toPacketlist = function() {
const packetlist = new packet.List(); const packetlist = new packet.List();
@ -210,7 +210,7 @@ Key.prototype.getKeyIds = function() {
/** /**
* Returns array containing first key packet for given key ID or all key packets in the case of a wildcard ID * Returns array containing first key packet for given key ID or all key packets in the case of a wildcard ID
* @param {type/keyid} keyId * @param {type/keyid} keyId
* @return {(module:packet/public_subkey|module:packet/public_key| * @returns {(module:packet/public_subkey|module:packet/public_key|
* module:packet/secret_subkey|module:packet/secret_key|null)} * module:packet/secret_subkey|module:packet/secret_key|null)}
*/ */
Key.prototype.getKeyPackets = function(packetKeyId) { Key.prototype.getKeyPackets = function(packetKeyId) {
@ -229,7 +229,7 @@ Key.prototype.getKeyPackets = function(packetKeyId) {
/** /**
* Returns userids * Returns userids
* @return {Array<string>} array of userids * @returns {Array<string>} array of userids
*/ */
Key.prototype.getUserIds = function() { Key.prototype.getUserIds = function() {
const userids = []; const userids = [];
@ -243,7 +243,7 @@ Key.prototype.getUserIds = function() {
/** /**
* Returns true if this is a public key * Returns true if this is a public key
* @return {Boolean} * @returns {Boolean}
*/ */
Key.prototype.isPublic = function() { Key.prototype.isPublic = function() {
return this.primaryKey.tag === enums.packet.publicKey; return this.primaryKey.tag === enums.packet.publicKey;
@ -251,7 +251,7 @@ Key.prototype.isPublic = function() {
/** /**
* Returns true if this is a private key * Returns true if this is a private key
* @return {Boolean} * @returns {Boolean}
*/ */
Key.prototype.isPrivate = function() { Key.prototype.isPrivate = function() {
return this.primaryKey.tag === enums.packet.secretKey; return this.primaryKey.tag === enums.packet.secretKey;
@ -259,7 +259,7 @@ Key.prototype.isPrivate = function() {
/** /**
* Returns key as public key (shallow copy) * Returns key as public key (shallow copy)
* @return {module:key~Key} new public Key * @returns {module:key~Key} new public Key
*/ */
Key.prototype.toPublic = function() { Key.prototype.toPublic = function() {
const packetlist = new packet.List(); const packetlist = new packet.List();
@ -290,7 +290,7 @@ Key.prototype.toPublic = function() {
/** /**
* Returns ASCII armored text of key * Returns ASCII armored text of key
* @return {String} ASCII armor * @returns {String} ASCII armor
*/ */
Key.prototype.armor = function() { Key.prototype.armor = function() {
const type = this.isPublic() ? enums.armor.public_key : enums.armor.private_key; const type = this.isPublic() ? enums.armor.public_key : enums.armor.private_key;
@ -301,7 +301,7 @@ Key.prototype.armor = function() {
* Returns first key packet or key packet by given keyId that is available for signing or signature verification * Returns first key packet or key packet by given keyId that is available for signing or signature verification
* @param {module:type/keyid} keyId, optional * @param {module:type/keyid} keyId, optional
* @param {Date} date use the given date for verification instead of the current time * @param {Date} date use the given date for verification instead of the current time
* @return {(module:packet/secret_subkey|module:packet/secret_key|null)} key packet or null if no signing key has been found * @returns {(module:packet/secret_subkey|module:packet/secret_key|null)} key packet or null if no signing key has been found
*/ */
Key.prototype.getSigningKeyPacket = function (keyId=null, date=new Date()) { Key.prototype.getSigningKeyPacket = function (keyId=null, date=new Date()) {
const primaryUser = this.getPrimaryUser(date); const primaryUser = this.getPrimaryUser(date);
@ -379,36 +379,34 @@ Key.prototype.getEncryptionKeyPacket = function(keyId, date=new Date()) {
/** /**
* Encrypts all secret key and subkey packets * Encrypts all secret key and subkey packets
* @param {String} passphrase * @param {String} passphrase
* @returns {Promise<Boolean>}
*/ */
Key.prototype.encrypt = function(passphrase) { Key.prototype.encrypt = async function(passphrase) {
if (!this.isPrivate()) { if (!this.isPrivate()) {
throw new Error("Nothing to encrypt in a public key"); throw new Error("Nothing to encrypt in a public key");
} }
const keys = this.getAllKeyPackets(); const keys = this.getAllKeyPackets();
for (let i = 0; i < keys.length; i++) { await Promise.all(keys.map(async function(packet) {
keys[i].encrypt(passphrase); await packet.encrypt(passphrase);
keys[i].clearPrivateParams(); await packet.clearPrivateParams();
} return packet;
}));
return true;
}; };
/** /**
* Decrypts all secret key and subkey packets * Decrypts all secret key and subkey packets
* @param {String} passphrase * @param {String} passphrase
* @return {Boolean} true if all key and subkey packets decrypted successfully * @returns {Promise<Boolean>} true if all key and subkey packets decrypted successfully
*/ */
Key.prototype.decrypt = function(passphrase) { Key.prototype.decrypt = async function(passphrase) {
if (this.isPrivate()) { if (!this.isPrivate()) {
const keys = this.getAllKeyPackets();
for (let i = 0; i < keys.length; i++) {
const success = keys[i].decrypt(passphrase);
if (!success) {
return false;
}
}
} else {
throw new Error("Nothing to decrypt in a public key"); throw new Error("Nothing to decrypt in a public key");
} }
const keys = this.getAllKeyPackets();
await Promise.all(keys.map(packet => packet.decrypt(passphrase)));
return true; return true;
}; };
@ -416,7 +414,7 @@ Key.prototype.decrypt = function(passphrase) {
* Decrypts specific key packets by key ID * Decrypts specific key packets by key ID
* @param {Array<module:type/keyid>} keyIds * @param {Array<module:type/keyid>} keyIds
* @param {String} passphrase * @param {String} passphrase
* @return {Boolean} true if all key packets decrypted successfully * @returns {Boolean} true if all key packets decrypted successfully
*/ */
Key.prototype.decryptKeyPacket = function(keyIds, passphrase) { Key.prototype.decryptKeyPacket = function(keyIds, passphrase) {
if (this.isPrivate()) { if (this.isPrivate()) {
@ -442,7 +440,7 @@ Key.prototype.decryptKeyPacket = function(keyIds, passphrase) {
* Verify primary key. Checks for revocation signatures, expiration time * Verify primary key. Checks for revocation signatures, expiration time
* and valid self signature * and valid self signature
* @param {Date} date (optional) use the given date for verification instead of the current time * @param {Date} date (optional) use the given date for verification instead of the current time
* @return {module:enums.keyStatus} The status of the primary key * @returns {Promise{module:enums.keyStatus}} The status of the primary key
*/ */
Key.prototype.verifyPrimaryKey = async function(date=new Date()) { Key.prototype.verifyPrimaryKey = async function(date=new Date()) {
// TODO clarify OpenPGP's behavior given an expired revocation signature // TODO clarify OpenPGP's behavior given an expired revocation signature
@ -484,7 +482,7 @@ Key.prototype.verifyPrimaryKey = async function(date=new Date()) {
/** /**
* Returns the expiration time of the primary key or null if key does not expire * Returns the expiration time of the primary key or null if key does not expire
* @return {Date|null} * @returns {Date|null}
*/ */
Key.prototype.getExpirationTime = function() { Key.prototype.getExpirationTime = function() {
if (this.primaryKey.version === 3) { if (this.primaryKey.version === 3) {
@ -519,7 +517,7 @@ function getExpirationTime(keyPacket, selfCertificate, defaultValue=null) {
* *
* NOTE: call verifyPrimaryUser before calling this function. * NOTE: call verifyPrimaryUser before calling this function.
* @param {Date} date use the given date for verification instead of the current time * @param {Date} date use the given date for verification instead of the current time
* @return {{user: Array<module:packet/User>, selfCertificate: Array<module:packet/signature>}|null} The primary user and the self signature * @returns {{user: Array<module:packet/User>, selfCertificate: Array<module:packet/signature>}|null} The primary user and the self signature
*/ */
Key.prototype.getPrimaryUser = function(date=new Date()) { Key.prototype.getPrimaryUser = function(date=new Date()) {
let primaryUsers = []; let primaryUsers = [];
@ -652,7 +650,7 @@ Key.prototype.revoke = function() {
/** /**
* Signs primary user of key * Signs primary user of key
* @param {Array<module:key~Key>} privateKey decrypted private keys for signing * @param {Array<module:key~Key>} privateKey decrypted private keys for signing
* @return {module:key~Key} new public key with new certificate signature * @returns {Promise<module:key~Key>} new public key with new certificate signature
*/ */
Key.prototype.signPrimaryUser = async function(privateKeys) { Key.prototype.signPrimaryUser = async function(privateKeys) {
await this.verifyPrimaryUser(); await this.verifyPrimaryUser();
@ -669,7 +667,7 @@ Key.prototype.signPrimaryUser = async function(privateKeys) {
/** /**
* Signs all users of key * Signs all users of key
* @param {Array<module:key~Key>} privateKeys decrypted private keys for signing * @param {Array<module:key~Key>} privateKeys decrypted private keys for signing
* @return {module:key~Key} new public key with new certificate signature * @returns {Promise<module:key~Key>} new public key with new certificate signature
*/ */
Key.prototype.signAllUsers = async function(privateKeys) { Key.prototype.signAllUsers = async function(privateKeys) {
const that = this; const that = this;
@ -685,7 +683,7 @@ Key.prototype.signAllUsers = async function(privateKeys) {
* - if no arguments are given, verifies the self certificates; * - if no arguments are given, verifies the self certificates;
* - otherwise, verifies all certificates signed with given keys. * - otherwise, verifies all certificates signed with given keys.
* @param {Array<module:key~Key>} keys array of keys to verify certificate signatures * @param {Array<module:key~Key>} keys array of keys to verify certificate signatures
* @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature * @returns {Promise<Array<({keyid: module:type/keyid, valid: Boolean})>>} list of signer's keyid and validity of signature
*/ */
Key.prototype.verifyPrimaryUser = async function(keys) { Key.prototype.verifyPrimaryUser = async function(keys) {
const { primaryKey } = this; const { primaryKey } = this;
@ -735,7 +733,7 @@ Key.prototype.verifyPrimaryUser = async function(keys) {
* - if no arguments are given, verifies the self certificates; * - if no arguments are given, verifies the self certificates;
* - otherwise, verifies all certificates signed with given keys. * - otherwise, verifies all certificates signed with given keys.
* @param {Array<module:key~Key>} keys array of keys to verify certificate signatures * @param {Array<module:key~Key>} keys array of keys to verify certificate signatures
* @return {Array<({userid: String, keyid: module:type/keyid, valid: Boolean})>} list of userid, signer's keyid and validity of signature * @returns {Promise<Array<({userid: String, keyid: module:type/keyid, valid: Boolean})>>} list of userid, signer's keyid and validity of signature
*/ */
Key.prototype.verifyAllUsers = async function(keys) { Key.prototype.verifyAllUsers = async function(keys) {
const results = []; const results = [];
@ -771,7 +769,7 @@ function User(userPacket) {
/** /**
* Transforms structured user data to packetlist * Transforms structured user data to packetlist
* @return {module:packet/packetlist} * @returns {module:packet/packetlist}
*/ */
User.prototype.toPacketlist = function() { User.prototype.toPacketlist = function() {
const packetlist = new packet.List(); const packetlist = new packet.List();
@ -788,7 +786,7 @@ User.prototype.toPacketlist = function() {
* @param {module:packet/signature} certificate The certificate to verify * @param {module:packet/signature} certificate The certificate to verify
* @param {module:packet/public_subkey|module:packet/public_key| * @param {module:packet/public_subkey|module:packet/public_key|
* module:packet/secret_subkey|module:packet/secret_key} key, optional The key to verify the signature * module:packet/secret_subkey|module:packet/secret_key} key, optional The key to verify the signature
* @return {Boolean} True if the certificate is revoked * @returns {Promise<Boolean>} True if the certificate is revoked
*/ */
User.prototype.isRevoked = async function(primaryKey, certificate, key) { User.prototype.isRevoked = async function(primaryKey, certificate, key) {
certificate.revoked = null; certificate.revoked = null;
@ -810,7 +808,7 @@ User.prototype.isRevoked = async function(primaryKey, certificate, key) {
* Signs user * Signs user
* @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet
* @param {Array<module:key~Key>} privateKeys decrypted private keys for signing * @param {Array<module:key~Key>} privateKeys decrypted private keys for signing
* @return {module:key~Key} new user with new certificate signatures * @returns {Promise<module:key~Key>} new user with new certificate signatures
*/ */
User.prototype.sign = async function(primaryKey, privateKeys) { User.prototype.sign = async function(primaryKey, privateKeys) {
const dataToSign = { userid: this.userId || this.userAttribute, key: primaryKey }; const dataToSign = { userid: this.userId || this.userAttribute, key: primaryKey };
@ -851,7 +849,7 @@ User.prototype.sign = async function(primaryKey, privateKeys) {
* @param {module:packet/signature} certificate A certificate of this user * @param {module:packet/signature} certificate A certificate of this user
* @param {Array<module:key~Key>} keys array of keys to verify certificate signatures * @param {Array<module:key~Key>} keys array of keys to verify certificate signatures
* @param {Date} date use the given date for verification instead of the current time * @param {Date} date use the given date for verification instead of the current time
* @return {module:enums.keyStatus} status of the certificate * @returns {Promise<module:enums.keyStatus>} status of the certificate
*/ */
User.prototype.verifyCertificate = async function(primaryKey, certificate, keys, date=new Date()) { User.prototype.verifyCertificate = async function(primaryKey, certificate, keys, date=new Date()) {
const that = this; const that = this;
@ -879,7 +877,7 @@ User.prototype.verifyCertificate = async function(primaryKey, certificate, keys,
* Verifies all user certificates * Verifies all user certificates
* @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet
* @param {Array<module:key~Key>} keys array of keys to verify certificate signatures * @param {Array<module:key~Key>} keys array of keys to verify certificate signatures
* @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature * @returns {Promise<Array<({keyid: module:type/keyid, valid: Boolean})>>} list of signer's keyid and validity of signature
*/ */
User.prototype.verifyAllCertifications = async function(primaryKey, keys) { User.prototype.verifyAllCertifications = async function(primaryKey, keys) {
const that = this; const that = this;
@ -897,7 +895,7 @@ User.prototype.verifyAllCertifications = async function(primaryKey, keys) {
* Verify User. Checks for existence of self signatures, revocation signatures * Verify User. Checks for existence of self signatures, revocation signatures
* and validity of self signature * and validity of self signature
* @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet
* @return {module:enums.keyStatus} status of user * @returns {Promise<module:enums.keyStatus>} status of user
*/ */
User.prototype.verify = async function(primaryKey) { User.prototype.verify = async function(primaryKey) {
if (!this.selfCertifications) { if (!this.selfCertifications) {
@ -925,7 +923,7 @@ User.prototype.verify = async function(primaryKey) {
/** /**
* Update user with new components from specified user * Update user with new components from specified user
* @param {module:key~User} user source user to merge * @param {module:key~User} user source user to merge
* @param {module:packet/signature} primaryKey primary key used for validation * @param {Promise<module:packet/signature>} primaryKey primary key used for validation
*/ */
User.prototype.update = async function(user, primaryKey) { User.prototype.update = async function(user, primaryKey) {
const dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; const dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey };
@ -954,7 +952,7 @@ function SubKey(subKeyPacket) {
/** /**
* Transforms structured subkey data to packetlist * Transforms structured subkey data to packetlist
* @return {module:packet/packetlist} * @returns {module:packet/packetlist}
*/ */
SubKey.prototype.toPacketlist = function() { SubKey.prototype.toPacketlist = function() {
const packetlist = new packet.List(); const packetlist = new packet.List();
@ -970,7 +968,7 @@ SubKey.prototype.toPacketlist = function() {
* Returns true if the subkey can be used for encryption * Returns true if the subkey can be used for encryption
* @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet
* @param {Date} date use the given date for verification instead of the current time * @param {Date} date use the given date for verification instead of the current time
* @return {Boolean} * @returns {Promise<Boolean>}
*/ */
SubKey.prototype.isValidEncryptionKey = async function(primaryKey, date=new Date()) { SubKey.prototype.isValidEncryptionKey = async function(primaryKey, date=new Date()) {
if (await this.verify(primaryKey, date) !== enums.keyStatus.valid) { if (await this.verify(primaryKey, date) !== enums.keyStatus.valid) {
@ -988,7 +986,7 @@ SubKey.prototype.isValidEncryptionKey = async function(primaryKey, date=new Date
* Returns true if the subkey can be used for signing of data * Returns true if the subkey can be used for signing of data
* @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet
* @param {Date} date use the given date for verification instead of the current time * @param {Date} date use the given date for verification instead of the current time
* @return {Boolean} * @returns {Promise<Boolean>}
*/ */
SubKey.prototype.isValidSigningKey = async function(primaryKey, date=new Date()) { SubKey.prototype.isValidSigningKey = async function(primaryKey, date=new Date()) {
if (await this.verify(primaryKey, date) !== enums.keyStatus.valid) { if (await this.verify(primaryKey, date) !== enums.keyStatus.valid) {
@ -1007,7 +1005,7 @@ SubKey.prototype.isValidSigningKey = async function(primaryKey, date=new Date())
* and valid binding signature * and valid binding signature
* @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet
* @param {Date} date use the given date for verification instead of the current time * @param {Date} date use the given date for verification instead of the current time
* @return {module:enums.keyStatus} The status of the subkey * @returns {Promise<module:enums.keyStatus>} The status of the subkey
*/ */
SubKey.prototype.verify = async function(primaryKey, date=new Date()) { SubKey.prototype.verify = async function(primaryKey, date=new Date()) {
const that = this; const that = this;
@ -1054,7 +1052,7 @@ SubKey.prototype.verify = async function(primaryKey, date=new Date()) {
/** /**
* Returns the expiration time of the subkey or null if key does not expire * Returns the expiration time of the subkey or null if key does not expire
* @return {Date|null} * @returns {Date|null}
*/ */
SubKey.prototype.getExpirationTime = function() { SubKey.prototype.getExpirationTime = function() {
let highest; let highest;
@ -1073,7 +1071,7 @@ SubKey.prototype.getExpirationTime = function() {
/** /**
* Update subkey with new components from specified subkey * Update subkey with new components from specified subkey
* @param {module:key~SubKey} subKey source subkey to merge * @param {module:key~SubKey} subKey source subkey to merge
* @param {module:packet/signature} primaryKey primary key used for validation * @param {Promise<module:packet/signature>} primaryKey primary key used for validation
*/ */
SubKey.prototype.update = async function(subKey, primaryKey) { SubKey.prototype.update = async function(subKey, primaryKey) {
if (await subKey.verify(primaryKey) === enums.keyStatus.invalid) { if (await subKey.verify(primaryKey) === enums.keyStatus.invalid) {
@ -1115,7 +1113,7 @@ SubKey.prototype.update = async function(subKey, primaryKey) {
/** /**
* Reads an unarmored OpenPGP key list and returns one or multiple key objects * Reads an unarmored OpenPGP key list and returns one or multiple key objects
* @param {Uint8Array} data to be parsed * @param {Uint8Array} data to be parsed
* @return {{keys: Array<module:key~Key>, err: (Array<Error>|null)}} result object with key and error arrays * @returns {{keys: Array<module:key~Key>, err: (Array<Error>|null)}} result object with key and error arrays
* @static * @static
*/ */
export function read(data) { export function read(data) {
@ -1148,7 +1146,7 @@ export function read(data) {
/** /**
* Reads an OpenPGP armored text and returns one or multiple key objects * Reads an OpenPGP armored text and returns one or multiple key objects
* @param {String} armoredText text to be parsed * @param {String} armoredText text to be parsed
* @return {{keys: Array<module:key~Key>, err: (Array<Error>|null)}} result object with key and error arrays * @returns {{keys: Array<module:key~Key>, err: (Array<Error>|null)}} result object with key and error arrays
* @static * @static
*/ */
export function readArmored(armoredText) { export function readArmored(armoredText) {
@ -1176,7 +1174,7 @@ export function readArmored(armoredText) {
* @param {String} options.passphrase The passphrase used to encrypt the resulting private key * @param {String} options.passphrase The passphrase used to encrypt the resulting private key
* @param {Boolean} [options.unlocked=false] The secret part of the generated key is unlocked * @param {Boolean} [options.unlocked=false] The secret part of the generated key is unlocked
* @param {Number} [options.keyExpirationTime=0] The number of seconds after the key creation time that the key expires * @param {Number} [options.keyExpirationTime=0] The number of seconds after the key creation time that the key expires
* @return {module:key~Key} * @returns {module:key~Key}
* @static * @static
*/ */
export function generate(options) { export function generate(options) {
@ -1251,49 +1249,51 @@ export function generate(options) {
* @param {String} options.passphrase The passphrase used to encrypt the resulting private key * @param {String} options.passphrase The passphrase used to encrypt the resulting private key
* @param {Boolean} [options.unlocked=false] The secret part of the generated key is unlocked * @param {Boolean} [options.unlocked=false] The secret part of the generated key is unlocked
* @param {Number} [options.keyExpirationTime=0] The number of seconds after the key creation time that the key expires * @param {Number} [options.keyExpirationTime=0] The number of seconds after the key creation time that the key expires
* @return {module:key~Key} * @returns {Promise<module:key~Key>}
* @static * @static
*/ */
export function reformat(options) { export async function reformat(options) {
let secretKeyPacket; let secretKeyPacket;
let secretSubkeyPacket; let secretSubkeyPacket;
return Promise.resolve().then(() => {
options.keyType = options.keyType || enums.publicKey.rsa_encrypt_sign;
if (options.keyType !== enums.publicKey.rsa_encrypt_sign) { // RSA Encrypt-Only and RSA Sign-Only are deprecated and SHOULD NOT be generated
throw new Error('Only RSA Encrypt or Sign supported');
}
if (!options.privateKey.decrypt()) { options.keyType = options.keyType || enums.publicKey.rsa_encrypt_sign;
throw new Error('Key not decrypted'); if (options.keyType !== enums.publicKey.rsa_encrypt_sign) { // RSA Encrypt-Only and RSA Sign-Only are deprecated and SHOULD NOT be generated
} throw new Error('Only RSA Encrypt or Sign supported');
}
if (!options.passphrase) { // Key without passphrase is unlocked by definition try {
options.unlocked = true; await options.privateKey.decrypt();
}
catch(err) {
throw new Error('Key not decrypted');
}
if (!options.passphrase) { // Key without passphrase is unlocked by definition
options.unlocked = true;
}
if (util.isString(options.userIds)) {
options.userIds = [options.userIds];
}
const packetlist = options.privateKey.toPacketlist();
for (let i = 0; i < packetlist.length; i++) {
if (packetlist[i].tag === enums.packet.secretKey) {
secretKeyPacket = packetlist[i];
options.keyType = secretKeyPacket.algorithm;
} else if (packetlist[i].tag === enums.packet.secretSubkey) {
secretSubkeyPacket = packetlist[i];
options.subkeyType = secretSubkeyPacket.algorithm;
} }
if (util.isString(options.userIds)) { }
options.userIds = [options.userIds]; if (!secretKeyPacket) {
} throw new Error('Key does not contain a secret key packet');
const packetlist = options.privateKey.toPacketlist(); }
for (let i = 0; i < packetlist.length; i++) { return wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options);
if (packetlist[i].tag === enums.packet.secretKey) {
secretKeyPacket = packetlist[i];
options.keyType = secretKeyPacket.algorithm;
} else if (packetlist[i].tag === enums.packet.secretSubkey) {
secretSubkeyPacket = packetlist[i];
options.subkeyType = secretSubkeyPacket.algorithm;
}
}
if (!secretKeyPacket) {
throw new Error('Key does not contain a secret key packet');
}
return wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options);
});
} }
async function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { async function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) {
// set passphrase protection // set passphrase protection
if (options.passphrase) { if (options.passphrase) {
secretKeyPacket.encrypt(options.passphrase); await secretKeyPacket.encrypt(options.passphrase);
if (secretSubkeyPacket) { if (secretSubkeyPacket) {
secretSubkeyPacket.encrypt(options.passphrase); secretSubkeyPacket.encrypt(options.passphrase);
} }
@ -1383,7 +1383,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) {
/** /**
* Returns the preferred signature hash algorithm of a key * Returns the preferred signature hash algorithm of a key
* @param {object} key * @param {object} key
* @return {String} * @returns {String}
*/ */
export function getPreferredHashAlgo(key) { export function getPreferredHashAlgo(key) {
let hash_algo = config.prefer_hash_algorithm; let hash_algo = config.prefer_hash_algorithm;
@ -1417,7 +1417,7 @@ export function getPreferredHashAlgo(key) {
/** /**
* Returns the preferred symmetric algorithm for a set of keys * Returns the preferred symmetric algorithm for a set of keys
* @param {Array<module:key~Key>} keys Set of keys * @param {Array<module:key~Key>} keys Set of keys
* @return {enums.symmetric} Preferred symmetric algorithm * @returns {enums.symmetric} Preferred symmetric algorithm
*/ */
export function getPreferredSymAlgo(keys) { export function getPreferredSymAlgo(keys) {
const prioMap = {}; const prioMap = {};

View File

@ -55,7 +55,7 @@ export function Message(packetlist) {
/** /**
* Returns the key IDs of the keys to which the session key is encrypted * Returns the key IDs of the keys to which the session key is encrypted
* @return {Array<module:type/keyid>} array of keyid objects * @returns {Array<module:type/keyid>} array of keyid objects
*/ */
Message.prototype.getEncryptionKeyIds = function() { Message.prototype.getEncryptionKeyIds = function() {
const keyIds = []; const keyIds = [];
@ -68,7 +68,7 @@ Message.prototype.getEncryptionKeyIds = function() {
/** /**
* Returns the key IDs of the keys that signed the message * Returns the key IDs of the keys that signed the message
* @return {Array<module:type/keyid>} array of keyid objects * @returns {Array<module:type/keyid>} array of keyid objects
*/ */
Message.prototype.getSigningKeyIds = function() { Message.prototype.getSigningKeyIds = function() {
const keyIds = []; const keyIds = [];
@ -93,7 +93,7 @@ Message.prototype.getSigningKeyIds = function() {
* @param {Array<Key>} privateKeys (optional) private keys with decrypted secret data * @param {Array<Key>} privateKeys (optional) private keys with decrypted secret data
* @param {Array<String>} passwords (optional) passwords used to decrypt * @param {Array<String>} passwords (optional) passwords used to decrypt
* @param {Array<Object>} sessionKeys (optional) session keys in the form: { data:Uint8Array, algorithm:String } * @param {Array<Object>} sessionKeys (optional) session keys in the form: { data:Uint8Array, algorithm:String }
* @return {Message} new message with decrypted content * @returns {Promise<Message>} new message with decrypted content
*/ */
Message.prototype.decrypt = async function(privateKeys, passwords, sessionKeys) { Message.prototype.decrypt = async function(privateKeys, passwords, sessionKeys) {
const keyObjs = sessionKeys || await this.decryptSessionKeys(privateKeys, passwords); const keyObjs = sessionKeys || await this.decryptSessionKeys(privateKeys, passwords);
@ -105,7 +105,7 @@ Message.prototype.decrypt = async function(privateKeys, passwords, sessionKeys)
); );
if (symEncryptedPacketlist.length === 0) { if (symEncryptedPacketlist.length === 0) {
return; return this;
} }
const symEncryptedPacket = symEncryptedPacketlist[0]; const symEncryptedPacket = symEncryptedPacketlist[0];
@ -138,74 +138,73 @@ Message.prototype.decrypt = async function(privateKeys, passwords, sessionKeys)
* Decrypt encrypted session keys either with private keys or passwords. * Decrypt encrypted session keys either with private keys or passwords.
* @param {Array<Key>} privateKeys (optional) private keys with decrypted secret data * @param {Array<Key>} privateKeys (optional) private keys with decrypted secret data
* @param {Array<String>} passwords (optional) passwords used to decrypt * @param {Array<String>} passwords (optional) passwords used to decrypt
* @return {Array<{ data:Uint8Array, algorithm:String }>} array of object with potential sessionKey, algorithm pairs * @returns {Promise{Array<{ data:Uint8Array, algorithm:String }>}} array of object with potential sessionKey, algorithm pairs
*/ */
Message.prototype.decryptSessionKeys = function(privateKeys, passwords) { Message.prototype.decryptSessionKeys = async function(privateKeys, passwords) {
let keyPackets = []; let keyPackets = [];
return Promise.resolve().then(async () => {
if (passwords) {
const symESKeyPacketlist = this.packets.filterByTag(enums.packet.symEncryptedSessionKey);
if (!symESKeyPacketlist) {
throw new Error('No symmetrically encrypted session key packet found.');
}
await Promise.all(symESKeyPacketlist.map(async function(packet) {
await Promise.all(passwords.map(async function(password) {
try {
await packet.decrypt(password);
keyPackets.push(packet);
} catch (err) {}
}));
}));
} else if (privateKeys) { if (passwords) {
const pkESKeyPacketlist = this.packets.filterByTag(enums.packet.publicKeyEncryptedSessionKey); const symESKeyPacketlist = this.packets.filterByTag(enums.packet.symEncryptedSessionKey);
if (!pkESKeyPacketlist) { if (!symESKeyPacketlist) {
throw new Error('No public key encrypted session key packet found.'); throw new Error('No symmetrically encrypted session key packet found.');
} }
await Promise.all(pkESKeyPacketlist.map(async function(packet) { await Promise.all(symESKeyPacketlist.map(async function(packet) {
const privateKeyPackets = privateKeys.reduce(function(acc, privateKey) { await Promise.all(passwords.map(async function(password) {
return acc.concat(privateKey.getKeyPackets(packet.publicKeyId)); try {
}, []); await packet.decrypt(password);
await Promise.all(privateKeyPackets.map(async function(privateKeyPacket) { keyPackets.push(packet);
if (!privateKeyPacket) { } catch (err) {}
return;
}
if (!privateKeyPacket.isDecrypted) {
throw new Error('Private key is not decrypted.');
}
try {
await packet.decrypt(privateKeyPacket);
keyPackets.push(packet);
} catch (err) {}
}));
})); }));
} else { }));
throw new Error('No key or password specified.');
}
}).then(() => {
if (keyPackets.length) {
// Return only unique session keys
if (keyPackets.length > 1) {
const seen = {};
keyPackets = keyPackets.filter(function(item) {
const k = item.sessionKeyAlgorithm + util.Uint8Array_to_str(item.sessionKey);
if (seen.hasOwnProperty(k)) {
return false;
}
seen[k] = true;
return true;
});
}
return keyPackets.map(packet => ({ data: packet.sessionKey, algorithm: packet.sessionKeyAlgorithm })); } else if (privateKeys) {
const pkESKeyPacketlist = this.packets.filterByTag(enums.packet.publicKeyEncryptedSessionKey);
if (!pkESKeyPacketlist) {
throw new Error('No public key encrypted session key packet found.');
} }
throw new Error('Session key decryption failed.'); await Promise.all(pkESKeyPacketlist.map(async function(packet) {
}); const privateKeyPackets = privateKeys.reduce(function(acc, privateKey) {
return acc.concat(privateKey.getKeyPackets(packet.publicKeyId));
}, []);
await Promise.all(privateKeyPackets.map(async function(privateKeyPacket) {
if (!privateKeyPacket) {
return;
}
if (!privateKeyPacket.isDecrypted) {
throw new Error('Private key is not decrypted.');
}
try {
await packet.decrypt(privateKeyPacket);
keyPackets.push(packet);
} catch (err) {}
}));
}));
} else {
throw new Error('No key or password specified.');
}
if (keyPackets.length) {
// Return only unique session keys
if (keyPackets.length > 1) {
const seen = {};
keyPackets = keyPackets.filter(function(item) {
const k = item.sessionKeyAlgorithm + util.Uint8Array_to_str(item.sessionKey);
if (seen.hasOwnProperty(k)) {
return false;
}
seen[k] = true;
return true;
});
}
return keyPackets.map(packet => ({ data: packet.sessionKey, algorithm: packet.sessionKeyAlgorithm }));
}
throw new Error('Session key decryption failed.');
}; };
/** /**
* Get literal data that is the body of the message * Get literal data that is the body of the message
* @return {(Uint8Array|null)} literal body of the message as Uint8Array * @returns {(Uint8Array|null)} literal body of the message as Uint8Array
*/ */
Message.prototype.getLiteralData = function() { Message.prototype.getLiteralData = function() {
const literal = this.packets.findPacket(enums.packet.literal); const literal = this.packets.findPacket(enums.packet.literal);
@ -214,7 +213,7 @@ Message.prototype.getLiteralData = function() {
/** /**
* Get filename from literal data packet * Get filename from literal data packet
* @return {(String|null)} filename of literal data packet as string * @returns {(String|null)} filename of literal data packet as string
*/ */
Message.prototype.getFilename = function() { Message.prototype.getFilename = function() {
const literal = this.packets.findPacket(enums.packet.literal); const literal = this.packets.findPacket(enums.packet.literal);
@ -223,7 +222,7 @@ Message.prototype.getFilename = function() {
/** /**
* Get literal data as text * Get literal data as text
* @return {(String|null)} literal body of the message interpreted as text * @returns {(String|null)} literal body of the message interpreted as text
*/ */
Message.prototype.getText = function() { Message.prototype.getText = function() {
const literal = this.packets.findPacket(enums.packet.literal); const literal = this.packets.findPacket(enums.packet.literal);
@ -240,54 +239,52 @@ Message.prototype.getText = function() {
* @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String } * @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String }
* @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs * @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs
* @param {Date} date (optional) override the creation date of the literal package * @param {Date} date (optional) override the creation date of the literal package
* @return {Message} new message with encrypted content * @returns {Promise<Message>} new message with encrypted content
*/ */
Message.prototype.encrypt = function(keys, passwords, sessionKey, wildcard=false, date=new Date()) { Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard=false, date=new Date()) {
let symAlgo; let symAlgo;
let msg;
let symEncryptedPacket; let symEncryptedPacket;
return Promise.resolve().then(async () => {
if (sessionKey) { if (sessionKey) {
if (!util.isUint8Array(sessionKey.data) || !util.isString(sessionKey.algorithm)) { if (!util.isUint8Array(sessionKey.data) || !util.isString(sessionKey.algorithm)) {
throw new Error('Invalid session key for encryption.'); throw new Error('Invalid session key for encryption.');
}
symAlgo = sessionKey.algorithm;
sessionKey = sessionKey.data;
} else if (keys && keys.length) {
symAlgo = enums.read(enums.symmetric, getPreferredSymAlgo(keys));
} else if (passwords && passwords.length) {
symAlgo = enums.read(enums.symmetric, config.encryption_cipher);
} else {
throw new Error('No keys, passwords, or session key provided.');
} }
symAlgo = sessionKey.algorithm;
sessionKey = sessionKey.data;
} else if (keys && keys.length) {
symAlgo = enums.read(enums.symmetric, getPreferredSymAlgo(keys));
} else if (passwords && passwords.length) {
symAlgo = enums.read(enums.symmetric, config.encryption_cipher);
} else {
throw new Error('No keys, passwords, or session key provided.');
}
if (!sessionKey) { if (!sessionKey) {
sessionKey = crypto.generateSessionKey(symAlgo); sessionKey = await crypto.generateSessionKey(symAlgo);
}
const msg = await encryptSessionKey(sessionKey, symAlgo, keys, passwords, wildcard, date);
if (config.aead_protect) {
symEncryptedPacket = new packet.SymEncryptedAEADProtected();
} else if (config.integrity_protect) {
symEncryptedPacket = new packet.SymEncryptedIntegrityProtected();
} else {
symEncryptedPacket = new packet.SymmetricallyEncrypted();
}
symEncryptedPacket.packets = this.packets;
await symEncryptedPacket.encrypt(symAlgo, sessionKey);
msg.packets.push(symEncryptedPacket);
symEncryptedPacket.packets = new packet.List(); // remove packets after encryption
return {
message: msg,
sessionKey: {
data: sessionKey,
algorithm: symAlgo
} }
};
msg = await encryptSessionKey(sessionKey, symAlgo, keys, passwords, wildcard, date);
if (config.aead_protect) {
symEncryptedPacket = new packet.SymEncryptedAEADProtected();
} else if (config.integrity_protect) {
symEncryptedPacket = new packet.SymEncryptedIntegrityProtected();
} else {
symEncryptedPacket = new packet.SymmetricallyEncrypted();
}
symEncryptedPacket.packets = this.packets;
return symEncryptedPacket.encrypt(symAlgo, sessionKey);
}).then(() => {
msg.packets.push(symEncryptedPacket);
symEncryptedPacket.packets = new packet.List(); // remove packets after encryption
return {
message: msg,
sessionKey: {
data: sessionKey,
algorithm: symAlgo
}
};
});
}; };
/** /**
@ -298,64 +295,64 @@ Message.prototype.encrypt = function(keys, passwords, sessionKey, wildcard=false
* @param {Array<String>} passwords (optional) for message encryption * @param {Array<String>} passwords (optional) for message encryption
* @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs * @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs
* @param {Date} date (optional) override the creation date signature * @param {Date} date (optional) override the creation date signature
* @return {Message} new message with encrypted content * @returns {Promise<Message>} new message with encrypted content
*/ */
export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords, wildcard=false, date=new Date()) { export async function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords, wildcard=false, date=new Date()) {
const packetlist = new packet.List(); const packetlist = new packet.List();
return Promise.resolve().then(async () => { if (publicKeys) {
if (publicKeys) { const results = await Promise.all(publicKeys.map(async function(key) {
const results = await Promise.all(publicKeys.map(async function(key) { await key.verifyPrimaryUser();
await key.verifyPrimaryUser(); const encryptionKeyPacket = key.getEncryptionKeyPacket(undefined, date);
const encryptionKeyPacket = key.getEncryptionKeyPacket(undefined, date); if (!encryptionKeyPacket) {
if (!encryptionKeyPacket) { throw new Error('Could not find valid key packet for encryption in key ' + key.primaryKey.getKeyId().toHex());
throw new Error('Could not find valid key packet for encryption in key ' + key.primaryKey.getKeyId().toHex()); }
const pkESKeyPacket = new packet.PublicKeyEncryptedSessionKey();
pkESKeyPacket.publicKeyId = wildcard ? type_keyid.wildcard() : encryptionKeyPacket.getKeyId();
pkESKeyPacket.publicKeyAlgorithm = encryptionKeyPacket.algorithm;
pkESKeyPacket.sessionKey = sessionKey;
pkESKeyPacket.sessionKeyAlgorithm = symAlgo;
await pkESKeyPacket.encrypt(encryptionKeyPacket);
delete pkESKeyPacket.sessionKey; // delete plaintext session key after encryption
return pkESKeyPacket;
}));
packetlist.concat(results);
}
if (passwords) {
const testDecrypt = async function(keyPacket, password) {
try {
await keyPacket.decrypt(password);
return 1;
} catch (e) {
return 0;
}
};
const sum = (accumulator, currentValue) => accumulator + currentValue;
const encryptPassword = async function(sessionKey, symAlgo, password) {
const symEncryptedSessionKeyPacket = new packet.SymEncryptedSessionKey();
symEncryptedSessionKeyPacket.sessionKey = sessionKey;
symEncryptedSessionKeyPacket.sessionKeyAlgorithm = symAlgo;
await symEncryptedSessionKeyPacket.encrypt(password);
if (config.password_collision_check) {
const results = await Promise.all(passwords.map(pwd => testDecrypt(symEncryptedSessionKeyPacket, pwd)));
if (results.reduce(sum) !== 1) {
return encryptPassword(sessionKey, symAlgo, password);
} }
const pkESKeyPacket = new packet.PublicKeyEncryptedSessionKey(); }
pkESKeyPacket.publicKeyId = wildcard ? type_keyid.wildcard() : encryptionKeyPacket.getKeyId();
pkESKeyPacket.publicKeyAlgorithm = encryptionKeyPacket.algorithm;
pkESKeyPacket.sessionKey = sessionKey;
pkESKeyPacket.sessionKeyAlgorithm = symAlgo;
await pkESKeyPacket.encrypt(encryptionKeyPacket);
delete pkESKeyPacket.sessionKey; // delete plaintext session key after encryption
return pkESKeyPacket;
}));
packetlist.concat(results);
}
if (passwords) { delete symEncryptedSessionKeyPacket.sessionKey; // delete plaintext session key after encryption
const testDecrypt = async function(keyPacket, password) { return symEncryptedSessionKeyPacket;
try { };
await keyPacket.decrypt(password);
return 1;
} catch (e) {
return 0;
}
};
const sum = (accumulator, currentValue) => accumulator + currentValue; const results = await Promise.all(passwords.map(pwd => encryptPassword(sessionKey, symAlgo, pwd)));
packetlist.concat(results);
}
const encryptPassword = async function(sessionKey, symAlgo, password) { return new Message(packetlist);
const symEncryptedSessionKeyPacket = new packet.SymEncryptedSessionKey();
symEncryptedSessionKeyPacket.sessionKey = sessionKey;
symEncryptedSessionKeyPacket.sessionKeyAlgorithm = symAlgo;
await symEncryptedSessionKeyPacket.encrypt(password);
if (config.password_collision_check) {
const results = await Promise.all(passwords.map(pwd => testDecrypt(symEncryptedSessionKeyPacket, pwd)));
if (results.reduce(sum) !== 1) {
return encryptPassword(sessionKey, symAlgo, password);
}
}
delete symEncryptedSessionKeyPacket.sessionKey; // delete plaintext session key after encryption
return symEncryptedSessionKeyPacket;
};
const results = await Promise.all(passwords.map(pwd => encryptPassword(sessionKey, symAlgo, pwd)));
packetlist.concat(results);
}
}).then(() => new Message(packetlist));
} }
/** /**
@ -363,7 +360,7 @@ export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords, wi
* @param {Array<module:key~Key>} privateKeys private keys with decrypted secret key data for signing * @param {Array<module:key~Key>} privateKeys private keys with decrypted secret key data for signing
* @param {Signature} signature (optional) any existing detached signature to add to the message * @param {Signature} signature (optional) any existing detached signature to add to the message
* @param {Date} date} (optional) override the creation time of the signature * @param {Date} date} (optional) override the creation time of the signature
* @return {module:message~Message} new message with signed content * @returns {Promise<Message>} new message with signed content
*/ */
Message.prototype.sign = async function(privateKeys=[], signature=null, date=new Date()) { Message.prototype.sign = async function(privateKeys=[], signature=null, date=new Date()) {
const packetlist = new packet.List(); const packetlist = new packet.List();
@ -427,7 +424,7 @@ Message.prototype.sign = async function(privateKeys=[], signature=null, date=new
/** /**
* Compresses the message (the literal and -if signed- signature data packets of the message) * Compresses the message (the literal and -if signed- signature data packets of the message)
* @param {module:enums.compression} compression compression algorithm to be used * @param {module:enums.compression} compression compression algorithm to be used
* @return {module:message~Message} new message with compressed content * @returns {module:message~Message} new message with compressed content
*/ */
Message.prototype.compress = function(compression) { Message.prototype.compress = function(compression) {
if (compression === enums.compression.uncompressed) { if (compression === enums.compression.uncompressed) {
@ -446,10 +443,10 @@ Message.prototype.compress = function(compression) {
/** /**
* Create a detached signature for the message (the literal data packet of the message) * Create a detached signature for the message (the literal data packet of the message)
* @param {Array<module:key~Key>} privateKeys private keys with decrypted secret key data for signing * @param {Array<module:key~Key>} privateKeys private keys with decrypted secret key data for signing
* @param {Signature} signature (optional) any existing detached signature * @param {Signature} signature (optional) any existing detached signature
* @param {Date} date (optional) override the creation time of the signature * @param {Date} date (optional) override the creation time of the signature
* @return {module:signature~Signature} new detached signature of message content * @returns {Promise<module:signature~Signature>} new detached signature of message content
*/ */
Message.prototype.signDetached = async function(privateKeys=[], signature=null, date=new Date()) { Message.prototype.signDetached = async function(privateKeys=[], signature=null, date=new Date()) {
const literalDataPacket = this.packets.findPacket(enums.packet.literal); const literalDataPacket = this.packets.findPacket(enums.packet.literal);
@ -461,11 +458,11 @@ Message.prototype.signDetached = async function(privateKeys=[], signature=null,
/** /**
* Create signature packets for the message * Create signature packets for the message
* @param {module:packet/literal} literalDataPacket the literal data packet to sign * @param {module:packet/literal} literalDataPacket the literal data packet to sign
* @param {Array<module:key~Key>} privateKeys private keys with decrypted secret key data for signing * @param {Array<module:key~Key>} privateKeys private keys with decrypted secret key data for signing
* @param {Signature} signature (optional) any existing detached signature to append * @param {Signature} signature (optional) any existing detached signature to append
* @param {Date} date (optional) override the creationtime of the signature * @param {Date} date (optional) override the creationtime of the signature
* @return {module:packet/packetlist} list of signature packets * @returns {Promise<module:packet/packetlist>} list of signature packets
*/ */
export async function createSignaturePackets(literalDataPacket, privateKeys, signature=null, date=new Date()) { export async function createSignaturePackets(literalDataPacket, privateKeys, signature=null, date=new Date()) {
const packetlist = new packet.List(); const packetlist = new packet.List();
@ -507,7 +504,7 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig
* Verify message signatures * Verify message signatures
* @param {Array<module:key~Key>} keys array of keys to verify signatures * @param {Array<module:key~Key>} keys array of keys to verify signatures
* @param {Date} date (optional) Verify the signature against the given date, i.e. check signature creation time < date < expiration time * @param {Date} date (optional) Verify the signature against the given date, i.e. check signature creation time < date < expiration time
* @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature * @returns {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature
*/ */
Message.prototype.verify = function(keys, date=new Date()) { Message.prototype.verify = function(keys, date=new Date()) {
const msg = this.unwrapCompressed(); const msg = this.unwrapCompressed();
@ -524,7 +521,7 @@ Message.prototype.verify = function(keys, date=new Date()) {
* @param {Array<module:key~Key>} keys array of keys to verify signatures * @param {Array<module:key~Key>} keys array of keys to verify signatures
* @param {Signature} signature * @param {Signature} signature
* @param {Date} date Verify the signature against the given date, i.e. check signature creation time < date < expiration time * @param {Date} date Verify the signature against the given date, i.e. check signature creation time < date < expiration time
* @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature * @returns {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature
*/ */
Message.prototype.verifyDetached = function(signature, keys, date=new Date()) { Message.prototype.verifyDetached = function(signature, keys, date=new Date()) {
const msg = this.unwrapCompressed(); const msg = this.unwrapCompressed();
@ -542,7 +539,7 @@ Message.prototype.verifyDetached = function(signature, keys, date=new Date()) {
* @param {Array<module:packet/literal>} literalDataList array of literal data packets * @param {Array<module:packet/literal>} literalDataList array of literal data packets
* @param {Array<module:key~Key>} keys array of keys to verify signatures * @param {Array<module:key~Key>} keys array of keys to verify signatures
* @param {Date} date Verify the signature against the given date, i.e. check signature creation time < date < expiration time * @param {Date} date Verify the signature against the given date, i.e. check signature creation time < date < expiration time
* @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature * @returns {Promise{Array<({keyid: module:type/keyid, valid: Boolean})>}} list of signer's keyid and validity of signature
*/ */
export async function createVerificationObjects(signatureList, literalDataList, keys, date=new Date()) { export async function createVerificationObjects(signatureList, literalDataList, keys, date=new Date()) {
return Promise.all(signatureList.map(async function(signature) { return Promise.all(signatureList.map(async function(signature) {
@ -572,7 +569,7 @@ export async function createVerificationObjects(signatureList, literalDataList,
/** /**
* Unwrap compressed message * Unwrap compressed message
* @return {module:message~Message} message Content of compressed message * @returns {module:message~Message} message Content of compressed message
*/ */
Message.prototype.unwrapCompressed = function() { Message.prototype.unwrapCompressed = function() {
const compressed = this.packets.filterByTag(enums.packet.compressed); const compressed = this.packets.filterByTag(enums.packet.compressed);
@ -592,7 +589,7 @@ Message.prototype.appendSignature = function(detachedSignature) {
/** /**
* Returns ASCII armored text of message * Returns ASCII armored text of message
* @return {String} ASCII armor * @returns {String} ASCII armor
*/ */
Message.prototype.armor = function() { Message.prototype.armor = function() {
return armor.encode(enums.armor.message, this.packets.write()); return armor.encode(enums.armor.message, this.packets.write());
@ -601,7 +598,7 @@ Message.prototype.armor = function() {
/** /**
* reads an OpenPGP armored message and returns a message object * reads an OpenPGP armored message and returns a message object
* @param {String} armoredText text to be parsed * @param {String} armoredText text to be parsed
* @return {module:message~Message} new message object * @returns {module:message~Message} new message object
* @static * @static
*/ */
export function readArmored(armoredText) { export function readArmored(armoredText) {
@ -614,7 +611,7 @@ export function readArmored(armoredText) {
/** /**
* reads an OpenPGP message as byte array and returns a message object * reads an OpenPGP message as byte array and returns a message object
* @param {Uint8Array} input binary message * @param {Uint8Array} input binary message
* @return {Message} new message object * @returns {Message} new message object
* @static * @static
*/ */
export function read(input) { export function read(input) {
@ -628,7 +625,7 @@ export function read(input) {
* @param {String} text * @param {String} text
* @param {String} filename (optional) * @param {String} filename (optional)
* @param {Date} date (optional) * @param {Date} date (optional)
* @return {module:message~Message} new message object * @returns {module:message~Message} new message object
* @static * @static
*/ */
export function fromText(text, filename, date=new Date()) { export function fromText(text, filename, date=new Date()) {
@ -648,7 +645,7 @@ export function fromText(text, filename, date=new Date()) {
* @param {Uint8Array} bytes * @param {Uint8Array} bytes
* @param {String} filename (optional) * @param {String} filename (optional)
* @param {Date} date (optional) * @param {Date} date (optional)
* @return {module:message~Message} new message object * @returns {module:message~Message} new message object
* @static * @static
*/ */
export function fromBinary(bytes, filename, date=new Date()) { export function fromBinary(bytes, filename, date=new Date()) {

View File

@ -55,19 +55,20 @@ let asyncProxy; // instance of the asyncproxy
/** /**
* Set the path for the web worker script and create an instance of the async proxy * Set the path for the web worker script and create an instance of the async proxy
* @param {String} path relative path to the worker scripts, default: 'openpgp.worker.js' * @param {String} path relative path to the worker scripts, default: 'openpgp.worker.js'
* @param {Object} worker alternative to path parameter: web worker initialized with 'openpgp.worker.js' * @param {Number} n number of workers to initialize
* @param {Array<Object>} workers alternative to path parameter: web workers initialized with 'openpgp.worker.js'
*/ */
export function initWorker({ path='openpgp.worker.js', worker } = {}) { export function initWorker({ path='openpgp.worker.js', n = 1, workers = [] } = {}) {
if (worker || (typeof window !== 'undefined' && window.Worker)) { if (workers.length || (typeof window !== 'undefined' && window.Worker)) {
asyncProxy = new AsyncProxy({ path, worker, config }); asyncProxy = new AsyncProxy({ path, n, workers, config });
return true; return true;
} }
} }
/** /**
* Returns a reference to the async proxy if the worker was initialized with openpgp.initWorker() * Returns a reference to the async proxy if the worker was initialized with openpgp.initWorker()
* @return {module:worker/async_proxy~AsyncProxy|null} the async proxy or null if not initialized * @returns {module:worker/async_proxy~AsyncProxy|null} the async proxy or null if not initialized
*/ */
export function getWorker() { export function getWorker() {
return asyncProxy; return asyncProxy;
@ -96,7 +97,7 @@ export function destroyWorker() {
* @param {String} curve (optional) elliptic curve for ECC keys: curve25519, p256, p384, p521, or secp256k1 * @param {String} curve (optional) elliptic curve for ECC keys: curve25519, p256, p384, p521, or secp256k1
* @param {Boolean} unlocked (optional) If the returned secret part of the generated key is unlocked * @param {Boolean} unlocked (optional) If the returned secret part of the generated key is unlocked
* @param {Number} keyExpirationTime (optional) The number of seconds after the key creation time that the key expires * @param {Number} keyExpirationTime (optional) The number of seconds after the key creation time that the key expires
* @return {Promise<Object>} The generated key object in the form: * @returns {Promise<Object>} The generated key object in the form:
* { key:Key, privateKeyArmored:String, publicKeyArmored:String } * { key:Key, privateKeyArmored:String, publicKeyArmored:String }
* @static * @static
*/ */
@ -133,7 +134,7 @@ export function generateKey({
* @param {String} passphrase (optional) The passphrase used to encrypt the resulting private key * @param {String} passphrase (optional) The passphrase used to encrypt the resulting private key
* @param {Boolean} unlocked (optional) If the returned secret part of the generated key is unlocked * @param {Boolean} unlocked (optional) If the returned secret part of the generated key is unlocked
* @param {Number} keyExpirationTime (optional) The number of seconds after the key creation time that the key expires * @param {Number} keyExpirationTime (optional) The number of seconds after the key creation time that the key expires
* @return {Promise<Object>} The generated key object in the form: * @returns {Promise<Object>} The generated key object in the form:
* { key:Key, privateKeyArmored:String, publicKeyArmored:String } * { key:Key, privateKeyArmored:String, publicKeyArmored:String }
* @static * @static
*/ */
@ -163,7 +164,7 @@ export function reformatKey({
* Unlock a private key with your passphrase. * Unlock a private key with your passphrase.
* @param {Key} privateKey the private key that is to be decrypted * @param {Key} privateKey the private key that is to be decrypted
* @param {String} passphrase the user's passphrase chosen during key generation * @param {String} passphrase the user's passphrase chosen during key generation
* @return {Key} the unlocked private key * @returns {Promise<Object>} the unlocked key object in the form: { key:Key }
*/ */
export function decryptKey({ privateKey, passphrase }) { export function decryptKey({ privateKey, passphrase }) {
if (asyncProxy) { // use web worker if available if (asyncProxy) { // use web worker if available
@ -179,6 +180,26 @@ export function decryptKey({ privateKey, passphrase }) {
}).catch(onError.bind(null, 'Error decrypting private key')); }).catch(onError.bind(null, 'Error decrypting private key'));
} }
/**
* Lock a private key with your passphrase.
* @param {Key} privateKey the private key that is to be decrypted
* @param {String} passphrase the user's passphrase chosen during key generation
* @returns {Promise<Object>} the locked key object in the form: { key:Key }
*/
export function encryptKey({ privateKey, passphrase }) {
if (asyncProxy) { // use web worker if available
return asyncProxy.delegate('encryptKey', { privateKey, passphrase });
}
return Promise.resolve().then(async function() {
await privateKey.encrypt(passphrase);
return {
key: privateKey
};
}).catch(onError.bind(null, 'Error decrypting private key'));
}
/////////////////////////////////////////// ///////////////////////////////////////////
// // // //
@ -203,7 +224,7 @@ export function decryptKey({ privateKey, passphrase }) {
* @param {Boolean} returnSessionKey (optional) if the unencrypted session key should be added to returned object * @param {Boolean} returnSessionKey (optional) if the unencrypted session key should be added to returned object
* @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs * @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs
* @param {Date} date (optional) override the creation date of the message and the message signature * @param {Date} date (optional) override the creation date of the message and the message signature
* @return {Promise<Object>} encrypted (and optionally signed message) in the form: * @returns {Promise<Object>} encrypted (and optionally signed message) in the form:
* {data: ASCII armored message if 'armor' is true, * {data: ASCII armored message if 'armor' is true,
* message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true} * message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true}
* @static * @static
@ -255,7 +276,7 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, sessionKey,
* @param {String} format (optional) return data format either as 'utf8' or 'binary' * @param {String} format (optional) return data format either as 'utf8' or 'binary'
* @param {Signature} signature (optional) detached signature for verification * @param {Signature} signature (optional) detached signature for verification
* @param {Date} date (optional) use the given date for verification instead of the current time * @param {Date} date (optional) use the given date for verification instead of the current time
* @return {Promise<Object>} decrypted and verified message in the form: * @returns {Promise<Object>} decrypted and verified message in the form:
* { data:Uint8Array|String, filename:String, signatures:[{ keyid:String, valid:Boolean }] } * { data:Uint8Array|String, filename:String, signatures:[{ keyid:String, valid:Boolean }] }
* @static * @static
*/ */
@ -294,7 +315,7 @@ export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKe
* @param {Boolean} armor (optional) if the return value should be ascii armored or the message object * @param {Boolean} armor (optional) if the return value should be ascii armored or the message object
* @param {Boolean} detached (optional) if the return value should contain a detached signature * @param {Boolean} detached (optional) if the return value should contain a detached signature
* @param {Date} date (optional) override the creation date signature * @param {Date} date (optional) override the creation date signature
* @return {Promise<Object>} signed cleartext in the form: * @returns {Promise<Object>} signed cleartext in the form:
* {data: ASCII armored message if 'armor' is true, * {data: ASCII armored message if 'armor' is true,
* message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true} * message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true}
* @static * @static
@ -336,7 +357,7 @@ export function sign({
* @param {CleartextMessage} message cleartext message object with signatures * @param {CleartextMessage} message cleartext message object with signatures
* @param {Signature} signature (optional) detached signature for verification * @param {Signature} signature (optional) detached signature for verification
* @param {Date} date (optional) use the given date for verification instead of the current time * @param {Date} date (optional) use the given date for verification instead of the current time
* @return {Promise<Object>} cleartext with status of verified signatures in the form of: * @returns {Promise<Object>} cleartext with status of verified signatures in the form of:
* { data:String, signatures: [{ keyid:String, valid:Boolean }] } * { data:String, signatures: [{ keyid:String, valid:Boolean }] }
* @static * @static
*/ */
@ -372,7 +393,7 @@ export function verify({ message, publicKeys, signature=null, date=new Date() })
* @param {Key|Array<Key>} publicKeys (optional) array of public keys or single key, used to encrypt the key * @param {Key|Array<Key>} publicKeys (optional) array of public keys or single key, used to encrypt the key
* @param {String|Array<String>} passwords (optional) passwords for the message * @param {String|Array<String>} passwords (optional) passwords for the message
* @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs * @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs
* @return {Promise<Message>} the encrypted session key packets contained in a message object * @returns {Promise<Message>} the encrypted session key packets contained in a message object
* @static * @static
*/ */
export function encryptSessionKey({ data, algorithm, publicKeys, passwords, wildcard=false }) { export function encryptSessionKey({ data, algorithm, publicKeys, passwords, wildcard=false }) {
@ -395,7 +416,7 @@ export function encryptSessionKey({ data, algorithm, publicKeys, passwords, wild
* @param {Message} message a message object containing the encrypted session key packets * @param {Message} message a message object containing the encrypted session key packets
* @param {Key|Array<Key>} privateKeys (optional) private keys with decrypted secret key data * @param {Key|Array<Key>} privateKeys (optional) private keys with decrypted secret key data
* @param {String|Array<String>} passwords (optional) passwords to decrypt the session key * @param {String|Array<String>} passwords (optional) passwords to decrypt the session key
* @return {Promise<Object|undefined>} Array of decrypted session key, algorithm pairs in form: * @returns {Promise<Object|undefined>} Array of decrypted session key, algorithm pairs in form:
* { data:Uint8Array, algorithm:String } * { data:Uint8Array, algorithm:String }
* or 'undefined' if no key packets found * or 'undefined' if no key packets found
* @static * @static
@ -484,7 +505,7 @@ function formatUserIds(userIds) {
/** /**
* Normalize parameter to an array if it is not undefined. * Normalize parameter to an array if it is not undefined.
* @param {Object} param the parameter to be normalized * @param {Object} param the parameter to be normalized
* @return {Array<Object>|undefined} the resulting array or undefined * @returns {Array<Object>|undefined} the resulting array or undefined
*/ */
function toArray(param) { function toArray(param) {
if (param && !util.isArray(param)) { if (param && !util.isArray(param)) {
@ -498,7 +519,7 @@ function toArray(param) {
* @param {String|Uint8Array} data the payload for the message * @param {String|Uint8Array} data the payload for the message
* @param {String} filename the literal data packet's filename * @param {String} filename the literal data packet's filename
* @param {Date} date the creation date of the package * @param {Date} date the creation date of the package
* @return {Message} a message object * @returns {Message} a message object
*/ */
function createMessage(data, filename, date=new Date()) { function createMessage(data, filename, date=new Date()) {
let msg; let msg;
@ -516,7 +537,7 @@ function createMessage(data, filename, date=new Date()) {
* Parse the message given a certain format. * Parse the message given a certain format.
* @param {Message} message the message object to be parse * @param {Message} message the message object to be parse
* @param {String} format the output format e.g. 'utf8' or 'binary' * @param {String} format the output format e.g. 'utf8' or 'binary'
* @return {Object} the parse data in the respective format * @returns {Object} the parse data in the respective format
*/ */
function parseMessage(message, format) { function parseMessage(message, format) {
if (format === 'binary') { if (format === 'binary') {
@ -551,7 +572,7 @@ function onError(message, error) {
/** /**
* Check for AES-GCM support and configuration by the user. Only browsers that * Check for AES-GCM support and configuration by the user. Only browsers that
* implement the current WebCrypto specification support native AES-GCM. * implement the current WebCrypto specification support native AES-GCM.
* @return {Boolean} If authenticated encryption should be used * @returns {Boolean} If authenticated encryption should be used
*/ */
function nativeAEAD() { function nativeAEAD() {
return util.getWebCrypto() && config.aead_protect; return util.getWebCrypto() && config.aead_protect;

View File

@ -91,7 +91,7 @@ Packetlist.prototype.push = function (packet) {
/** /**
* Remove a packet from the list and return it. * Remove a packet from the list and return it.
* @return {Object} The packet that was removed * @returns {Object} The packet that was removed
*/ */
Packetlist.prototype.pop = function() { Packetlist.prototype.pop = function() {
if (this.length === 0) { if (this.length === 0) {
@ -164,6 +164,8 @@ Packetlist.prototype.map = function (callback) {
/** /**
* Executes the callback function once for each element * Executes the callback function once for each element
* until it finds one where callback returns a truthy value * until it finds one where callback returns a truthy value
* @param {Function} callback
* @returns {Promise<Boolean>}
*/ */
Packetlist.prototype.some = async function (callback) { Packetlist.prototype.some = async function (callback) {
for (let i = 0; i < this.length; i++) { for (let i = 0; i < this.length; i++) {
@ -178,7 +180,7 @@ Packetlist.prototype.some = async function (callback) {
/** /**
* Traverses packet tree and returns first matching packet * Traverses packet tree and returns first matching packet
* @param {module:enums.packet} type The packet type * @param {module:enums.packet} type The packet type
* @return {module:packet/packet|null} * @returns {module:packet/packet|null}
*/ */
Packetlist.prototype.findPacket = function (type) { Packetlist.prototype.findPacket = function (type) {
const packetlist = this.filterByTag(type); const packetlist = this.filterByTag(type);

View File

@ -67,7 +67,7 @@ export default function PublicKeyEncryptedSessionKey() {
* @param {Integer} position Position to start reading from the input string * @param {Integer} position Position to start reading from the input string
* @param {Integer} len Length of the packet or the remaining length of * @param {Integer} len Length of the packet or the remaining length of
* input at position * input at position
* @return {module:packet/public_key_encrypted_session_key} Object representation * @returns {module:packet/public_key_encrypted_session_key} Object representation
*/ */
PublicKeyEncryptedSessionKey.prototype.read = function (bytes) { PublicKeyEncryptedSessionKey.prototype.read = function (bytes) {
this.version = bytes[0]; this.version = bytes[0];
@ -88,7 +88,7 @@ PublicKeyEncryptedSessionKey.prototype.read = function (bytes) {
/** /**
* Create a string representation of a tag 1 packet * Create a string representation of a tag 1 packet
* *
* @return {Uint8Array} The Uint8Array representation * @returns {Uint8Array} The Uint8Array representation
*/ */
PublicKeyEncryptedSessionKey.prototype.write = function () { PublicKeyEncryptedSessionKey.prototype.write = function () {
const arr = [new Uint8Array([this.version]), this.publicKeyId.write(), new Uint8Array([enums.write(enums.publicKey, this.publicKeyAlgorithm)])]; const arr = [new Uint8Array([this.version]), this.publicKeyId.write(), new Uint8Array([enums.write(enums.publicKey, this.publicKeyAlgorithm)])];
@ -100,6 +100,11 @@ PublicKeyEncryptedSessionKey.prototype.write = function () {
return util.concatUint8Array(arr); return util.concatUint8Array(arr);
}; };
/**
* Encrypt session key packet
* @param {module:packet/public_key} key Public key
* @returns {Promise<Boolean>}
*/
PublicKeyEncryptedSessionKey.prototype.encrypt = async function (key) { PublicKeyEncryptedSessionKey.prototype.encrypt = async function (key) {
let data = String.fromCharCode(enums.write(enums.symmetric, this.sessionKeyAlgorithm)); let data = String.fromCharCode(enums.write(enums.symmetric, this.sessionKeyAlgorithm));
@ -112,11 +117,12 @@ PublicKeyEncryptedSessionKey.prototype.encrypt = async function (key) {
if (algo === enums.publicKey.ecdh) { if (algo === enums.publicKey.ecdh) {
toEncrypt = new type_mpi(crypto.pkcs5.encode(data)); toEncrypt = new type_mpi(crypto.pkcs5.encode(data));
} else { } else {
toEncrypt = new type_mpi(crypto.pkcs1.eme.encode(data, key.params[0].byteLength())); toEncrypt = new type_mpi(await crypto.pkcs1.eme.encode(data, key.params[0].byteLength()));
} }
this.encrypted = await crypto.publicKeyEncrypt( this.encrypted = await crypto.publicKeyEncrypt(
algo, key.params, toEncrypt, key.fingerprint); algo, key.params, toEncrypt, key.fingerprint);
return true;
}; };
/** /**
@ -125,7 +131,7 @@ PublicKeyEncryptedSessionKey.prototype.encrypt = async function (key) {
* *
* @param {module:packet/secret_key} key * @param {module:packet/secret_key} key
* Private key with secret params unlocked * Private key with secret params unlocked
* @return {String} The unencrypted session key * @returns {Promise<Boolean>}
*/ */
PublicKeyEncryptedSessionKey.prototype.decrypt = async function (key) { PublicKeyEncryptedSessionKey.prototype.decrypt = async function (key) {
const algo = enums.write(enums.publicKey, this.publicKeyAlgorithm); const algo = enums.write(enums.publicKey, this.publicKeyAlgorithm);
@ -150,6 +156,7 @@ PublicKeyEncryptedSessionKey.prototype.decrypt = async function (key) {
this.sessionKey = key; this.sessionKey = key;
this.sessionKeyAlgorithm = enums.read(enums.symmetric, decoded.charCodeAt(0)); this.sessionKeyAlgorithm = enums.read(enums.symmetric, decoded.charCodeAt(0));
} }
return true;
}; };
/** /**

View File

@ -81,7 +81,7 @@ function parse_cleartext_params(hash_algorithm, cleartext, algorithm) {
const hash = util.Uint8Array_to_str(hashfn(cleartext)); const hash = util.Uint8Array_to_str(hashfn(cleartext));
if (hash !== hashtext) { if (hash !== hashtext) {
return new Error("Hash mismatch."); return new Error("Incorrect key passphrase");
} }
const algo = enums.write(enums.publicKey, algorithm); const algo = enums.write(enums.publicKey, algorithm);
@ -151,7 +151,7 @@ SecretKey.prototype.read = function (bytes) {
}; };
/** Creates an OpenPGP key packet for the given key. /** Creates an OpenPGP key packet for the given key.
* @return {String} A string of bytes containing the secret key OpenPGP packet * @returns {String} A string of bytes containing the secret key OpenPGP packet
*/ */
SecretKey.prototype.write = function () { SecretKey.prototype.write = function () {
const arr = [this.writePublicKey()]; const arr = [this.writePublicKey()];
@ -172,21 +172,23 @@ SecretKey.prototype.write = function () {
* and the passphrase is empty or undefined, the key will be set as not encrypted. * and the passphrase is empty or undefined, the key will be set as not encrypted.
* This can be used to remove passphrase protection after calling decrypt(). * This can be used to remove passphrase protection after calling decrypt().
* @param {String} passphrase * @param {String} passphrase
* @returns {Promise<Boolean>}
*/ */
SecretKey.prototype.encrypt = function (passphrase) { SecretKey.prototype.encrypt = async function (passphrase) {
if (this.isDecrypted && !passphrase) { if (this.isDecrypted && !passphrase) {
this.encrypted = null; this.encrypted = null;
return; return false;
} else if (!passphrase) { } else if (!passphrase) {
throw new Error('The key must be decrypted before removing passphrase protection.'); throw new Error('The key must be decrypted before removing passphrase protection.');
} }
const s2k = new type_s2k(); const s2k = new type_s2k();
s2k.salt = await crypto.random.getRandomBytes(8);
const symmetric = 'aes256'; const symmetric = 'aes256';
const cleartext = write_cleartext_params('sha1', this.algorithm, this.params); const cleartext = write_cleartext_params('sha1', this.algorithm, this.params);
const key = produceEncryptionKey(s2k, passphrase, symmetric); const key = produceEncryptionKey(s2k, passphrase, symmetric);
const blockLen = crypto.cipher[symmetric].blockSize; const blockLen = crypto.cipher[symmetric].blockSize;
const iv = crypto.random.getRandomBytes(blockLen); const iv = await crypto.random.getRandomBytes(blockLen);
const arr = [new Uint8Array([254, enums.write(enums.symmetric, symmetric)])]; const arr = [new Uint8Array([254, enums.write(enums.symmetric, symmetric)])];
arr.push(s2k.write()); arr.push(s2k.write());
@ -194,6 +196,7 @@ SecretKey.prototype.encrypt = function (passphrase) {
arr.push(crypto.cfb.normalEncrypt(symmetric, key, cleartext, iv)); arr.push(crypto.cfb.normalEncrypt(symmetric, key, cleartext, iv));
this.encrypted = util.concatUint8Array(arr); this.encrypted = util.concatUint8Array(arr);
return true;
}; };
function produceEncryptionKey(s2k, passphrase, algorithm) { function produceEncryptionKey(s2k, passphrase, algorithm) {
@ -208,12 +211,10 @@ function produceEncryptionKey(s2k, passphrase, algorithm) {
* @link module:packet/secret_key.isDecrypted should be * @link module:packet/secret_key.isDecrypted should be
* false otherwise a call to this function is not needed * false otherwise a call to this function is not needed
* *
* @param {String} str_passphrase The passphrase for this private key * @param {String} passphrase The passphrase for this private key as string
* as string * @returns {Promise<Boolean>}
* @return {Boolean} True if the passphrase was correct or param already
* decrypted; false if not
*/ */
SecretKey.prototype.decrypt = function (passphrase) { SecretKey.prototype.decrypt = async function (passphrase) {
if (this.isDecrypted) { if (this.isDecrypted) {
return true; return true;
} }
@ -261,11 +262,12 @@ SecretKey.prototype.decrypt = function (passphrase) {
const privParams = parse_cleartext_params(hash, cleartext, this.algorithm); const privParams = parse_cleartext_params(hash, cleartext, this.algorithm);
if (privParams instanceof Error) { if (privParams instanceof Error) {
return false; throw privParams;
} }
this.params = this.params.concat(privParams); this.params = this.params.concat(privParams);
this.isDecrypted = true; this.isDecrypted = true;
this.encrypted = null; this.encrypted = null;
return true; return true;
}; };

View File

@ -211,6 +211,7 @@ Signature.prototype.write = function () {
* Signs provided data. This needs to be done prior to serialization. * Signs provided data. This needs to be done prior to serialization.
* @param {module:packet/secret_key} key private key used to sign the message. * @param {module:packet/secret_key} key private key used to sign the message.
* @param {Object} data Contains packets to be signed. * @param {Object} data Contains packets to be signed.
* @returns {Promise<Boolean>}
*/ */
Signature.prototype.sign = async function (key, data) { Signature.prototype.sign = async function (key, data) {
const signatureType = enums.write(enums.signature, this.signatureType); const signatureType = enums.write(enums.signature, this.signatureType);
@ -247,6 +248,7 @@ Signature.prototype.sign = async function (key, data) {
this.signature = await crypto.signature.sign( this.signature = await crypto.signature.sign(
publicKeyAlgorithm, hashAlgorithm, key.params, toHash publicKeyAlgorithm, hashAlgorithm, key.params, toHash
); );
return true;
}; };
/** /**
@ -615,7 +617,7 @@ Signature.prototype.calculateTrailer = function () {
* @param {String|Object} data data which on the signature applies * @param {String|Object} data data which on the signature applies
* @param {module:packet/public_subkey|module:packet/public_key| * @param {module:packet/public_subkey|module:packet/public_key|
* module:packet/secret_subkey|module:packet/secret_key} key the public key to verify the signature * module:packet/secret_subkey|module:packet/secret_key} key the public key to verify the signature
* @return {boolean} True if message is verified, else false. * @return {Promise<Boolean>} True if message is verified, else false.
*/ */
Signature.prototype.verify = async function (key, data) { Signature.prototype.verify = async function (key, data) {
const signatureType = enums.write(enums.signature, this.signatureType); const signatureType = enums.write(enums.signature, this.signatureType);

View File

@ -64,23 +64,21 @@ SymEncryptedAEADProtected.prototype.write = function () {
* Decrypt the encrypted payload. * Decrypt the encrypted payload.
* @param {String} sessionKeyAlgorithm The session key's cipher algorithm e.g. 'aes128' * @param {String} sessionKeyAlgorithm The session key's cipher algorithm e.g. 'aes128'
* @param {Uint8Array} key The session key used to encrypt the payload * @param {Uint8Array} key The session key used to encrypt the payload
* @return {Promise<undefined>} Nothing is returned * @return {Promise<Boolean>}
*/ */
SymEncryptedAEADProtected.prototype.decrypt = function (sessionKeyAlgorithm, key) { SymEncryptedAEADProtected.prototype.decrypt = async function (sessionKeyAlgorithm, key) {
return crypto.gcm.decrypt(sessionKeyAlgorithm, this.encrypted, key, this.iv).then(decrypted => { this.packets.read(await crypto.gcm.decrypt(sessionKeyAlgorithm, this.encrypted, key, this.iv));
this.packets.read(decrypted); return true;
});
}; };
/** /**
* Encrypt the packet list payload. * Encrypt the packet list payload.
* @param {String} sessionKeyAlgorithm The session key's cipher algorithm e.g. 'aes128' * @param {String} sessionKeyAlgorithm The session key's cipher algorithm e.g. 'aes128'
* @param {Uint8Array} key The session key used to encrypt the payload * @param {Uint8Array} key The session key used to encrypt the payload
* @return {Promise<undefined>} Nothing is returned * @return {Promise<Boolean>}
*/ */
SymEncryptedAEADProtected.prototype.encrypt = function (sessionKeyAlgorithm, key) { SymEncryptedAEADProtected.prototype.encrypt = async function (sessionKeyAlgorithm, key) {
this.iv = crypto.random.getRandomValues(new Uint8Array(IV_LEN)); // generate new random IV this.iv = await crypto.random.getRandomBytes(IV_LEN); // generate new random IV
return crypto.gcm.encrypt(sessionKeyAlgorithm, this.packets.write(), key, this.iv).then(encrypted => { this.encrypted = await crypto.gcm.encrypt(sessionKeyAlgorithm, this.packets.write(), key, this.iv);
this.encrypted = encrypted; return true;
});
}; };

View File

@ -79,11 +79,11 @@ SymEncryptedIntegrityProtected.prototype.write = function () {
* Encrypt the payload in the packet. * Encrypt the payload in the packet.
* @param {String} sessionKeyAlgorithm The selected symmetric encryption algorithm to be used e.g. 'aes128' * @param {String} sessionKeyAlgorithm The selected symmetric encryption algorithm to be used e.g. 'aes128'
* @param {Uint8Array} key The key of cipher blocksize length to be used * @param {Uint8Array} key The key of cipher blocksize length to be used
* @return {Promise} * @return {Promise<Boolean>}
*/ */
SymEncryptedIntegrityProtected.prototype.encrypt = function (sessionKeyAlgorithm, key) { SymEncryptedIntegrityProtected.prototype.encrypt = async function (sessionKeyAlgorithm, key) {
const bytes = this.packets.write(); const bytes = this.packets.write();
const prefixrandom = crypto.getPrefixRandom(sessionKeyAlgorithm); const prefixrandom = await crypto.getPrefixRandom(sessionKeyAlgorithm);
const repeat = new Uint8Array([prefixrandom[prefixrandom.length - 2], prefixrandom[prefixrandom.length - 1]]); const repeat = new Uint8Array([prefixrandom[prefixrandom.length - 2], prefixrandom[prefixrandom.length - 1]]);
const prefix = util.concatUint8Array([prefixrandom, repeat]); const prefix = util.concatUint8Array([prefixrandom, repeat]);
const mdc = new Uint8Array([0xD3, 0x14]); // modification detection code packet const mdc = new Uint8Array([0xD3, 0x14]); // modification detection code packet
@ -98,17 +98,16 @@ SymEncryptedIntegrityProtected.prototype.encrypt = function (sessionKeyAlgorithm
this.encrypted = crypto.cfb.encrypt(prefixrandom, sessionKeyAlgorithm, tohash, key, false); this.encrypted = crypto.cfb.encrypt(prefixrandom, sessionKeyAlgorithm, tohash, key, false);
this.encrypted = this.encrypted.subarray(0, prefix.length + tohash.length); this.encrypted = this.encrypted.subarray(0, prefix.length + tohash.length);
} }
return true;
return Promise.resolve();
}; };
/** /**
* Decrypts the encrypted data contained in the packet. * Decrypts the encrypted data contained in the packet.
* @param {String} sessionKeyAlgorithm The selected symmetric encryption algorithm to be used e.g. 'aes128' * @param {String} sessionKeyAlgorithm The selected symmetric encryption algorithm to be used e.g. 'aes128'
* @param {Uint8Array} key The key of cipher blocksize length to be used * @param {Uint8Array} key The key of cipher blocksize length to be used
* @return {Promise} * @return {Promise<Boolean>}
*/ */
SymEncryptedIntegrityProtected.prototype.decrypt = function (sessionKeyAlgorithm, key) { SymEncryptedIntegrityProtected.prototype.decrypt = async function (sessionKeyAlgorithm, key) {
let decrypted; let decrypted;
if (sessionKeyAlgorithm.substr(0, 3) === 'aes') { // AES optimizations. Native code for node, asmCrypto for browser. if (sessionKeyAlgorithm.substr(0, 3) === 'aes') { // AES optimizations. Native code for node, asmCrypto for browser.
decrypted = aesDecrypt(sessionKeyAlgorithm, this.encrypted, key); decrypted = aesDecrypt(sessionKeyAlgorithm, this.encrypted, key);

View File

@ -52,7 +52,7 @@ export default function SymEncryptedSessionKey() {
this.sessionKeyEncryptionAlgorithm = null; this.sessionKeyEncryptionAlgorithm = null;
this.sessionKeyAlgorithm = 'aes256'; this.sessionKeyAlgorithm = 'aes256';
this.encrypted = null; this.encrypted = null;
this.s2k = new type_s2k(); this.s2k = null;
} }
/** /**
@ -73,6 +73,7 @@ SymEncryptedSessionKey.prototype.read = function(bytes) {
const algo = enums.read(enums.symmetric, bytes[1]); const algo = enums.read(enums.symmetric, bytes[1]);
// A string-to-key (S2K) specifier, length as defined above. // A string-to-key (S2K) specifier, length as defined above.
this.s2k = new type_s2k();
const s2klength = this.s2k.read(bytes.subarray(2, bytes.length)); const s2klength = this.s2k.read(bytes.subarray(2, bytes.length));
// Optionally, the encrypted session key itself, which is decrypted // Optionally, the encrypted session key itself, which is decrypted
@ -101,12 +102,11 @@ SymEncryptedSessionKey.prototype.write = function() {
}; };
/** /**
* Decrypts the session key (only for public key encrypted session key * Decrypts the session key
* packets (tag 1) * @param {String} passphrase The passphrase in string form
* * @return {Promise<Boolean}
* @return {Uint8Array} The unencrypted session key
*/ */
SymEncryptedSessionKey.prototype.decrypt = function(passphrase) { SymEncryptedSessionKey.prototype.decrypt = async function(passphrase) {
const algo = this.sessionKeyEncryptionAlgorithm !== null ? const algo = this.sessionKeyEncryptionAlgorithm !== null ?
this.sessionKeyEncryptionAlgorithm : this.sessionKeyEncryptionAlgorithm :
this.sessionKeyAlgorithm; this.sessionKeyAlgorithm;
@ -122,26 +122,36 @@ SymEncryptedSessionKey.prototype.decrypt = function(passphrase) {
this.sessionKeyAlgorithm = enums.read(enums.symmetric, decrypted[0]); this.sessionKeyAlgorithm = enums.read(enums.symmetric, decrypted[0]);
this.sessionKey = decrypted.subarray(1, decrypted.length); this.sessionKey = decrypted.subarray(1, decrypted.length);
} }
return true;
}; };
SymEncryptedSessionKey.prototype.encrypt = function(passphrase) { /**
* Encrypts the session key
* @param {String} passphrase The passphrase in string form
* @return {Promise<Boolean}
*/
SymEncryptedSessionKey.prototype.encrypt = async function(passphrase) {
const algo = this.sessionKeyEncryptionAlgorithm !== null ? const algo = this.sessionKeyEncryptionAlgorithm !== null ?
this.sessionKeyEncryptionAlgorithm : this.sessionKeyEncryptionAlgorithm :
this.sessionKeyAlgorithm; this.sessionKeyAlgorithm;
this.sessionKeyEncryptionAlgorithm = algo; this.sessionKeyEncryptionAlgorithm = algo;
this.s2k = new type_s2k();
this.s2k.salt = await crypto.random.getRandomBytes(8);
const length = crypto.cipher[algo].keySize; const length = crypto.cipher[algo].keySize;
const key = this.s2k.produce_key(passphrase, length); const key = this.s2k.produce_key(passphrase, length);
const algo_enum = new Uint8Array([enums.write(enums.symmetric, this.sessionKeyAlgorithm)]); const algo_enum = new Uint8Array([enums.write(enums.symmetric, this.sessionKeyAlgorithm)]);
if (this.sessionKey === null) { if (this.sessionKey === null) {
this.sessionKey = crypto.getRandomBytes(crypto.cipher[this.sessionKeyAlgorithm].keySize); this.sessionKey = await crypto.generateSessionKey(this.sessionKeyAlgorithm);
} }
const private_key = util.concatUint8Array([algo_enum, this.sessionKey]); const private_key = util.concatUint8Array([algo_enum, this.sessionKey]);
this.encrypted = crypto.cfb.normalEncrypt(algo, key, private_key, null); this.encrypted = crypto.cfb.normalEncrypt(algo, key, private_key, null);
return true;
}; };
/** /**

View File

@ -54,15 +54,13 @@ SymmetricallyEncrypted.prototype.write = function () {
}; };
/** /**
* Symmetrically decrypt the packet data * Decrypt the symmetrically-encrypted packet data
*
* @param {module:enums.symmetric} sessionKeyAlgorithm * @param {module:enums.symmetric} sessionKeyAlgorithm
* Symmetric key algorithm to use // See {@link https://tools.ietf.org/html/rfc4880#section-9.2|RFC4880 9.2} * Symmetric key algorithm to use // See {@link https://tools.ietf.org/html/rfc4880#section-9.2|RFC4880 9.2}
* @param {String} key * @param {Uint8Array} key The key of cipher blocksize length to be used
* Key as string with the corresponding length to the * @returns {Promise<Boolean>}
* algorithm
*/ */
SymmetricallyEncrypted.prototype.decrypt = function (sessionKeyAlgorithm, key) { SymmetricallyEncrypted.prototype.decrypt = async function (sessionKeyAlgorithm, key) {
const decrypted = crypto.cfb.decrypt(sessionKeyAlgorithm, key, this.encrypted, true); const decrypted = crypto.cfb.decrypt(sessionKeyAlgorithm, key, this.encrypted, true);
// for modern cipher (blocklength != 64 bit, except for Twofish) MDC is required // for modern cipher (blocklength != 64 bit, except for Twofish) MDC is required
if (!this.ignore_mdc_error && if (!this.ignore_mdc_error &&
@ -73,13 +71,20 @@ SymmetricallyEncrypted.prototype.decrypt = function (sessionKeyAlgorithm, key) {
} }
this.packets.read(decrypted); this.packets.read(decrypted);
return Promise.resolve(); return true;
}; };
SymmetricallyEncrypted.prototype.encrypt = function (algo, key) { /**
* Encrypt the symmetrically-encrypted packet data
* @param {module:enums.symmetric} sessionKeyAlgorithm
* Symmetric key algorithm to use // See {@link https://tools.ietf.org/html/rfc4880#section-9.2|RFC4880 9.2}
* @param {Uint8Array} key The key of cipher blocksize length to be used
* @returns {Promise<Boolean>}
*/
SymmetricallyEncrypted.prototype.encrypt = async function (algo, key) {
const data = this.packets.write(); const data = this.packets.write();
this.encrypted = crypto.cfb.encrypt(crypto.getPrefixRandom(algo), algo, data, key, true); this.encrypted = crypto.cfb.encrypt(await crypto.getPrefixRandom(algo), algo, data, key, true);
return Promise.resolve(); return true;
}; };

View File

@ -46,7 +46,7 @@ export default function S2K() {
/** Eight bytes of salt in a binary string. /** Eight bytes of salt in a binary string.
* @type {String} * @type {String}
*/ */
this.salt = crypto.random.getRandomBytes(8); this.salt = null;
} }
S2K.prototype.get_count = function () { S2K.prototype.get_count = function () {

View File

@ -19,28 +19,68 @@ import util from '../util.js';
import crypto from '../crypto'; import crypto from '../crypto';
import packet from '../packet'; import packet from '../packet';
const INITIAL_RANDOM_SEED = 50000; // random bytes seeded to worker /**
const RANDOM_SEED_REQUEST = 20000; // random bytes seeded after worker request * Message handling
*/
function handleMessage(workerId) {
return function(event) {
const msg = event.data;
switch (msg.event) {
case 'method-return':
if (msg.err) {
// fail
const err = new Error(msg.err);
// add worker stack
err.workerStack = msg.stack;
this.tasks[msg.id].reject(err);
} else {
// success
this.tasks[msg.id].resolve(msg.data);
}
delete this.tasks[msg.id];
this.workers[workerId].requests--;
break;
case 'request-seed':
this.seedRandom(workerId, msg.amount);
break;
default:
throw new Error('Unknown Worker Event.');
}
};
}
/** /**
* Initializes a new proxy and loads the web worker * Initializes a new proxy and loads the web worker
* @constructor * @constructor
* @param {String} path The path to the worker or 'openpgp.worker.js' by default * @param {String} path The path to the worker or 'openpgp.worker.js' by default
* @param {Object} config config The worker configuration * @param {Number} n number of workers to initialize if path given
* @param {Object} worker alternative to path parameter: web worker initialized with 'openpgp.worker.js' * @param {Object} config config The worker configuration
* @return {Promise} * @param {Array<Object>} worker alternative to path parameter: web worker initialized with 'openpgp.worker.js'
*/ */
export default function AsyncProxy({ path='openpgp.worker.js', worker, config } = {}) { export default function AsyncProxy({ path='openpgp.worker.js', n = 1, workers = [], config } = {}) {
this.worker = worker || new Worker(path);
this.worker.onmessage = this.onMessage.bind(this);
this.worker.onerror = e => {
throw new Error('Unhandled error in openpgp worker: ' + e.message + ' (' + e.filename + ':' + e.lineno + ')');
};
this.seedRandom(INITIAL_RANDOM_SEED);
if (config) { if (workers.length) {
this.worker.postMessage({ event:'configure', config }); this.workers = workers;
} }
else {
this.workers = [];
while (this.workers.length < n) {
this.workers.push(new Worker(path));
}
}
let workerId = 0;
this.workers.forEach(worker => {
worker.requests = 0;
worker.onmessage = handleMessage(workerId++).bind(this);
worker.onerror = e => {
throw new Error('Unhandled error in openpgp worker: ' + e.message + ' (' + e.filename + ':' + e.lineno + ')');
};
if (config) {
worker.postMessage({ event:'configure', config });
}
});
// Cannot rely on task order being maintained, use object keyed by request ID to track tasks // Cannot rely on task order being maintained, use object keyed by request ID to track tasks
this.tasks = {}; this.tasks = {};
@ -55,61 +95,22 @@ AsyncProxy.prototype.getID = function() {
return this.currentID++; return this.currentID++;
}; };
/**
* Message handling
*/
AsyncProxy.prototype.onMessage = function(event) {
const msg = event.data;
switch (msg.event) {
case 'method-return':
if (msg.err) {
// fail
const err = new Error(msg.err);
// add worker stack
err.workerStack = msg.stack;
this.tasks[msg.id].reject(err);
} else {
// success
this.tasks[msg.id].resolve(msg.data);
}
delete this.tasks[msg.id];
break;
case 'request-seed':
this.seedRandom(RANDOM_SEED_REQUEST);
break;
default:
throw new Error('Unknown Worker Event.');
}
};
/** /**
* Send message to worker with random data * Send message to worker with random data
* @param {Integer} size Number of bytes to send * @param {Integer} size Number of bytes to send
*/ */
AsyncProxy.prototype.seedRandom = function(size) { AsyncProxy.prototype.seedRandom = async function(workerId, size) {
const buf = this.getRandomBuffer(size); const buf = await crypto.random.getRandomBytes(size);
this.worker.postMessage({ event:'seed-random', buf }, util.getTransferables(buf)); this.workers[workerId].postMessage({ event:'seed-random', buf }, util.getTransferables(buf));
}; };
/** /**
* Get Uint8Array with random numbers * Terminates the workers
* @param {Integer} size Length of buffer
* @return {Uint8Array}
*/
AsyncProxy.prototype.getRandomBuffer = function(size) {
if (!size) {
return null;
}
const buf = new Uint8Array(size);
crypto.random.getRandomValues(buf);
return buf;
};
/**
* Terminates the worker
*/ */
AsyncProxy.prototype.terminate = function() { AsyncProxy.prototype.terminate = function() {
this.worker.terminate(); this.workers.forEach(worker => {
worker.terminate();
});
}; };
/** /**
@ -119,11 +120,21 @@ AsyncProxy.prototype.terminate = function() {
* @return {Promise} see the corresponding public api functions for their return types * @return {Promise} see the corresponding public api functions for their return types
*/ */
AsyncProxy.prototype.delegate = function(method, options) { AsyncProxy.prototype.delegate = function(method, options) {
const id = this.getID(); const id = this.getID();
const requests = this.workers.map(worker => worker.requests);
const minRequests = Math.min(requests);
let workerId = 0;
for(; workerId < this.workers.length; workerId++) {
if (this.workers[workerId].requests === minRequests) {
break;
}
}
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// clone packets (for web worker structured cloning algorithm) // clone packets (for web worker structured cloning algorithm)
this.worker.postMessage({ id:id, event:method, options:packet.clone.clonePackets(options) }, util.getTransferables(options)); this.workers[workerId].postMessage({ id:id, event:method, options:packet.clone.clonePackets(options) }, util.getTransferables(options));
this.workers[workerId].requests++;
// remember to handle parsing cloned packets from worker // remember to handle parsing cloned packets from worker
this.tasks[id] = { resolve: data => resolve(packet.clone.parseClonedPackets(data, method)), reject }; this.tasks[id] = { resolve: data => resolve(packet.clone.parseClonedPackets(data, method)), reject };

View File

@ -24,10 +24,29 @@ self.window = {}; // to make UMD bundles work
importScripts('openpgp.js'); importScripts('openpgp.js');
var openpgp = window.openpgp; var openpgp = window.openpgp;
var randomQueue = [];
var randomRequested = false;
var MIN_SIZE_RANDOM_BUFFER = 40000; var MIN_SIZE_RANDOM_BUFFER = 40000;
var MAX_SIZE_RANDOM_BUFFER = 60000; var MAX_SIZE_RANDOM_BUFFER = 60000;
var MIN_SIZE_RANDOM_REQUEST = 20000;
openpgp.crypto.random.randomBuffer.init(MAX_SIZE_RANDOM_BUFFER); /**
* Handle random buffer exhaustion by requesting more random bytes from the main window
* @return {Promise<Object>} Empty promise whose resolution indicates that the buffer has been refilled
*/
function randomCallback() {
if (!randomRequested) {
self.postMessage({ event: 'request-seed', amount: MAX_SIZE_RANDOM_BUFFER });
}
randomRequested = true;
return new Promise(function(resolve, reject) {
randomQueue.push(resolve);
});
}
openpgp.crypto.random.randomBuffer.init(MAX_SIZE_RANDOM_BUFFER, randomCallback);
/** /**
* Handle messages from the main window. * Handle messages from the main window.
@ -43,6 +62,13 @@ self.onmessage = function(event) {
case 'seed-random': case 'seed-random':
seedRandom(msg.buf); seedRandom(msg.buf);
var queueCopy = randomQueue;
randomQueue = [];
for (var i = 0; i < queueCopy.length; i++) {
queueCopy[i]();
}
break; break;
default: default:
@ -99,8 +125,8 @@ function delegate(id, method, options) {
* @param {Object} event Contains event type and data * @param {Object} event Contains event type and data
*/ */
function response(event) { function response(event) {
if (openpgp.crypto.random.randomBuffer.size < MIN_SIZE_RANDOM_BUFFER) { if (!randomRequested && openpgp.crypto.random.randomBuffer.size < MIN_SIZE_RANDOM_BUFFER) {
self.postMessage({ event: 'request-seed' }); self.postMessage({ event: 'request-seed', amount: MIN_SIZE_RANDOM_REQUEST });
} }
self.postMessage(event, openpgp.util.getTransferables(event.data)); self.postMessage(event, openpgp.util.getTransferables(event.data));
} }

View File

@ -278,19 +278,19 @@ describe('API functional testing', function() {
}); });
function testCFB(plaintext, resync) { function testCFB(plaintext, resync) {
symmAlgos.forEach(function(algo) { symmAlgos.forEach(async function(algo) {
const symmKey = crypto.generateSessionKey(algo); const symmKey = await crypto.generateSessionKey(algo);
const symmencData = crypto.cfb.encrypt(crypto.getPrefixRandom(algo), algo, util.str_to_Uint8Array(plaintext), symmKey, resync); const symmencData = crypto.cfb.encrypt(await crypto.getPrefixRandom(algo), algo, util.str_to_Uint8Array(plaintext), symmKey, resync);
const text = util.Uint8Array_to_str(crypto.cfb.decrypt(algo, symmKey, symmencData, resync)); const text = util.Uint8Array_to_str(crypto.cfb.decrypt(algo, symmKey, symmencData, resync));
expect(text).to.equal(plaintext); expect(text).to.equal(plaintext);
}); });
} }
function testAESCFB(plaintext) { function testAESCFB(plaintext) {
symmAlgos.forEach(function(algo) { symmAlgos.forEach(async function(algo) {
if(algo.substr(0,3) === 'aes') { if(algo.substr(0,3) === 'aes') {
const symmKey = crypto.generateSessionKey(algo); const symmKey = await crypto.generateSessionKey(algo);
const rndm = crypto.getPrefixRandom(algo); const rndm = await crypto.getPrefixRandom(algo);
const repeat = new Uint8Array([rndm[rndm.length - 2], rndm[rndm.length - 1]]); const repeat = new Uint8Array([rndm[rndm.length - 2], rndm[rndm.length - 1]]);
const prefix = util.concatUint8Array([rndm, repeat]); const prefix = util.concatUint8Array([rndm, repeat]);
@ -307,9 +307,9 @@ describe('API functional testing', function() {
function testAESGCM(plaintext) { function testAESGCM(plaintext) {
symmAlgos.forEach(function(algo) { symmAlgos.forEach(function(algo) {
if(algo.substr(0,3) === 'aes') { if(algo.substr(0,3) === 'aes') {
it(algo, function() { it(algo, async function() {
const key = crypto.generateSessionKey(algo); const key = await crypto.generateSessionKey(algo);
const iv = crypto.random.getRandomValues(new Uint8Array(crypto.gcm.ivLength)); const iv = await crypto.random.getRandomBytes(crypto.gcm.ivLength);
return crypto.gcm.encrypt( return crypto.gcm.encrypt(
algo, util.str_to_Uint8Array(plaintext), key, iv algo, util.str_to_Uint8Array(plaintext), key, iv
@ -373,11 +373,10 @@ describe('API functional testing', function() {
it('Asymmetric using RSA with eme_pkcs1 padding', function () { it('Asymmetric using RSA with eme_pkcs1 padding', function () {
const symmKey = util.Uint8Array_to_str(crypto.generateSessionKey('aes256')); const symmKey = util.Uint8Array_to_str(crypto.generateSessionKey('aes256'));
const RSAUnencryptedData = crypto.pkcs1.eme.encode(symmKey, RSApubMPIs[0].byteLength()) return crypto.pkcs1.eme.encode(symmKey, RSApubMPIs[0].byteLength()).then(RSAUnencryptedData => {
const RSAUnencryptedMPI = new openpgp.MPI(RSAUnencryptedData); const RSAUnencryptedMPI = new openpgp.MPI(RSAUnencryptedData);
return crypto.publicKeyEncrypt( return crypto.publicKeyEncrypt(1, RSApubMPIs, RSAUnencryptedMPI);
1, RSApubMPIs, RSAUnencryptedMPI }).then(RSAEncryptedData => {
).then(RSAEncryptedData => {
return crypto.publicKeyDecrypt( return crypto.publicKeyDecrypt(
1, RSApubMPIs.concat(RSAsecMPIs), RSAEncryptedData 1, RSApubMPIs.concat(RSAsecMPIs), RSAEncryptedData
@ -393,12 +392,10 @@ describe('API functional testing', function() {
it('Asymmetric using Elgamal with eme_pkcs1 padding', function () { it('Asymmetric using Elgamal with eme_pkcs1 padding', function () {
const symmKey = util.Uint8Array_to_str(crypto.generateSessionKey('aes256')); const symmKey = util.Uint8Array_to_str(crypto.generateSessionKey('aes256'));
const ElgamalUnencryptedData = crypto.pkcs1.eme.encode(symmKey, ElgamalpubMPIs[0].byteLength()); return crypto.pkcs1.eme.encode(symmKey, ElgamalpubMPIs[0].byteLength()).then(ElgamalUnencryptedData => {
const ElgamalUnencryptedMPI = new openpgp.MPI(ElgamalUnencryptedData); const ElgamalUnencryptedMPI = new openpgp.MPI(ElgamalUnencryptedData);
return crypto.publicKeyEncrypt(16, ElgamalpubMPIs, ElgamalUnencryptedMPI);
return crypto.publicKeyEncrypt( }).then(ElgamalEncryptedData => {
16, ElgamalpubMPIs, ElgamalUnencryptedMPI
).then(ElgamalEncryptedData => {
return crypto.publicKeyDecrypt( return crypto.publicKeyDecrypt(
16, ElgamalpubMPIs.concat(ElgamalsecMPIs), ElgamalEncryptedData 16, ElgamalpubMPIs.concat(ElgamalsecMPIs), ElgamalEncryptedData

View File

@ -12,9 +12,9 @@ describe('Random Buffer', function() {
expect(randomBuffer).to.exist; expect(randomBuffer).to.exist;
}); });
it('Throw error if not initialized', function () { it('Throw error if not initialized', async function () {
expect(randomBuffer.set.bind(randomBuffer)).to.throw('RandomBuffer is not initialized'); expect(randomBuffer.set.bind(randomBuffer)).to.throw('RandomBuffer is not initialized');
expect(randomBuffer.get.bind(randomBuffer)).to.throw('RandomBuffer is not initialized'); await expect(randomBuffer.get(new Uint8Array(1))).to.eventually.be.rejectedWith('RandomBuffer is not initialized');
}); });
it('Initialization', function () { it('Initialization', function () {
@ -56,13 +56,13 @@ describe('Random Buffer', function() {
expect(randomBuffer.size).to.equal(1); expect(randomBuffer.size).to.equal(1);
}); });
it('Get Method', function () { it('Get Method', async function () {
randomBuffer.init(5); randomBuffer.init(5);
let buf = new Uint8Array(5); let buf = new Uint8Array(5);
buf[0] = 1; buf[1] = 2; buf[2] = 5; buf[3] = 7; buf[4] = 8; buf[0] = 1; buf[1] = 2; buf[2] = 5; buf[3] = 7; buf[4] = 8;
randomBuffer.set(buf); randomBuffer.set(buf);
buf = new Uint32Array(2); buf = new Uint32Array(2);
expect(randomBuffer.get.bind(randomBuffer, buf)).to.throw('Invalid type: buf not an Uint8Array'); await expect(randomBuffer.get(buf)).to.eventually.be.rejectedWith('Invalid type: buf not an Uint8Array');
buf = new Uint8Array(2); buf = new Uint8Array(2);
randomBuffer.get(buf); randomBuffer.get(buf);
expect(equal(randomBuffer.buffer, [1,2,5,0,0])).to.be.true; expect(equal(randomBuffer.buffer, [1,2,5,0,0])).to.be.true;
@ -74,6 +74,6 @@ describe('Random Buffer', function() {
expect(buf).to.to.have.property('1', 2); expect(buf).to.to.have.property('1', 2);
expect(equal(randomBuffer.buffer, [1,0,0,0,0])).to.be.true; expect(equal(randomBuffer.buffer, [1,0,0,0,0])).to.be.true;
expect(randomBuffer.size).to.equal(1); expect(randomBuffer.size).to.equal(1);
expect(function() { randomBuffer.get(buf); }).to.throw('Random number buffer depleted'); await expect(randomBuffer.get(buf)).to.eventually.be.rejectedWith('Random number buffer depleted');
}); });
}); });

View File

@ -142,7 +142,7 @@ describe('Elliptic Curve Cryptography', function () {
data[name].pub_key = pub.keys[0]; data[name].pub_key = pub.keys[0];
return data[name].pub_key; return data[name].pub_key;
} }
function load_priv_key(name) { async function load_priv_key(name) {
if (data[name].priv_key) { if (data[name].priv_key) {
return data[name].priv_key; return data[name].priv_key;
} }
@ -151,7 +151,7 @@ describe('Elliptic Curve Cryptography', function () {
expect(pk.err).to.not.exist; expect(pk.err).to.not.exist;
expect(pk.keys).to.have.length(1); expect(pk.keys).to.have.length(1);
expect(pk.keys[0].primaryKey.getKeyId().toHex()).to.equal(data[name].id); expect(pk.keys[0].primaryKey.getKeyId().toHex()).to.equal(data[name].id);
expect(pk.keys[0].decrypt(data[name].pass)).to.be.true; expect(await pk.keys[0].decrypt(data[name].pass)).to.be.true;
data[name].priv_key = pk.keys[0]; data[name].priv_key = pk.keys[0];
return data[name].priv_key; return data[name].priv_key;
} }
@ -160,10 +160,10 @@ describe('Elliptic Curve Cryptography', function () {
load_pub_key('juliet'); load_pub_key('juliet');
done(); done();
}); });
it('Load private key', function (done) { it('Load private key', async function () {
load_priv_key('romeo'); await load_priv_key('romeo');
load_priv_key('juliet'); await load_priv_key('juliet');
done(); return true;
}); });
it('Verify clear signed message', function () { it('Verify clear signed message', function () {
const pub = load_pub_key('juliet'); const pub = load_pub_key('juliet');
@ -175,52 +175,45 @@ describe('Elliptic Curve Cryptography', function () {
expect(result.signatures[0].valid).to.be.true; expect(result.signatures[0].valid).to.be.true;
}); });
}); });
it('Sign message', function () { it('Sign message', async function () {
const romeo = load_priv_key('romeo'); const romeoPrivate = await load_priv_key('romeo');
return openpgp.sign({privateKeys: [romeo], data: data.romeo.message + "\n"}).then(function (signed) { const signed = await openpgp.sign({privateKeys: [romeoPrivate], data: data.romeo.message + "\n"});
const romeo = load_pub_key('romeo'); const romeoPublic = load_pub_key('romeo');
const msg = openpgp.cleartext.readArmored(signed.data); const msg = openpgp.cleartext.readArmored(signed.data);
return openpgp.verify({publicKeys: [romeo], message: msg}).then(function (result) { const result = await openpgp.verify({publicKeys: [romeoPublic], message: msg});
expect(result).to.exist;
expect(result.data.trim()).to.equal(data.romeo.message); expect(result).to.exist;
expect(result.signatures).to.have.length(1); expect(result.data.trim()).to.equal(data.romeo.message);
expect(result.signatures[0].valid).to.be.true; expect(result.signatures).to.have.length(1);
}); expect(result.signatures[0].valid).to.be.true;
});
}); });
it('Decrypt and verify message', function () { it('Decrypt and verify message', async function () {
const juliet = load_pub_key('juliet'); const juliet = load_pub_key('juliet');
const romeo = load_priv_key('romeo'); const romeo = await load_priv_key('romeo');
const msg = openpgp.message.readArmored(data.juliet.message_encrypted); const msg = openpgp.message.readArmored(data.juliet.message_encrypted);
return openpgp.decrypt( const result = await openpgp.decrypt({privateKeys: romeo, publicKeys: [juliet], message: msg});
{privateKeys: romeo, publicKeys: [juliet], message: msg}
).then(function (result) { expect(result).to.exist;
expect(result).to.exist; // trim required because https://github.com/openpgpjs/openpgpjs/issues/311
// trim required because https://github.com/openpgpjs/openpgpjs/issues/311 expect(result.data.trim()).to.equal(data.juliet.message);
expect(result.data.trim()).to.equal(data.juliet.message); expect(result.signatures).to.have.length(1);
expect(result.signatures).to.have.length(1); expect(result.signatures[0].valid).to.be.true;
expect(result.signatures[0].valid).to.be.true;
});
}); });
it('Encrypt and sign message', function () { it('Encrypt and sign message', async function () {
const romeo = load_priv_key('romeo'); const romeoPrivate = await load_priv_key('romeo');
const juliet = load_pub_key('juliet'); const julietPublic = load_pub_key('juliet');
expect(romeo.decrypt(data.romeo.pass)).to.be.true; expect(await romeoPrivate.decrypt(data.romeo.pass)).to.be.true;
return openpgp.encrypt( const encrypted = await openpgp.encrypt({publicKeys: [julietPublic], privateKeys: [romeoPrivate], data: data.romeo.message + "\n"});
{publicKeys: [juliet], privateKeys: [romeo], data: data.romeo.message + "\n"}
).then(function (encrypted) { const message = openpgp.message.readArmored(encrypted.data);
const message = openpgp.message.readArmored(encrypted.data); const romeoPublic = load_pub_key('romeo');
const romeo = load_pub_key('romeo'); const julietPrivate = await load_priv_key('juliet');
const juliet = load_priv_key('juliet'); const result = await openpgp.decrypt({privateKeys: julietPrivate, publicKeys: [romeoPublic], message: message});
return openpgp.decrypt(
{privateKeys: juliet, publicKeys: [romeo], message: message} expect(result).to.exist;
).then(function (result) { expect(result.data.trim()).to.equal(data.romeo.message);
expect(result).to.exist; expect(result.signatures).to.have.length(1);
expect(result.data.trim()).to.equal(data.romeo.message); expect(result.signatures[0].valid).to.be.true;
expect(result.signatures).to.have.length(1);
expect(result.signatures[0].valid).to.be.true;
});
});
}); });
it('Generate key', function () { it('Generate key', function () {
const options = { const options = {

View File

@ -1037,26 +1037,24 @@ describe('Key', function() {
}); });
}); });
it('Encrypt key with new passphrase', function() { it('Encrypt key with new passphrase', async function() {
const userId = 'test <a@b.com>'; const userId = 'test <a@b.com>';
const opt = {numBits: 512, userIds: userId, passphrase: 'passphrase'}; const opt = {numBits: 512, userIds: userId, passphrase: 'passphrase'};
if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys
return openpgp.generateKey(opt).then(function(key) { const key = (await openpgp.generateKey(opt)).key;
key = key.key; const armor1 = key.armor();
const armor1 = key.armor(); const armor2 = key.armor();
const armor2 = key.armor(); expect(armor1).to.equal(armor2);
expect(armor1).to.equal(armor2); expect(await key.decrypt('passphrase')).to.be.true;
expect(key.decrypt('passphrase')).to.be.true; expect(key.primaryKey.isDecrypted).to.be.true;
expect(key.primaryKey.isDecrypted).to.be.true; await key.encrypt('new_passphrase');
key.encrypt('new_passphrase'); expect(key.primaryKey.isDecrypted).to.be.false;
expect(key.primaryKey.isDecrypted).to.be.false; await expect(key.decrypt('passphrase')).to.eventually.be.rejectedWith('Incorrect key passphrase');
expect(key.decrypt('passphrase')).to.be.false; expect(key.primaryKey.isDecrypted).to.be.false;
expect(key.primaryKey.isDecrypted).to.be.false; expect(await key.decrypt('new_passphrase')).to.be.true;
expect(key.decrypt('new_passphrase')).to.be.true; expect(key.primaryKey.isDecrypted).to.be.true;
expect(key.primaryKey.isDecrypted).to.be.true; const armor3 = key.armor();
const armor3 = key.armor(); expect(armor3).to.not.equal(armor1);
expect(armor3).to.not.equal(armor1);
});
}); });
it('Generate key - ensure keyExpirationTime works', function() { it('Generate key - ensure keyExpirationTime works', function() {
@ -1268,7 +1266,7 @@ describe('Key', function() {
}); });
}); });
it('Throw user friendly error when reformatting encrypted key', function() { it('Reject with user-friendly error when reformatting encrypted key', function() {
const opt = {numBits: 512, userIds: 'test1 <a@b.com>', passphrase: '1234'}; const opt = {numBits: 512, userIds: 'test1 <a@b.com>', passphrase: '1234'};
if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys
return openpgp.generateKey(opt).then(function(original) { return openpgp.generateKey(opt).then(function(original) {

View File

@ -363,7 +363,7 @@ describe('OpenPGP.js public api tests', function() {
postMessage: function() {} postMessage: function() {}
}; };
openpgp.initWorker({ openpgp.initWorker({
worker: workerStub workers: [workerStub]
}); });
expect(openpgp.getWorker()).to.exist; expect(openpgp.getWorker()).to.exist;
openpgp.destroyWorker(); openpgp.destroyWorker();
@ -522,7 +522,7 @@ describe('OpenPGP.js public api tests', function() {
postMessage: function() {} postMessage: function() {}
}; };
openpgp.initWorker({ openpgp.initWorker({
worker: workerStub workers: [workerStub]
}); });
const proxyGenStub = stub(openpgp.getWorker(), 'delegate'); const proxyGenStub = stub(openpgp.getWorker(), 'delegate');
getWebCryptoAllStub.returns(); getWebCryptoAllStub.returns();
@ -635,12 +635,12 @@ describe('OpenPGP.js public api tests', function() {
openpgp.config.aead_protect = aead_protectVal; openpgp.config.aead_protect = aead_protectVal;
}); });
it('Decrypting key with wrong passphrase returns false', function () { it('Decrypting key with wrong passphrase rejected', function () {
expect(privateKey.keys[0].decrypt('wrong passphrase')).to.be.false; expect(privateKey.keys[0].decrypt('wrong passphrase')).to.eventually.be.rejectedWith('Incorrect key passphrase');
}); });
it('Decrypting key with correct passphrase returns true', function () { it('Decrypting key with correct passphrase returns true', async function () {
expect(privateKey.keys[0].decrypt(passphrase)).to.be.true; expect(await privateKey.keys[0].decrypt(passphrase)).to.be.true;
}); });
tryTests('CFB mode (asm.js)', tests, { tryTests('CFB mode (asm.js)', tests, {
@ -719,7 +719,7 @@ describe('OpenPGP.js public api tests', function() {
privateKey: privateKey.keys[0], privateKey: privateKey.keys[0],
passphrase: 'incorrect' passphrase: 'incorrect'
}).catch(function(error){ }).catch(function(error){
expect(error.message).to.match(/Invalid passphrase/); expect(error.message).to.match(/Incorrect key passphrase/);
}); });
}); });
}); });
@ -727,9 +727,10 @@ describe('OpenPGP.js public api tests', function() {
describe('encryptSessionKey, decryptSessionKeys', function() { describe('encryptSessionKey, decryptSessionKeys', function() {
const sk = new Uint8Array([0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01]); const sk = new Uint8Array([0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01]);
beforeEach(function(done) { beforeEach(async function() {
expect(privateKey.keys[0].decrypt(passphrase)).to.be.true; expect(await privateKey.keys[0].decrypt(passphrase)).to.be.true;
privateKey.keys[0].verifyPrimaryUser().then(() => done()); await privateKey.keys[0].verifyPrimaryUser();
return true;
}); });
it('should encrypt with public key', function() { it('should encrypt with public key', function() {
@ -867,33 +868,13 @@ describe('OpenPGP.js public api tests', function() {
'=6XMW\r\n' + '=6XMW\r\n' +
'-----END PGP PUBLIC KEY BLOCK-----\r\n\r\n'; '-----END PGP PUBLIC KEY BLOCK-----\r\n\r\n';
beforeEach(function (done) { beforeEach( async function () {
expect(privateKey.keys[0].decrypt(passphrase)).to.be.true; expect(await privateKey.keys[0].decrypt(passphrase)).to.be.true;
Promise.all([ await privateKey.keys[0].verifyPrimaryUser();
privateKey.keys[0].verifyPrimaryUser(), await privateKey_2000_2008.keys[0].verifyPrimaryUser();
privateKey_2000_2008.keys[0].verifyPrimaryUser(), await privateKey_1337.keys[0].verifyPrimaryUser();
privateKey_1337.keys[0].verifyPrimaryUser(), await privateKey_2038_2045.keys[0].verifyPrimaryUser();
privateKey_2038_2045.keys[0].verifyPrimaryUser() return true;
]).then(() => done());
});
it('should encrypt then decrypt', function () {
const encOpt = {
data: plaintext,
publicKeys: publicKey.keys
};
const decOpt = {
privateKeys: privateKey.keys
};
return openpgp.encrypt(encOpt).then(function (encrypted) {
expect(encrypted.data).to.match(/^-----BEGIN PGP MESSAGE/);
decOpt.message = openpgp.message.readArmored(encrypted.data);
return openpgp.decrypt(decOpt);
}).then(function (decrypted) {
expect(decrypted.data).to.equal(plaintext);
expect(decrypted.signatures).to.exist;
expect(decrypted.signatures.length).to.equal(0);
});
}); });
it('should encrypt then decrypt', function () { it('should encrypt then decrypt', function () {
@ -1001,9 +982,9 @@ describe('OpenPGP.js public api tests', function() {
}); });
}); });
it('should encrypt using custom session key and decrypt using session key', function () { it('should encrypt using custom session key and decrypt using session key', async function () {
const sessionKey = { const sessionKey = {
data: openpgp.crypto.generateSessionKey('aes256'), data: await openpgp.crypto.generateSessionKey('aes256'),
algorithm: 'aes256' algorithm: 'aes256'
}; };
const encOpt = { const encOpt = {

View File

@ -55,7 +55,7 @@ describe("Packet", function() {
'=KXkj\n' + '=KXkj\n' +
'-----END PGP PRIVATE KEY BLOCK-----'; '-----END PGP PRIVATE KEY BLOCK-----';
it('Symmetrically encrypted packet', function(done) { it('Symmetrically encrypted packet', async function() {
const message = new openpgp.packet.List(); const message = new openpgp.packet.List();
const literal = new openpgp.packet.Literal(); const literal = new openpgp.packet.Literal();
@ -68,18 +68,17 @@ describe("Packet", function() {
const key = new Uint8Array([1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2]); const key = new Uint8Array([1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2]);
const algo = 'aes256'; const algo = 'aes256';
enc.encrypt(algo, key); await enc.encrypt(algo, key);
const msg2 = new openpgp.packet.List(); const msg2 = new openpgp.packet.List();
msg2.read(message.write()); msg2.read(message.write());
msg2[0].ignore_mdc_error = true; msg2[0].ignore_mdc_error = true;
msg2[0].decrypt(algo, key); await msg2[0].decrypt(algo, key);
expect(stringify(msg2[0].packets[0].data)).to.equal(stringify(literal.data)); expect(stringify(msg2[0].packets[0].data)).to.equal(stringify(literal.data));
done();
}); });
it('Symmetrically encrypted packet - MDC error for modern cipher', function() { it('Symmetrically encrypted packet - MDC error for modern cipher', async function() {
const message = new openpgp.packet.List(); const message = new openpgp.packet.List();
const literal = new openpgp.packet.Literal(); const literal = new openpgp.packet.Literal();
@ -87,19 +86,19 @@ describe("Packet", function() {
const enc = new openpgp.packet.SymmetricallyEncrypted(); const enc = new openpgp.packet.SymmetricallyEncrypted();
message.push(enc); message.push(enc);
enc.packets.push(literal); await enc.packets.push(literal);
const key = '12345678901234567890123456789012'; const key = '12345678901234567890123456789012';
const algo = 'aes256'; const algo = 'aes256';
enc.encrypt(algo, key); await enc.encrypt(algo, key);
const msg2 = new openpgp.packet.List(); const msg2 = new openpgp.packet.List();
msg2.read(message.write()); msg2.read(message.write());
expect(msg2[0].decrypt.bind(msg2[0], algo, key)).to.throw('Decryption failed due to missing MDC in combination with modern cipher.'); expect(msg2[0].decrypt(algo, key)).to.eventually.be.rejectedWith('Decryption failed due to missing MDC in combination with modern cipher.');
}); });
it('Sym. encrypted integrity protected packet', function(done) { it('Sym. encrypted integrity protected packet', async function() {
const key = new Uint8Array([1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2]); const key = new Uint8Array([1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2]);
const algo = 'aes256'; const algo = 'aes256';
@ -110,15 +109,14 @@ describe("Packet", function() {
msg.push(enc); msg.push(enc);
literal.setText('Hello world!'); literal.setText('Hello world!');
enc.packets.push(literal); enc.packets.push(literal);
enc.encrypt(algo, key); await enc.encrypt(algo, key);
const msg2 = new openpgp.packet.List(); const msg2 = new openpgp.packet.List();
msg2.read(msg.write()); msg2.read(msg.write());
msg2[0].decrypt(algo, key); await msg2[0].decrypt(algo, key);
expect(stringify(msg2[0].packets[0].data)).to.equal(stringify(literal.data)); expect(stringify(msg2[0].packets[0].data)).to.equal(stringify(literal.data));
done();
}); });
it('Sym. encrypted AEAD protected packet', function() { it('Sym. encrypted AEAD protected packet', function() {
@ -310,7 +308,7 @@ describe("Packet", function() {
}); });
}); });
it('Sym. encrypted session key reading/writing', function(done) { it('Sym. encrypted session key reading/writing', async function() {
const passphrase = 'hello'; const passphrase = 'hello';
const algo = 'aes256'; const algo = 'aes256';
@ -323,23 +321,22 @@ describe("Packet", function() {
msg.push(enc); msg.push(enc);
key_enc.sessionKeyAlgorithm = algo; key_enc.sessionKeyAlgorithm = algo;
key_enc.decrypt(passphrase); await key_enc.encrypt(passphrase);
const key = key_enc.sessionKey; const key = key_enc.sessionKey;
literal.setText('Hello world!'); literal.setText('Hello world!');
enc.packets.push(literal); enc.packets.push(literal);
enc.encrypt(algo, key); await enc.encrypt(algo, key);
const msg2 = new openpgp.packet.List(); const msg2 = new openpgp.packet.List();
msg2.read(msg.write()); msg2.read(msg.write());
msg2[0].decrypt(passphrase); await msg2[0].decrypt(passphrase);
const key2 = msg2[0].sessionKey; const key2 = msg2[0].sessionKey;
msg2[1].decrypt(msg2[0].sessionKeyAlgorithm, key2); await msg2[1].decrypt(msg2[0].sessionKeyAlgorithm, key2);
expect(stringify(msg2[1].packets[0].data)).to.equal(stringify(literal.data)); expect(stringify(msg2[1].packets[0].data)).to.equal(stringify(literal.data));
done();
}); });
it('Secret key encryption/decryption test', function() { it('Secret key encryption/decryption test', function() {

View File

@ -127,7 +127,7 @@ describe('X25519 Cryptography', function () {
return data[name].pub_key; return data[name].pub_key;
} }
function load_priv_key(name) { async function load_priv_key(name) {
if (data[name].priv_key) { if (data[name].priv_key) {
return data[name].priv_key; return data[name].priv_key;
} }
@ -136,7 +136,7 @@ describe('X25519 Cryptography', function () {
expect(pk.err).to.not.exist; expect(pk.err).to.not.exist;
expect(pk.keys).to.have.length(1); expect(pk.keys).to.have.length(1);
expect(pk.keys[0].primaryKey.getKeyId().toHex()).to.equal(data[name].id); expect(pk.keys[0].primaryKey.getKeyId().toHex()).to.equal(data[name].id);
expect(pk.keys[0].decrypt(data[name].pass)).to.be.true; expect(await pk.keys[0].decrypt(data[name].pass)).to.be.true;
data[name].priv_key = pk.keys[0]; data[name].priv_key = pk.keys[0];
return data[name].priv_key; return data[name].priv_key;
} }
@ -149,10 +149,10 @@ describe('X25519 Cryptography', function () {
// This test is slow because the keys are generated by GPG2, which // This test is slow because the keys are generated by GPG2, which
// by default chooses a larger number for S2K iterations than we do. // by default chooses a larger number for S2K iterations than we do.
it('Load private key', function (done) { it('Load private key', async function () {
load_priv_key('light'); await load_priv_key('light');
load_priv_key('night'); await load_priv_key('night');
done(); return true;
}); });
it('Verify clear signed message', function () { it('Verify clear signed message', function () {
@ -167,56 +167,49 @@ describe('X25519 Cryptography', function () {
}); });
}); });
it('Sign message', function () { it('Sign message', async function () {
const name = 'light'; const name = 'light';
const priv = load_priv_key(name); const priv = await load_priv_key(name);
return openpgp.sign({ privateKeys: [priv], data: data[name].message + "\n" }).then(function (signed) { const signed = await openpgp.sign({ privateKeys: [priv], data: data[name].message + "\n" });
const pub = load_pub_key(name); const pub = load_pub_key(name);
const msg = openpgp.cleartext.readArmored(signed.data); const msg = openpgp.cleartext.readArmored(signed.data);
return openpgp.verify({ publicKeys: [pub], message: msg}).then(function (result) { const result = await openpgp.verify({ publicKeys: [pub], message: msg});
expect(result).to.exist;
expect(result.data.trim()).to.equal(data[name].message); expect(result).to.exist;
expect(result.signatures).to.have.length(1); expect(result.data.trim()).to.equal(data[name].message);
expect(result.signatures[0].valid).to.be.true; expect(result.signatures).to.have.length(1);
}); expect(result.signatures[0].valid).to.be.true;
});
}); });
it('Decrypt and verify message', function () { it('Decrypt and verify message', async function () {
const light = load_pub_key('light'); const light = load_pub_key('light');
const night = load_priv_key('night'); const night = await load_priv_key('night');
expect(night.decrypt(data.night.pass)).to.be.true; expect(await night.decrypt(data.night.pass)).to.be.true;
const msg = openpgp.message.readArmored(data.night.message_encrypted); const msg = openpgp.message.readArmored(data.night.message_encrypted);
return openpgp.decrypt( const result = await openpgp.decrypt({ privateKeys: night, publicKeys: [light], message: msg });
{ privateKeys: night, publicKeys: [light], message: msg }
).then(function (result) { expect(result).to.exist;
expect(result).to.exist; // trim required because https://github.com/openpgpjs/openpgpjs/issues/311
// trim required because https://github.com/openpgpjs/openpgpjs/issues/311 expect(result.data.trim()).to.equal(data.night.message);
expect(result.data.trim()).to.equal(data.night.message); expect(result.signatures).to.have.length(1);
expect(result.signatures).to.have.length(1); expect(result.signatures[0].valid).to.be.true;
expect(result.signatures[0].valid).to.be.true;
});
}); });
it('Encrypt and sign message', function () { it('Encrypt and sign message', async function () {
const night = load_pub_key('night'); const nightPublic = load_pub_key('night');
const light = load_priv_key('light'); const lightPrivate = await load_priv_key('light');
expect(light.decrypt(data.light.pass)).to.be.true; expect(await lightPrivate.decrypt(data.light.pass)).to.be.true;
openpgp.encrypt( const encrypted = await openpgp.encrypt({ publicKeys: [nightPublic], privateKeys: [lightPrivate], data: data.light.message + "\n" });
{ publicKeys: [night], privateKeys: [light], data: data.light.message + "\n" }
).then(function (encrypted) { const message = openpgp.message.readArmored(encrypted.data);
const message = openpgp.message.readArmored(encrypted.data); const lightPublic = load_pub_key('light');
const light = load_pub_key('light'); const nightPrivate = await load_priv_key('night');
const night = load_priv_key('night'); const result = await openpgp.decrypt({ privateKeys: nightPrivate, publicKeys: [lightPublic], message: message });
return openpgp.decrypt(
{ privateKeys: night, publicKeys: [light], message: message } expect(result).to.exist;
).then(function (result) { expect(result.data.trim()).to.equal(data.light.message);
expect(result).to.exist; expect(result.signatures).to.have.length(1);
expect(result.data.trim()).to.equal(data.light.message); expect(result.signatures[0].valid).to.be.true;
expect(result.signatures).to.have.length(1);
expect(result.signatures[0].valid).to.be.true;
});
});
}); });
// TODO export, then reimport key and validate // TODO export, then reimport key and validate

View File

@ -47,15 +47,12 @@ tryTests('Async Proxy', tests, {
function tests() { function tests() {
describe('Error handling', function() { describe('Random number pipeline', function() {
it('Depleted random buffer in worker gives error', function() { it('Random number buffer automatically reseeded', function() {
const wProxy = new openpgp.AsyncProxy({ path:'../dist/openpgp.worker.js' }); const worker = new Worker('../dist/openpgp.worker.js');
wProxy.worker = new Worker('../dist/openpgp.worker.js'); const wProxy = new openpgp.AsyncProxy({ path:'../dist/openpgp.worker.js', workers: [worker] });
wProxy.worker.onmessage = wProxy.onMessage.bind(wProxy);
wProxy.seedRandom(10); return wProxy.delegate('encrypt', { publicKeys:[pubKey], data:plaintext });
return wProxy.delegate('encrypt', { publicKeys:[pubKey], data:plaintext }).catch(function(err) {
expect(err.message).to.match(/Random number buffer depleted/);
});
}); });
}); });