From b088f005daa10ef19a6535eb4514f7bd86cdc7c2 Mon Sep 17 00:00:00 2001 From: Bart Butler Date: Mon, 5 Mar 2018 12:18:04 -0800 Subject: [PATCH 1/6] simplify random.js --- src/crypto/pkcs1.js | 2 +- src/crypto/random.js | 48 ++-------------------- src/packet/sym_encrypted_aead_protected.js | 2 +- src/worker/async_proxy.js | 19 +-------- src/worker/worker.js | 32 +++++++++++++-- test/crypto/crypto.js | 2 +- 6 files changed, 38 insertions(+), 67 deletions(-) diff --git a/src/crypto/pkcs1.js b/src/crypto/pkcs1.js index 38dc0671..71719651 100644 --- a/src/crypto/pkcs1.js +++ b/src/crypto/pkcs1.js @@ -54,7 +54,7 @@ function getPkcs1Padding(length) { let result = ''; let randomByte; while (result.length < length) { - randomByte = random.getSecureRandomOctet(); + randomByte = random.getRandomBytes(1)[0]; if (randomByte !== 0) { result += String.fromCharCode(randomByte); } diff --git a/src/crypto/random.js b/src/crypto/random.js index 320c70dd..e00af509 100644 --- a/src/crypto/random.js +++ b/src/crypto/random.js @@ -38,49 +38,7 @@ export default { * @return {Uint8Array} Random byte array */ getRandomBytes: function(length) { - const result = 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'); - } + const buf = new Uint8Array(length); if (typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues) { window.crypto.getRandomValues(buf); } else if (typeof window !== 'undefined' && typeof window.msCrypto === 'object' && typeof window.msCrypto.getRandomValues === 'function') { @@ -126,15 +84,17 @@ export default { function RandomBuffer() { this.buffer = null; this.size = null; + this.callback = null; } /** * Initialize buffer * @param {Integer} size size of buffer */ -RandomBuffer.prototype.init = function(size) { +RandomBuffer.prototype.init = function(size, callback) { this.buffer = new Uint8Array(size); this.size = 0; + this.callback = callback; }; /** diff --git a/src/packet/sym_encrypted_aead_protected.js b/src/packet/sym_encrypted_aead_protected.js index 631ea56a..0557883c 100644 --- a/src/packet/sym_encrypted_aead_protected.js +++ b/src/packet/sym_encrypted_aead_protected.js @@ -79,7 +79,7 @@ SymEncryptedAEADProtected.prototype.decrypt = function (sessionKeyAlgorithm, key * @return {Promise} Nothing is returned */ SymEncryptedAEADProtected.prototype.encrypt = function (sessionKeyAlgorithm, key) { - this.iv = crypto.random.getRandomValues(new Uint8Array(IV_LEN)); // generate new random IV + this.iv = crypto.random.getRandomBytes(IV_LEN); // generate new random IV return crypto.gcm.encrypt(sessionKeyAlgorithm, this.packets.write(), key, this.iv).then(encrypted => { this.encrypted = encrypted; }); diff --git a/src/worker/async_proxy.js b/src/worker/async_proxy.js index 4e68e25c..3e42cd42 100644 --- a/src/worker/async_proxy.js +++ b/src/worker/async_proxy.js @@ -20,7 +20,6 @@ import crypto from '../crypto'; 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 /** * Initializes a new proxy and loads the web worker @@ -75,7 +74,7 @@ AsyncProxy.prototype.onMessage = function(event) { delete this.tasks[msg.id]; break; case 'request-seed': - this.seedRandom(RANDOM_SEED_REQUEST); + this.seedRandom(msg.amount); break; default: throw new Error('Unknown Worker Event.'); @@ -87,24 +86,10 @@ AsyncProxy.prototype.onMessage = function(event) { * @param {Integer} size Number of bytes to send */ AsyncProxy.prototype.seedRandom = function(size) { - const buf = this.getRandomBuffer(size); + const buf = crypto.random.getRandomBytes(size); this.worker.postMessage({ event:'seed-random', buf }, util.getTransferables(buf)); }; -/** - * Get Uint8Array with random numbers - * @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 */ diff --git a/src/worker/worker.js b/src/worker/worker.js index b0354aac..7b934376 100644 --- a/src/worker/worker.js +++ b/src/worker/worker.js @@ -24,10 +24,29 @@ self.window = {}; // to make UMD bundles work importScripts('openpgp.js'); var openpgp = window.openpgp; +var randomQueue = []; +var randomRequested = false; var MIN_SIZE_RANDOM_BUFFER = 40000; 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} 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. @@ -43,6 +62,13 @@ self.onmessage = function(event) { case 'seed-random': seedRandom(msg.buf); + + var queueCopy = randomQueue; + randomQueue = []; + for (var i = 0; i < queueCopy.length; i++) { + queueCopy[i](); + } + break; default: @@ -99,8 +125,8 @@ function delegate(id, method, options) { * @param {Object} event Contains event type and data */ function response(event) { - if (openpgp.crypto.random.randomBuffer.size < MIN_SIZE_RANDOM_BUFFER) { - self.postMessage({ event: 'request-seed' }); + if (!randomRequested && openpgp.crypto.random.randomBuffer.size < MIN_SIZE_RANDOM_BUFFER) { + self.postMessage({ event: 'request-seed', amount: MIN_SIZE_RANDOM_REQUEST }); } self.postMessage(event, openpgp.util.getTransferables(event.data)); } diff --git a/test/crypto/crypto.js b/test/crypto/crypto.js index 0a59504d..fde07520 100644 --- a/test/crypto/crypto.js +++ b/test/crypto/crypto.js @@ -309,7 +309,7 @@ describe('API functional testing', function() { if(algo.substr(0,3) === 'aes') { it(algo, function() { const key = crypto.generateSessionKey(algo); - const iv = crypto.random.getRandomValues(new Uint8Array(crypto.gcm.ivLength)); + const iv = crypto.random.getRandomBytes(crypto.gcm.ivLength); return crypto.gcm.encrypt( algo, util.str_to_Uint8Array(plaintext), key, iv From f57888fe554645be530370b22317cdaaeaccf753 Mon Sep 17 00:00:00 2001 From: Bart Butler Date: Mon, 5 Mar 2018 16:31:56 -0800 Subject: [PATCH 2/6] change all calls of getRandomBytes and getRandomBN to be async --- Gruntfile.js | 3 + src/crypto/pkcs1.js | 29 +-- src/crypto/public_key/dsa.js | 7 +- src/crypto/public_key/elgamal.js | 6 +- src/crypto/public_key/elliptic/curves.js | 2 +- src/crypto/public_key/prime.js | 18 +- src/crypto/public_key/rsa.js | 14 +- src/crypto/signature.js | 6 +- src/key.js | 91 ++++---- src/message.js | 2 +- src/openpgp.js | 20 ++ .../public_key_encrypted_session_key.js | 2 +- src/packet/secret_key.js | 12 +- src/packet/sym_encrypted_aead_protected.js | 14 +- .../sym_encrypted_integrity_protected.js | 8 +- src/packet/sym_encrypted_session_key.js | 12 +- src/packet/symmetrically_encrypted.js | 6 +- src/type/s2k.js | 2 +- src/worker/async_proxy.js | 4 +- test/crypto/crypto.js | 19 +- test/general/ecc_nist.js | 89 ++++--- test/general/key.js | 34 ++- test/general/openpgp.js | 32 +-- test/general/openpgp2.js | 219 ++++++++++++++++++ test/general/packet.js | 33 ++- test/general/x25519.js | 91 ++++---- 26 files changed, 498 insertions(+), 277 deletions(-) create mode 100644 test/general/openpgp2.js diff --git a/Gruntfile.js b/Gruntfile.js index 0a7505b5..6e61f5de 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -107,9 +107,12 @@ module.exports = function(grunt) { transform: [ ["babelify", { global: true, + // Only babelify chai-as-promised in node_modules + only: /^(?:.*\/node_modules\/chai-as-promised\/|(?!.*\/node_modules\/)).*$/, plugins: ["transform-async-to-generator", "syntax-async-functions", "transform-regenerator", + "transform-runtime", "transform-remove-strict-mode"], ignore: ['*.min.js'], presets: ["env"] diff --git a/src/crypto/pkcs1.js b/src/crypto/pkcs1.js index 71719651..c0cc29cf 100644 --- a/src/crypto/pkcs1.js +++ b/src/crypto/pkcs1.js @@ -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 * @return {String} Padding as string */ -function getPkcs1Padding(length) { +async function getPkcs1Padding(length) { let result = ''; - let randomByte; while (result.length < length) { - randomByte = random.getRandomBytes(1)[0]; - if (randomByte !== 0) { - result += String.fromCharCode(randomByte); + // eslint-disable-next-line no-await-in-loop + const randomBytes = await random.getRandomBytes(length - result.length); + for (let i = 0; i < randomBytes.length; i++) { + if (randomBytes[i] !== 0) { + result += String.fromCharCode(randomBytes[i]); + } } } 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}) * @param {String} M message to be encoded * @param {Integer} k the length in octets of the key modulus - * @return {String} EME-PKCS1 padded message + * @return {Promise} EME-PKCS1 padded message */ - encode: function(M, k) { + encode: async function(M, k) { const mLen = M.length; // length checking if (mLen > k - 11) { @@ -79,15 +81,14 @@ export default { } // Generate an octet string PS of length k - mLen - 3 consisting of // 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 // encoded message EM of length k octets as EM = 0x00 || 0x02 || PS || 0x00 || M. - const EM = String.fromCharCode(0) + - String.fromCharCode(2) + - PS + - String.fromCharCode(0) + - M; - return EM; + return String.fromCharCode(0) + + String.fromCharCode(2) + + PS + + String.fromCharCode(0) + + M; }, /** * decodes a EME-PKCS1-v1_5 padding (See {@link https://tools.ietf.org/html/rfc4880#section-13.1.2|RFC 4880 13.1.2}) diff --git a/src/crypto/public_key/dsa.js b/src/crypto/public_key/dsa.js index ee1c5e16..f30fc9d8 100644 --- a/src/crypto/public_key/dsa.js +++ b/src/crypto/public_key/dsa.js @@ -48,7 +48,7 @@ export default { * g, p, q, x are all 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 r; let s; @@ -73,7 +73,8 @@ export default { // or s = 0 if signatures are generated properly. while (true) { // 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 if (zero.cmp(r) === 0) { continue; @@ -96,7 +97,7 @@ export default { * p, q, g, y are all 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 || zero.ucmp(s) >= 0 || s.ucmp(q) >= 0) { util.print_debug("invalid DSA Signature"); diff --git a/src/crypto/public_key/elgamal.js b/src/crypto/public_key/elgamal.js index b48402e6..678b0561 100644 --- a/src/crypto/public_key/elgamal.js +++ b/src/crypto/public_key/elgamal.js @@ -33,13 +33,13 @@ export default { * m, p, g, y are all 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 mred = m.toRed(redp); const gred = g.toRed(redp); const yred = y.toRed(redp); // 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 { c1: gred.redPow(k).fromRed(), c2: yred.redPow(k).redMul(mred).fromRed() @@ -50,7 +50,7 @@ export default { * c1, c2, p, x are all BN * returns BN */ - decrypt: function(c1, c2, p, x) { + decrypt: async function(c1, c2, p, x) { const redp = new BN.red(p); const c1red = c1.toRed(redp); const c2red = c2.toRed(redp); diff --git a/src/crypto/public_key/elliptic/curves.js b/src/crypto/public_key/elliptic/curves.js index cd1dd8ed..3508002c 100644 --- a/src/crypto/public_key/elliptic/curves.js +++ b/src/crypto/public_key/elliptic/curves.js @@ -178,7 +178,7 @@ Curve.prototype.genKeyPair = async function () { if (!keyPair || !keyPair.priv) { // elliptic fallback 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'; if (this.keyType === enums.publicKey.eddsa) { diff --git a/src/crypto/public_key/prime.js b/src/crypto/public_key/prime.js index 53339f1a..95f232c9 100644 --- a/src/crypto/public_key/prime.js +++ b/src/crypto/public_key/prime.js @@ -37,15 +37,16 @@ export default { * @param {Integer} k Optional number of iterations of Miller-Rabin test * @return BN */ -function randomProbablePrime(bits, e, k) { +async function randomProbablePrime(bits, e, k) { 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()) { 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); // If reached the maximum, go back to the minimum. if (n.bitLength() > bits) { @@ -62,17 +63,17 @@ function randomProbablePrime(bits, e, k) { * @param {Integer} k Optional number of iterations of Miller-Rabin test * @return {boolean} */ -function isProbablePrime(n, e, k) { +async function isProbablePrime(n, e, k) { if (e && !n.subn(1).gcd(e).eqn(1)) { return false; } if (!fermat(n)) { 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; } - if (!millerRabin(n, k)) { + if (!await millerRabin(n, k)) { return false; } // TODO implement the Lucas test @@ -138,7 +139,7 @@ const lowprimes = [ * @param {Function} rand Optional function to generate potential witnesses * @return {boolean} */ -function millerRabin(n, k, rand) { +async function millerRabin(n, k, rand) { const len = n.bitLength(); const red = BN.mont(n); const rone = new BN(1).toRed(red); @@ -155,7 +156,8 @@ function millerRabin(n, k, rand) { const d = n.shrn(s); 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); if (x.eq(rone) || x.eq(rn1)) diff --git a/src/crypto/public_key/rsa.js b/src/crypto/public_key/rsa.js index baae76ca..ac5c9e2f 100644 --- a/src/crypto/public_key/rsa.js +++ b/src/crypto/public_key/rsa.js @@ -55,7 +55,7 @@ export default { * @param d private MPI part as BN * @return BN */ - sign: function(m, n, e, d) { + sign: async function(m, n, e, d) { if (n.cmp(m) <= 0) { throw new Error('Data too large.'); } @@ -70,7 +70,7 @@ export default { * @param e public MPI part as BN * @return BN */ - verify: function(s, n, e) { + verify: async function(s, n, e) { if (n.cmp(s) <= 0) { throw new Error('Data too large.'); } @@ -85,7 +85,7 @@ export default { * @param e public MPI part as BN * @return BN */ - encrypt: function(m, n, e) { + encrypt: async function(m, n, e) { if (n.cmp(m) <= 0) { throw new Error('Data too large.'); } @@ -104,7 +104,7 @@ export default { * @param u RSA u as BN * @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) { throw new Error('Data too large.'); } @@ -117,7 +117,7 @@ export default { let blinder; let unblinder; 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); m = m.toRed(nred).redMul(blinder).fromRed(); } @@ -204,8 +204,8 @@ export default { // RSA keygen fallback using 40 iterations of the Miller-Rabin test // See https://stackoverflow.com/a/6330138 for justification // Also see section C.3 here: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST - let p = prime.randomProbablePrime(B - (B >> 1), E, 40); - let q = prime.randomProbablePrime(B >> 1, E, 40); + let p = await prime.randomProbablePrime(B - (B >> 1), E, 40); + let q = await prime.randomProbablePrime(B >> 1, E, 40); if (p.cmp(q) < 0) { [p, q] = [q, p]; diff --git a/src/crypto/signature.js b/src/crypto/signature.js index 28ed2d92..8b77c6d2 100644 --- a/src/crypto/signature.js +++ b/src/crypto/signature.js @@ -34,7 +34,7 @@ export default { const m = msg_MPIs[0].toBN(); const n = pub_MPIs[0].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()); return util.Uint8Array_to_hex(EM) === EM2; } @@ -88,7 +88,7 @@ export default { const d = key_params[2].toBN(); data = util.Uint8Array_to_str(data); 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); } case enums.publicKey.dsa: { @@ -96,7 +96,7 @@ export default { const q = key_params[1].toBN(); const g = key_params[2].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([ util.Uint8Array_to_MPI(signature.r), util.Uint8Array_to_MPI(signature.s) diff --git a/src/key.js b/src/key.js index cbf7d99c..5af81553 100644 --- a/src/key.js +++ b/src/key.js @@ -380,35 +380,32 @@ Key.prototype.getEncryptionKeyPacket = function(keyId, date=new Date()) { * Encrypts all secret key and subkey packets * @param {String} passphrase */ -Key.prototype.encrypt = function(passphrase) { +Key.prototype.encrypt = async function(passphrase) { if (!this.isPrivate()) { throw new Error("Nothing to encrypt in a public key"); } const keys = this.getAllKeyPackets(); - for (let i = 0; i < keys.length; i++) { - keys[i].encrypt(passphrase); - keys[i].clearPrivateParams(); - } + await Promise.all(keys.map(async function(packet) { + await packet.encrypt(passphrase); + await packet.clearPrivateParams(); + return packet; + })); + return true; }; /** * Decrypts all secret key and subkey packets * @param {String} passphrase - * @return {Boolean} true if all key and subkey packets decrypted successfully + * @return {Promise} true if all key and subkey packets decrypted successfully */ -Key.prototype.decrypt = function(passphrase) { - 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 { +Key.prototype.decrypt = async function(passphrase) { + if (!this.isPrivate()) { 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; }; @@ -1254,46 +1251,48 @@ export function generate(options) { * @return {module:key~Key} * @static */ -export function reformat(options) { +export async function reformat(options) { let secretKeyPacket; 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()) { - throw new Error('Key not decrypted'); - } + 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.passphrase) { // Key without passphrase is unlocked by definition - options.unlocked = true; + try { + 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]; - } - 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 (!secretKeyPacket) { - throw new Error('Key does not contain a secret key packet'); - } - return wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options); - }); + } + if (!secretKeyPacket) { + throw new Error('Key does not contain a secret key packet'); + } + return wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options); } async function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { // set passphrase protection if (options.passphrase) { - secretKeyPacket.encrypt(options.passphrase); + await secretKeyPacket.encrypt(options.passphrase); if (secretSubkeyPacket) { secretSubkeyPacket.encrypt(options.passphrase); } diff --git a/src/message.js b/src/message.js index 9109b804..40788a4a 100644 --- a/src/message.js +++ b/src/message.js @@ -262,7 +262,7 @@ Message.prototype.encrypt = function(keys, passwords, sessionKey, wildcard=false } if (!sessionKey) { - sessionKey = crypto.generateSessionKey(symAlgo); + sessionKey = await crypto.generateSessionKey(symAlgo); } msg = await encryptSessionKey(sessionKey, symAlgo, keys, passwords, wildcard, date); diff --git a/src/openpgp.js b/src/openpgp.js index bbe9f658..a7c0d048 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -179,6 +179,26 @@ export function decryptKey({ privateKey, passphrase }) { }).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 + * @return {Key} the locked private 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')); +} + /////////////////////////////////////////// // // diff --git a/src/packet/public_key_encrypted_session_key.js b/src/packet/public_key_encrypted_session_key.js index 5342b649..a14a67cb 100644 --- a/src/packet/public_key_encrypted_session_key.js +++ b/src/packet/public_key_encrypted_session_key.js @@ -112,7 +112,7 @@ PublicKeyEncryptedSessionKey.prototype.encrypt = async function (key) { if (algo === enums.publicKey.ecdh) { toEncrypt = new type_mpi(crypto.pkcs5.encode(data)); } 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( diff --git a/src/packet/secret_key.js b/src/packet/secret_key.js index dc9d7a50..e7046f52 100644 --- a/src/packet/secret_key.js +++ b/src/packet/secret_key.js @@ -81,7 +81,7 @@ function parse_cleartext_params(hash_algorithm, cleartext, algorithm) { const hash = util.Uint8Array_to_str(hashfn(cleartext)); if (hash !== hashtext) { - return new Error("Hash mismatch."); + return new Error("Incorrect key passphrase"); } const algo = enums.write(enums.publicKey, algorithm); @@ -173,7 +173,7 @@ SecretKey.prototype.write = function () { * This can be used to remove passphrase protection after calling decrypt(). * @param {String} passphrase */ -SecretKey.prototype.encrypt = function (passphrase) { +SecretKey.prototype.encrypt = async function (passphrase) { if (this.isDecrypted && !passphrase) { this.encrypted = null; return; @@ -182,11 +182,12 @@ SecretKey.prototype.encrypt = function (passphrase) { } const s2k = new type_s2k(); + s2k.salt = await crypto.random.getRandomBytes(8); const symmetric = 'aes256'; const cleartext = write_cleartext_params('sha1', this.algorithm, this.params); const key = produceEncryptionKey(s2k, passphrase, symmetric); 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)])]; arr.push(s2k.write()); @@ -213,7 +214,7 @@ function produceEncryptionKey(s2k, passphrase, algorithm) { * @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) { return true; } @@ -261,12 +262,11 @@ SecretKey.prototype.decrypt = function (passphrase) { const privParams = parse_cleartext_params(hash, cleartext, this.algorithm); if (privParams instanceof Error) { - return false; + throw privParams; } this.params = this.params.concat(privParams); this.isDecrypted = true; this.encrypted = null; - return true; }; SecretKey.prototype.generate = function (bits, curve) { diff --git a/src/packet/sym_encrypted_aead_protected.js b/src/packet/sym_encrypted_aead_protected.js index 0557883c..a6fbe934 100644 --- a/src/packet/sym_encrypted_aead_protected.js +++ b/src/packet/sym_encrypted_aead_protected.js @@ -66,10 +66,8 @@ SymEncryptedAEADProtected.prototype.write = function () { * @param {Uint8Array} key The session key used to encrypt the payload * @return {Promise} Nothing is returned */ -SymEncryptedAEADProtected.prototype.decrypt = function (sessionKeyAlgorithm, key) { - return crypto.gcm.decrypt(sessionKeyAlgorithm, this.encrypted, key, this.iv).then(decrypted => { - this.packets.read(decrypted); - }); +SymEncryptedAEADProtected.prototype.decrypt = async function (sessionKeyAlgorithm, key) { + this.packets.read(await crypto.gcm.decrypt(sessionKeyAlgorithm, this.encrypted, key, this.iv)); }; /** @@ -78,9 +76,7 @@ SymEncryptedAEADProtected.prototype.decrypt = function (sessionKeyAlgorithm, key * @param {Uint8Array} key The session key used to encrypt the payload * @return {Promise} Nothing is returned */ -SymEncryptedAEADProtected.prototype.encrypt = function (sessionKeyAlgorithm, key) { - this.iv = crypto.random.getRandomBytes(IV_LEN); // generate new random IV - return crypto.gcm.encrypt(sessionKeyAlgorithm, this.packets.write(), key, this.iv).then(encrypted => { - this.encrypted = encrypted; - }); +SymEncryptedAEADProtected.prototype.encrypt = async function (sessionKeyAlgorithm, key) { + this.iv = await crypto.random.getRandomBytes(IV_LEN); // generate new random IV + this.encrypted = await crypto.gcm.encrypt(sessionKeyAlgorithm, this.packets.write(), key, this.iv); }; diff --git a/src/packet/sym_encrypted_integrity_protected.js b/src/packet/sym_encrypted_integrity_protected.js index 25a9075d..6b8c7c29 100644 --- a/src/packet/sym_encrypted_integrity_protected.js +++ b/src/packet/sym_encrypted_integrity_protected.js @@ -81,9 +81,9 @@ SymEncryptedIntegrityProtected.prototype.write = function () { * @param {Uint8Array} key The key of cipher blocksize length to be used * @return {Promise} */ -SymEncryptedIntegrityProtected.prototype.encrypt = function (sessionKeyAlgorithm, key) { +SymEncryptedIntegrityProtected.prototype.encrypt = async function (sessionKeyAlgorithm, key) { 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 prefix = util.concatUint8Array([prefixrandom, repeat]); const mdc = new Uint8Array([0xD3, 0x14]); // modification detection code packet @@ -98,8 +98,6 @@ SymEncryptedIntegrityProtected.prototype.encrypt = function (sessionKeyAlgorithm this.encrypted = crypto.cfb.encrypt(prefixrandom, sessionKeyAlgorithm, tohash, key, false); this.encrypted = this.encrypted.subarray(0, prefix.length + tohash.length); } - - return Promise.resolve(); }; /** @@ -108,7 +106,7 @@ SymEncryptedIntegrityProtected.prototype.encrypt = function (sessionKeyAlgorithm * @param {Uint8Array} key The key of cipher blocksize length to be used * @return {Promise} */ -SymEncryptedIntegrityProtected.prototype.decrypt = function (sessionKeyAlgorithm, key) { +SymEncryptedIntegrityProtected.prototype.decrypt = async function (sessionKeyAlgorithm, key) { let decrypted; if (sessionKeyAlgorithm.substr(0, 3) === 'aes') { // AES optimizations. Native code for node, asmCrypto for browser. decrypted = aesDecrypt(sessionKeyAlgorithm, this.encrypted, key); diff --git a/src/packet/sym_encrypted_session_key.js b/src/packet/sym_encrypted_session_key.js index fa6d25ab..248b13cc 100644 --- a/src/packet/sym_encrypted_session_key.js +++ b/src/packet/sym_encrypted_session_key.js @@ -52,7 +52,7 @@ export default function SymEncryptedSessionKey() { this.sessionKeyEncryptionAlgorithm = null; this.sessionKeyAlgorithm = 'aes256'; 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]); // 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)); // Optionally, the encrypted session key itself, which is decrypted @@ -106,7 +107,7 @@ SymEncryptedSessionKey.prototype.write = function() { * * @return {Uint8Array} The unencrypted session key */ -SymEncryptedSessionKey.prototype.decrypt = function(passphrase) { +SymEncryptedSessionKey.prototype.decrypt = async function(passphrase) { const algo = this.sessionKeyEncryptionAlgorithm !== null ? this.sessionKeyEncryptionAlgorithm : this.sessionKeyAlgorithm; @@ -124,20 +125,23 @@ SymEncryptedSessionKey.prototype.decrypt = function(passphrase) { } }; -SymEncryptedSessionKey.prototype.encrypt = function(passphrase) { +SymEncryptedSessionKey.prototype.encrypt = async function(passphrase) { const algo = this.sessionKeyEncryptionAlgorithm !== null ? this.sessionKeyEncryptionAlgorithm : this.sessionKeyAlgorithm; this.sessionKeyEncryptionAlgorithm = algo; + this.s2k = new type_s2k(); + this.s2k.salt = await crypto.random.getRandomBytes(8); + const length = crypto.cipher[algo].keySize; const key = this.s2k.produce_key(passphrase, length); const algo_enum = new Uint8Array([enums.write(enums.symmetric, this.sessionKeyAlgorithm)]); 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]); diff --git a/src/packet/symmetrically_encrypted.js b/src/packet/symmetrically_encrypted.js index 210ef532..c370ef94 100644 --- a/src/packet/symmetrically_encrypted.js +++ b/src/packet/symmetrically_encrypted.js @@ -62,7 +62,7 @@ SymmetricallyEncrypted.prototype.write = function () { * Key as string with the corresponding length to the * algorithm */ -SymmetricallyEncrypted.prototype.decrypt = function (sessionKeyAlgorithm, key) { +SymmetricallyEncrypted.prototype.decrypt = async function (sessionKeyAlgorithm, key) { const decrypted = crypto.cfb.decrypt(sessionKeyAlgorithm, key, this.encrypted, true); // for modern cipher (blocklength != 64 bit, except for Twofish) MDC is required if (!this.ignore_mdc_error && @@ -76,10 +76,10 @@ SymmetricallyEncrypted.prototype.decrypt = function (sessionKeyAlgorithm, key) { return Promise.resolve(); }; -SymmetricallyEncrypted.prototype.encrypt = function (algo, key) { +SymmetricallyEncrypted.prototype.encrypt = async function (algo, key) { 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(); }; diff --git a/src/type/s2k.js b/src/type/s2k.js index 1493b18f..d0294dc1 100644 --- a/src/type/s2k.js +++ b/src/type/s2k.js @@ -46,7 +46,7 @@ export default function S2K() { /** Eight bytes of salt in a binary string. * @type {String} */ - this.salt = crypto.random.getRandomBytes(8); + this.salt = null; } S2K.prototype.get_count = function () { diff --git a/src/worker/async_proxy.js b/src/worker/async_proxy.js index 3e42cd42..eb404ed3 100644 --- a/src/worker/async_proxy.js +++ b/src/worker/async_proxy.js @@ -85,8 +85,8 @@ AsyncProxy.prototype.onMessage = function(event) { * Send message to worker with random data * @param {Integer} size Number of bytes to send */ -AsyncProxy.prototype.seedRandom = function(size) { - const buf = crypto.random.getRandomBytes(size); +AsyncProxy.prototype.seedRandom = async function(size) { + const buf = await crypto.random.getRandomBytes(size); this.worker.postMessage({ event:'seed-random', buf }, util.getTransferables(buf)); }; diff --git a/test/crypto/crypto.js b/test/crypto/crypto.js index fde07520..d63c5231 100644 --- a/test/crypto/crypto.js +++ b/test/crypto/crypto.js @@ -373,11 +373,10 @@ describe('API functional testing', function() { it('Asymmetric using RSA with eme_pkcs1 padding', function () { const symmKey = util.Uint8Array_to_str(crypto.generateSessionKey('aes256')); - const RSAUnencryptedData = crypto.pkcs1.eme.encode(symmKey, RSApubMPIs[0].byteLength()) - const RSAUnencryptedMPI = new openpgp.MPI(RSAUnencryptedData); - return crypto.publicKeyEncrypt( - 1, RSApubMPIs, RSAUnencryptedMPI - ).then(RSAEncryptedData => { + return crypto.pkcs1.eme.encode(symmKey, RSApubMPIs[0].byteLength()).then(RSAUnencryptedData => { + const RSAUnencryptedMPI = new openpgp.MPI(RSAUnencryptedData); + return crypto.publicKeyEncrypt(1, RSApubMPIs, RSAUnencryptedMPI); + }).then(RSAEncryptedData => { return crypto.publicKeyDecrypt( 1, RSApubMPIs.concat(RSAsecMPIs), RSAEncryptedData @@ -393,12 +392,10 @@ describe('API functional testing', function() { it('Asymmetric using Elgamal with eme_pkcs1 padding', function () { const symmKey = util.Uint8Array_to_str(crypto.generateSessionKey('aes256')); - const ElgamalUnencryptedData = crypto.pkcs1.eme.encode(symmKey, ElgamalpubMPIs[0].byteLength()); - const ElgamalUnencryptedMPI = new openpgp.MPI(ElgamalUnencryptedData); - - return crypto.publicKeyEncrypt( - 16, ElgamalpubMPIs, ElgamalUnencryptedMPI - ).then(ElgamalEncryptedData => { + return crypto.pkcs1.eme.encode(symmKey, ElgamalpubMPIs[0].byteLength()).then(ElgamalUnencryptedData => { + const ElgamalUnencryptedMPI = new openpgp.MPI(ElgamalUnencryptedData); + return crypto.publicKeyEncrypt(16, ElgamalpubMPIs, ElgamalUnencryptedMPI); + }).then(ElgamalEncryptedData => { return crypto.publicKeyDecrypt( 16, ElgamalpubMPIs.concat(ElgamalsecMPIs), ElgamalEncryptedData diff --git a/test/general/ecc_nist.js b/test/general/ecc_nist.js index df5339b9..f88708d8 100644 --- a/test/general/ecc_nist.js +++ b/test/general/ecc_nist.js @@ -142,7 +142,7 @@ describe('Elliptic Curve Cryptography', function () { data[name].pub_key = pub.keys[0]; return data[name].pub_key; } - function load_priv_key(name) { + async function load_priv_key(name) { if (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.keys).to.have.length(1); 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]; return data[name].priv_key; } @@ -160,10 +160,10 @@ describe('Elliptic Curve Cryptography', function () { load_pub_key('juliet'); done(); }); - it('Load private key', function (done) { - load_priv_key('romeo'); - load_priv_key('juliet'); - done(); + it('Load private key', async function () { + await load_priv_key('romeo'); + await load_priv_key('juliet'); + return true; }); it('Verify clear signed message', function () { const pub = load_pub_key('juliet'); @@ -175,52 +175,45 @@ describe('Elliptic Curve Cryptography', function () { expect(result.signatures[0].valid).to.be.true; }); }); - it('Sign message', function () { - const romeo = load_priv_key('romeo'); - return openpgp.sign({privateKeys: [romeo], data: data.romeo.message + "\n"}).then(function (signed) { - const romeo = load_pub_key('romeo'); - const msg = openpgp.cleartext.readArmored(signed.data); - return openpgp.verify({publicKeys: [romeo], message: msg}).then(function (result) { - expect(result).to.exist; - expect(result.data.trim()).to.equal(data.romeo.message); - expect(result.signatures).to.have.length(1); - expect(result.signatures[0].valid).to.be.true; - }); - }); + it('Sign message', async function () { + const romeoPrivate = await load_priv_key('romeo'); + const signed = await openpgp.sign({privateKeys: [romeoPrivate], data: data.romeo.message + "\n"}); + const romeoPublic = load_pub_key('romeo'); + const msg = openpgp.cleartext.readArmored(signed.data); + const result = await openpgp.verify({publicKeys: [romeoPublic], message: msg}); + + expect(result).to.exist; + expect(result.data.trim()).to.equal(data.romeo.message); + 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 romeo = load_priv_key('romeo'); + const romeo = await load_priv_key('romeo'); const msg = openpgp.message.readArmored(data.juliet.message_encrypted); - return openpgp.decrypt( - {privateKeys: romeo, publicKeys: [juliet], message: msg} - ).then(function (result) { - expect(result).to.exist; - // trim required because https://github.com/openpgpjs/openpgpjs/issues/311 - expect(result.data.trim()).to.equal(data.juliet.message); - expect(result.signatures).to.have.length(1); - expect(result.signatures[0].valid).to.be.true; - }); + const result = await openpgp.decrypt({privateKeys: romeo, publicKeys: [juliet], message: msg}); + + expect(result).to.exist; + // trim required because https://github.com/openpgpjs/openpgpjs/issues/311 + expect(result.data.trim()).to.equal(data.juliet.message); + expect(result.signatures).to.have.length(1); + expect(result.signatures[0].valid).to.be.true; }); - it('Encrypt and sign message', function () { - const romeo = load_priv_key('romeo'); - const juliet = load_pub_key('juliet'); - expect(romeo.decrypt(data.romeo.pass)).to.be.true; - return openpgp.encrypt( - {publicKeys: [juliet], privateKeys: [romeo], data: data.romeo.message + "\n"} - ).then(function (encrypted) { - const message = openpgp.message.readArmored(encrypted.data); - const romeo = load_pub_key('romeo'); - const juliet = load_priv_key('juliet'); - return openpgp.decrypt( - {privateKeys: juliet, publicKeys: [romeo], message: message} - ).then(function (result) { - expect(result).to.exist; - expect(result.data.trim()).to.equal(data.romeo.message); - expect(result.signatures).to.have.length(1); - expect(result.signatures[0].valid).to.be.true; - }); - }); + it('Encrypt and sign message', async function () { + const romeoPrivate = await load_priv_key('romeo'); + const julietPublic = load_pub_key('juliet'); + expect(await romeoPrivate.decrypt(data.romeo.pass)).to.be.true; + const encrypted = await openpgp.encrypt({publicKeys: [julietPublic], privateKeys: [romeoPrivate], data: data.romeo.message + "\n"}); + + const message = openpgp.message.readArmored(encrypted.data); + const romeoPublic = load_pub_key('romeo'); + const julietPrivate = await load_priv_key('juliet'); + const result = await openpgp.decrypt({privateKeys: julietPrivate, publicKeys: [romeoPublic], message: message}); + + expect(result).to.exist; + expect(result.data.trim()).to.equal(data.romeo.message); + expect(result.signatures).to.have.length(1); + expect(result.signatures[0].valid).to.be.true; }); it('Generate key', function () { const options = { diff --git a/test/general/key.js b/test/general/key.js index ea7a1147..87d53190 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -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 '; const opt = {numBits: 512, userIds: userId, passphrase: 'passphrase'}; if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys - return openpgp.generateKey(opt).then(function(key) { - key = key.key; - const armor1 = key.armor(); - const armor2 = key.armor(); - expect(armor1).to.equal(armor2); - expect(key.decrypt('passphrase')).to.be.true; - expect(key.primaryKey.isDecrypted).to.be.true; - key.encrypt('new_passphrase'); - expect(key.primaryKey.isDecrypted).to.be.false; - expect(key.decrypt('passphrase')).to.be.false; - expect(key.primaryKey.isDecrypted).to.be.false; - expect(key.decrypt('new_passphrase')).to.be.true; - expect(key.primaryKey.isDecrypted).to.be.true; - const armor3 = key.armor(); - expect(armor3).to.not.equal(armor1); - }); + const key = (await openpgp.generateKey(opt)).key; + const armor1 = key.armor(); + const armor2 = key.armor(); + expect(armor1).to.equal(armor2); + expect(await key.decrypt('passphrase')).to.be.true; + expect(key.primaryKey.isDecrypted).to.be.true; + await key.encrypt('new_passphrase'); + expect(key.primaryKey.isDecrypted).to.be.false; + await expect(key.decrypt('passphrase')).to.eventually.be.rejectedWith('Incorrect key passphrase'); + expect(key.primaryKey.isDecrypted).to.be.false; + expect(await key.decrypt('new_passphrase')).to.be.true; + expect(key.primaryKey.isDecrypted).to.be.true; + const armor3 = key.armor(); + expect(armor3).to.not.equal(armor1); }); 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 ', passphrase: '1234'}; if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys return openpgp.generateKey(opt).then(function(original) { diff --git a/test/general/openpgp.js b/test/general/openpgp.js index 6a71eec6..5bbe68ee 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -635,12 +635,12 @@ describe('OpenPGP.js public api tests', function() { openpgp.config.aead_protect = aead_protectVal; }); - it('Decrypting key with wrong passphrase returns false', function () { - expect(privateKey.keys[0].decrypt('wrong passphrase')).to.be.false; + it('Decrypting key with wrong passphrase rejected', function () { + expect(privateKey.keys[0].decrypt('wrong passphrase')).to.eventually.be.rejectedWith('Incorrect key passphrase'); }); - it('Decrypting key with correct passphrase returns true', function () { - expect(privateKey.keys[0].decrypt(passphrase)).to.be.true; + it('Decrypting key with correct passphrase returns true', async function () { + expect(await privateKey.keys[0].decrypt(passphrase)).to.be.true; }); tryTests('CFB mode (asm.js)', tests, { @@ -719,7 +719,7 @@ describe('OpenPGP.js public api tests', function() { privateKey: privateKey.keys[0], passphrase: 'incorrect' }).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() { const sk = new Uint8Array([0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01]); - beforeEach(function(done) { - expect(privateKey.keys[0].decrypt(passphrase)).to.be.true; - privateKey.keys[0].verifyPrimaryUser().then(() => done()); + beforeEach(async function() { + expect(await privateKey.keys[0].decrypt(passphrase)).to.be.true; + await privateKey.keys[0].verifyPrimaryUser(); + return true; }); it('should encrypt with public key', function() { @@ -867,14 +868,13 @@ describe('OpenPGP.js public api tests', function() { '=6XMW\r\n' + '-----END PGP PUBLIC KEY BLOCK-----\r\n\r\n'; - beforeEach(function (done) { - expect(privateKey.keys[0].decrypt(passphrase)).to.be.true; - Promise.all([ - privateKey.keys[0].verifyPrimaryUser(), - privateKey_2000_2008.keys[0].verifyPrimaryUser(), - privateKey_1337.keys[0].verifyPrimaryUser(), - privateKey_2038_2045.keys[0].verifyPrimaryUser() - ]).then(() => done()); + beforeEach( async function () { + expect(await privateKey.keys[0].decrypt(passphrase)).to.be.true; + await privateKey.keys[0].verifyPrimaryUser(); + await privateKey_2000_2008.keys[0].verifyPrimaryUser(); + await privateKey_1337.keys[0].verifyPrimaryUser(); + await privateKey_2038_2045.keys[0].verifyPrimaryUser(); + return true; }); it('should encrypt then decrypt', function () { diff --git a/test/general/openpgp2.js b/test/general/openpgp2.js new file mode 100644 index 00000000..3183ce1e --- /dev/null +++ b/test/general/openpgp2.js @@ -0,0 +1,219 @@ +/* globals tryTests: true */ + +'use strict'; + +var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); + +var sinon = require('sinon'), + chai = require('chai'); +chai.use(require('chai-as-promised')); +var expect = chai.expect; + +var pub_key = + ['-----BEGIN PGP PUBLIC KEY BLOCK-----', + 'Version: GnuPG v2.0.19 (GNU/Linux)', + '', + 'mI0EUmEvTgEEANyWtQQMOybQ9JltDqmaX0WnNPJeLILIM36sw6zL0nfTQ5zXSS3+', + 'fIF6P29lJFxpblWk02PSID5zX/DYU9/zjM2xPO8Oa4xo0cVTOTLj++Ri5mtr//f5', + 'GLsIXxFrBJhD/ghFsL3Op0GXOeLJ9A5bsOn8th7x6JucNKuaRB6bQbSPABEBAAG0', + 'JFRlc3QgTWNUZXN0aW5ndG9uIDx0ZXN0QGV4YW1wbGUuY29tPoi5BBMBAgAjBQJS', + 'YS9OAhsvBwsJCAcDAgEGFQgCCQoLBBYCAwECHgECF4AACgkQSmNhOk1uQJQwDAP6', + 'AgrTyqkRlJVqz2pb46TfbDM2TDF7o9CBnBzIGoxBhlRwpqALz7z2kxBDmwpQa+ki', + 'Bq3jZN/UosY9y8bhwMAlnrDY9jP1gdCo+H0sD48CdXybblNwaYpwqC8VSpDdTndf', + '9j2wE/weihGp/DAdy/2kyBCaiOY1sjhUfJ1GogF49rC4jQRSYS9OAQQA6R/PtBFa', + 'JaT4jq10yqASk4sqwVMsc6HcifM5lSdxzExFP74naUMMyEsKHP53QxTF0Grqusag', + 'Qg/ZtgT0CN1HUM152y7ACOdp1giKjpMzOTQClqCoclyvWOFB+L/SwGEIJf7LSCEr', + 'woBuJifJc8xAVr0XX0JthoW+uP91eTQ3XpsAEQEAAYkBPQQYAQIACQUCUmEvTgIb', + 'LgCoCRBKY2E6TW5AlJ0gBBkBAgAGBQJSYS9OAAoJEOCE90RsICyXuqIEANmmiRCA', + 'SF7YK7PvFkieJNwzeK0V3F2lGX+uu6Y3Q/Zxdtwc4xR+me/CSBmsURyXTO29OWhP', + 'GLszPH9zSJU9BdDi6v0yNprmFPX/1Ng0Abn/sCkwetvjxC1YIvTLFwtUL/7v6NS2', + 'bZpsUxRTg9+cSrMWWSNjiY9qUKajm1tuzPDZXAUEAMNmAN3xXN/Kjyvj2OK2ck0X', + 'W748sl/tc3qiKPMJ+0AkMF7Pjhmh9nxqE9+QCEl7qinFqqBLjuzgUhBU4QlwX1GD', + 'AtNTq6ihLMD5v1d82ZC7tNatdlDMGWnIdvEMCv2GZcuIqDQ9rXWs49e7tq1NncLY', + 'hz3tYjKhoFTKEIq3y3Pp', + '=h/aX', + '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); + +var priv_key = + ['-----BEGIN PGP PRIVATE KEY BLOCK-----', + 'Version: GnuPG v2.0.19 (GNU/Linux)', + '', + 'lQH+BFJhL04BBADclrUEDDsm0PSZbQ6pml9FpzTyXiyCyDN+rMOsy9J300Oc10kt', + '/nyBej9vZSRcaW5VpNNj0iA+c1/w2FPf84zNsTzvDmuMaNHFUzky4/vkYuZra//3', + '+Ri7CF8RawSYQ/4IRbC9zqdBlzniyfQOW7Dp/LYe8eibnDSrmkQem0G0jwARAQAB', + '/gMDAu7L//czBpE40p1ZqO8K3k7UejemjsQqc7kOqnlDYd1Z6/3NEA/UM30Siipr', + 'KjdIFY5+hp0hcs6EiiNq0PDfm/W2j+7HfrZ5kpeQVxDek4irezYZrl7JS2xezaLv', + 'k0Fv/6fxasnFtjOM6Qbstu67s5Gpl9y06ZxbP3VpT62+Xeibn/swWrfiJjuGEEhM', + 'bgnsMpHtzAz/L8y6KSzViG/05hBaqrvk3/GeEA6nE+o0+0a6r0LYLTemmq6FbaA1', + 'PHo+x7k7oFcBFUUeSzgx78GckuPwqr2mNfeF+IuSRnrlpZl3kcbHASPAOfEkyMXS', + 'sWGE7grCAjbyQyM3OEXTSyqnehvGS/1RdB6kDDxGwgE/QFbwNyEh6K4eaaAThW2j', + 'IEEI0WEnRkPi9fXyxhFsCLSI1XhqTaq7iDNqJTxE+AX2b9ZuZXAxI3Tc/7++vEyL', + '3p18N/MB2kt1Wb1azmXWL2EKlT1BZ5yDaJuBQ8BhphM3tCRUZXN0IE1jVGVzdGlu', + 'Z3RvbiA8dGVzdEBleGFtcGxlLmNvbT6IuQQTAQIAIwUCUmEvTgIbLwcLCQgHAwIB', + 'BhUIAgkKCwQWAgMBAh4BAheAAAoJEEpjYTpNbkCUMAwD+gIK08qpEZSVas9qW+Ok', + '32wzNkwxe6PQgZwcyBqMQYZUcKagC8+89pMQQ5sKUGvpIgat42Tf1KLGPcvG4cDA', + 'JZ6w2PYz9YHQqPh9LA+PAnV8m25TcGmKcKgvFUqQ3U53X/Y9sBP8HooRqfwwHcv9', + 'pMgQmojmNbI4VHydRqIBePawnQH+BFJhL04BBADpH8+0EVolpPiOrXTKoBKTiyrB', + 'UyxzodyJ8zmVJ3HMTEU/vidpQwzISwoc/ndDFMXQauq6xqBCD9m2BPQI3UdQzXnb', + 'LsAI52nWCIqOkzM5NAKWoKhyXK9Y4UH4v9LAYQgl/stIISvCgG4mJ8lzzEBWvRdf', + 'Qm2Ghb64/3V5NDdemwARAQAB/gMDAu7L//czBpE40iPcpLzL7GwBbWFhSWgSLy53', + 'Md99Kxw3cApWCok2E8R9/4VS0490xKZIa5y2I/K8thVhqk96Z8Kbt7MRMC1WLHgC', + 'qJvkeQCI6PrFM0PUIPLHAQtDJYKtaLXxYuexcAdKzZj3FHdtLNWCooK6n3vJlL1c', + 'WjZcHJ1PH7USlj1jup4XfxsbziuysRUSyXkjn92GZLm+64vCIiwhqAYoizF2NHHG', + 'hRTN4gQzxrxgkeVchl+ag7DkQUDANIIVI+A63JeLJgWJiH1fbYlwESByHW+zBFNt', + 'qStjfIOhjrfNIc3RvsggbDdWQLcbxmLZj4sB0ydPSgRKoaUdRHJY0S4vp9ouKOtl', + '2au/P1BP3bhD0fDXl91oeheYth+MSmsJFDg/vZJzCJhFaQ9dp+2EnjN5auNCNbaI', + 'beFJRHFf9cha8p3hh+AK54NRCT++B2MXYf+TPwqX88jYMBv8kk8vYUgo8128r1zQ', + 'EzjviQE9BBgBAgAJBQJSYS9OAhsuAKgJEEpjYTpNbkCUnSAEGQECAAYFAlJhL04A', + 'CgkQ4IT3RGwgLJe6ogQA2aaJEIBIXtgrs+8WSJ4k3DN4rRXcXaUZf667pjdD9nF2', + '3BzjFH6Z78JIGaxRHJdM7b05aE8YuzM8f3NIlT0F0OLq/TI2muYU9f/U2DQBuf+w', + 'KTB62+PELVgi9MsXC1Qv/u/o1LZtmmxTFFOD35xKsxZZI2OJj2pQpqObW27M8Nlc', + 'BQQAw2YA3fFc38qPK+PY4rZyTRdbvjyyX+1zeqIo8wn7QCQwXs+OGaH2fGoT35AI', + 'SXuqKcWqoEuO7OBSEFThCXBfUYMC01OrqKEswPm/V3zZkLu01q12UMwZach28QwK', + '/YZly4ioND2tdazj17u2rU2dwtiHPe1iMqGgVMoQirfLc+k=', + '=lw5e', + '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); + +var pub_key_de = + ['-----BEGIN PGP PUBLIC KEY BLOCK-----', + 'Version: GnuPG v2.0.22 (GNU/Linux)', + '', + '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', + 'og2umGfGng==', + '=v3+L', + '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); + +var priv_key_de = + ['-----BEGIN PGP PRIVATE KEY BLOCK-----', + 'Version: GnuPG v2.0.22 (GNU/Linux)', + '', + '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', + 'DHTORVqAOdoOcu5Qh7AQg1GdSmfFAsx2', + '=kyeP', + '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); + +var passphrase = 'hello world'; +var plaintext = 'short message\nnext line\n한국어/조선말'; +var password1 = 'I am a password'; +var password2 = 'I am another password'; + +var fuckedUp = ['-----BEGIN PGP MESSAGE-----', +'Version: OpenPGP.js v3.0.0', +'Comment: https://openpgpjs.org', +'', +'wy4ECQMIWjj3WEfWxGpgrfb3vXu0TS9L8UNTBvNZFIjltGjMVkLFD+/afgs5', +'aXt0wy4ECQMIrFo3TFN5xqtgtB+AaAjBcWJrA4bvIPBpJ38PbMWeF0JQgrqg', +'j3uehxXy0mUB5i7B61g0ho+YplyFGM0s9XayJCnu40tWmr5LqqsRxuwrhJKR', +'migslOF/l6Y9F0F9xGIZWGhxp3ugQPjVKjj8fOH7ap14mLm60C8q8AOxiSmL', +'ubsd/hL7FPZatUYAAZVA0a6hmQ==', +'=cHCV', +'-----END PGP MESSAGE-----'].join('\n'); + +describe('encrypt, decrypt, sign, verify - integration tests', function() { + var privateKey, publicKey, zero_copyVal, use_nativeVal, aead_protectVal; + + beforeEach(function(done) { + publicKey = openpgp.key.readArmored(pub_key); + expect(publicKey.keys).to.have.length(1); + expect(publicKey.err).to.not.exist; + privateKey = openpgp.key.readArmored(priv_key); + expect(privateKey.keys).to.have.length(1); + expect(privateKey.err).to.not.exist; + zero_copyVal = openpgp.config.zero_copy; + use_nativeVal = openpgp.config.use_native; + aead_protectVal = openpgp.config.aead_protect; + privateKey.keys[0].verifyPrimaryUser().then(() => done()); + }); + + afterEach(function() { + openpgp.config.zero_copy = zero_copyVal; + openpgp.config.use_native = use_nativeVal; + openpgp.config.aead_protect = aead_protectVal; + }); + + it('should encrypt and decrypt with two passwords', function() { + var encOpt = { + data: plaintext, + passwords: [password1, password2] + }; + var decOpt = { + password: password2 + }; + return openpgp.encrypt(encOpt).then(function(encrypted) { + encrypted.data = fuckedUp; + //console.log(encrypted.data); + decOpt.message = openpgp.message.readArmored(encrypted.data); + return openpgp.decrypt(decOpt); + }).then(function(decrypted) { + expect(decrypted.data).to.equal(plaintext); + expect(decrypted.signatures.length).to.equal(0); + }); + }); +}); diff --git a/test/general/packet.js b/test/general/packet.js index 8e924fff..c5fe98d4 100644 --- a/test/general/packet.js +++ b/test/general/packet.js @@ -55,7 +55,7 @@ describe("Packet", function() { '=KXkj\n' + '-----END PGP PRIVATE KEY BLOCK-----'; - it('Symmetrically encrypted packet', function(done) { + it('Symmetrically encrypted packet', async function() { const message = new openpgp.packet.List(); 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 algo = 'aes256'; - enc.encrypt(algo, key); + await enc.encrypt(algo, key); const msg2 = new openpgp.packet.List(); msg2.read(message.write()); 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)); - 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 literal = new openpgp.packet.Literal(); @@ -87,19 +86,19 @@ describe("Packet", function() { const enc = new openpgp.packet.SymmetricallyEncrypted(); message.push(enc); - enc.packets.push(literal); + await enc.packets.push(literal); const key = '12345678901234567890123456789012'; const algo = 'aes256'; - enc.encrypt(algo, key); + await enc.encrypt(algo, key); const msg2 = new openpgp.packet.List(); 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 algo = 'aes256'; @@ -110,15 +109,14 @@ describe("Packet", function() { msg.push(enc); literal.setText('Hello world!'); enc.packets.push(literal); - enc.encrypt(algo, key); + await enc.encrypt(algo, key); const msg2 = new openpgp.packet.List(); 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)); - done(); }); 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 algo = 'aes256'; @@ -323,23 +321,22 @@ describe("Packet", function() { msg.push(enc); key_enc.sessionKeyAlgorithm = algo; - key_enc.decrypt(passphrase); + await key_enc.encrypt(passphrase); const key = key_enc.sessionKey; literal.setText('Hello world!'); enc.packets.push(literal); - enc.encrypt(algo, key); + await enc.encrypt(algo, key); const msg2 = new openpgp.packet.List(); msg2.read(msg.write()); - msg2[0].decrypt(passphrase); + await msg2[0].decrypt(passphrase); 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)); - done(); }); it('Secret key encryption/decryption test', function() { diff --git a/test/general/x25519.js b/test/general/x25519.js index cd21bca1..b7849304 100644 --- a/test/general/x25519.js +++ b/test/general/x25519.js @@ -127,7 +127,7 @@ describe('X25519 Cryptography', function () { return data[name].pub_key; } - function load_priv_key(name) { + async function load_priv_key(name) { if (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.keys).to.have.length(1); 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]; 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 // by default chooses a larger number for S2K iterations than we do. - it('Load private key', function (done) { - load_priv_key('light'); - load_priv_key('night'); - done(); + it('Load private key', async function () { + await load_priv_key('light'); + await load_priv_key('night'); + return true; }); 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 priv = load_priv_key(name); - return openpgp.sign({ privateKeys: [priv], data: data[name].message + "\n" }).then(function (signed) { - const pub = load_pub_key(name); - const msg = openpgp.cleartext.readArmored(signed.data); - return openpgp.verify({ publicKeys: [pub], message: msg}).then(function (result) { - expect(result).to.exist; - expect(result.data.trim()).to.equal(data[name].message); - expect(result.signatures).to.have.length(1); - expect(result.signatures[0].valid).to.be.true; - }); - }); + const priv = await load_priv_key(name); + const signed = await openpgp.sign({ privateKeys: [priv], data: data[name].message + "\n" }); + const pub = load_pub_key(name); + const msg = openpgp.cleartext.readArmored(signed.data); + const result = await openpgp.verify({ publicKeys: [pub], message: msg}); + + expect(result).to.exist; + expect(result.data.trim()).to.equal(data[name].message); + 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 night = load_priv_key('night'); - expect(night.decrypt(data.night.pass)).to.be.true; + const night = await load_priv_key('night'); + expect(await night.decrypt(data.night.pass)).to.be.true; const msg = openpgp.message.readArmored(data.night.message_encrypted); - return openpgp.decrypt( - { privateKeys: night, publicKeys: [light], message: msg } - ).then(function (result) { - expect(result).to.exist; - // trim required because https://github.com/openpgpjs/openpgpjs/issues/311 - expect(result.data.trim()).to.equal(data.night.message); - expect(result.signatures).to.have.length(1); - expect(result.signatures[0].valid).to.be.true; - }); + const result = await openpgp.decrypt({ privateKeys: night, publicKeys: [light], message: msg }); + + expect(result).to.exist; + // trim required because https://github.com/openpgpjs/openpgpjs/issues/311 + expect(result.data.trim()).to.equal(data.night.message); + expect(result.signatures).to.have.length(1); + expect(result.signatures[0].valid).to.be.true; }); - it('Encrypt and sign message', function () { - const night = load_pub_key('night'); - const light = load_priv_key('light'); - expect(light.decrypt(data.light.pass)).to.be.true; - openpgp.encrypt( - { publicKeys: [night], privateKeys: [light], data: data.light.message + "\n" } - ).then(function (encrypted) { - const message = openpgp.message.readArmored(encrypted.data); - const light = load_pub_key('light'); - const night = load_priv_key('night'); - return openpgp.decrypt( - { privateKeys: night, publicKeys: [light], message: message } - ).then(function (result) { - expect(result).to.exist; - expect(result.data.trim()).to.equal(data.light.message); - expect(result.signatures).to.have.length(1); - expect(result.signatures[0].valid).to.be.true; - }); - }); + it('Encrypt and sign message', async function () { + const nightPublic = load_pub_key('night'); + const lightPrivate = await load_priv_key('light'); + expect(await lightPrivate.decrypt(data.light.pass)).to.be.true; + const encrypted = await openpgp.encrypt({ publicKeys: [nightPublic], privateKeys: [lightPrivate], data: data.light.message + "\n" }); + + const message = openpgp.message.readArmored(encrypted.data); + const lightPublic = load_pub_key('light'); + const nightPrivate = await load_priv_key('night'); + const result = await openpgp.decrypt({ privateKeys: nightPrivate, publicKeys: [lightPublic], message: message }); + + expect(result).to.exist; + expect(result.data.trim()).to.equal(data.light.message); + expect(result.signatures).to.have.length(1); + expect(result.signatures[0].valid).to.be.true; }); // TODO export, then reimport key and validate From 433ae5cce71afc7b4c574639d0b5250a1bf0bb91 Mon Sep 17 00:00:00 2001 From: Bart Butler Date: Mon, 5 Mar 2018 16:32:16 -0800 Subject: [PATCH 3/6] remove extraneous file --- test/general/openpgp2.js | 219 --------------------------------------- 1 file changed, 219 deletions(-) delete mode 100644 test/general/openpgp2.js diff --git a/test/general/openpgp2.js b/test/general/openpgp2.js deleted file mode 100644 index 3183ce1e..00000000 --- a/test/general/openpgp2.js +++ /dev/null @@ -1,219 +0,0 @@ -/* globals tryTests: true */ - -'use strict'; - -var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); - -var sinon = require('sinon'), - chai = require('chai'); -chai.use(require('chai-as-promised')); -var expect = chai.expect; - -var pub_key = - ['-----BEGIN PGP PUBLIC KEY BLOCK-----', - 'Version: GnuPG v2.0.19 (GNU/Linux)', - '', - 'mI0EUmEvTgEEANyWtQQMOybQ9JltDqmaX0WnNPJeLILIM36sw6zL0nfTQ5zXSS3+', - 'fIF6P29lJFxpblWk02PSID5zX/DYU9/zjM2xPO8Oa4xo0cVTOTLj++Ri5mtr//f5', - 'GLsIXxFrBJhD/ghFsL3Op0GXOeLJ9A5bsOn8th7x6JucNKuaRB6bQbSPABEBAAG0', - 'JFRlc3QgTWNUZXN0aW5ndG9uIDx0ZXN0QGV4YW1wbGUuY29tPoi5BBMBAgAjBQJS', - 'YS9OAhsvBwsJCAcDAgEGFQgCCQoLBBYCAwECHgECF4AACgkQSmNhOk1uQJQwDAP6', - 'AgrTyqkRlJVqz2pb46TfbDM2TDF7o9CBnBzIGoxBhlRwpqALz7z2kxBDmwpQa+ki', - 'Bq3jZN/UosY9y8bhwMAlnrDY9jP1gdCo+H0sD48CdXybblNwaYpwqC8VSpDdTndf', - '9j2wE/weihGp/DAdy/2kyBCaiOY1sjhUfJ1GogF49rC4jQRSYS9OAQQA6R/PtBFa', - 'JaT4jq10yqASk4sqwVMsc6HcifM5lSdxzExFP74naUMMyEsKHP53QxTF0Grqusag', - 'Qg/ZtgT0CN1HUM152y7ACOdp1giKjpMzOTQClqCoclyvWOFB+L/SwGEIJf7LSCEr', - 'woBuJifJc8xAVr0XX0JthoW+uP91eTQ3XpsAEQEAAYkBPQQYAQIACQUCUmEvTgIb', - 'LgCoCRBKY2E6TW5AlJ0gBBkBAgAGBQJSYS9OAAoJEOCE90RsICyXuqIEANmmiRCA', - 'SF7YK7PvFkieJNwzeK0V3F2lGX+uu6Y3Q/Zxdtwc4xR+me/CSBmsURyXTO29OWhP', - 'GLszPH9zSJU9BdDi6v0yNprmFPX/1Ng0Abn/sCkwetvjxC1YIvTLFwtUL/7v6NS2', - 'bZpsUxRTg9+cSrMWWSNjiY9qUKajm1tuzPDZXAUEAMNmAN3xXN/Kjyvj2OK2ck0X', - 'W748sl/tc3qiKPMJ+0AkMF7Pjhmh9nxqE9+QCEl7qinFqqBLjuzgUhBU4QlwX1GD', - 'AtNTq6ihLMD5v1d82ZC7tNatdlDMGWnIdvEMCv2GZcuIqDQ9rXWs49e7tq1NncLY', - 'hz3tYjKhoFTKEIq3y3Pp', - '=h/aX', - '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); - -var priv_key = - ['-----BEGIN PGP PRIVATE KEY BLOCK-----', - 'Version: GnuPG v2.0.19 (GNU/Linux)', - '', - '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', - '=lw5e', - '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); - -var pub_key_de = - ['-----BEGIN PGP PUBLIC KEY BLOCK-----', - 'Version: GnuPG v2.0.22 (GNU/Linux)', - '', - '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', - 'og2umGfGng==', - '=v3+L', - '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); - -var priv_key_de = - ['-----BEGIN PGP PRIVATE KEY BLOCK-----', - 'Version: GnuPG v2.0.22 (GNU/Linux)', - '', - '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', - 'DHTORVqAOdoOcu5Qh7AQg1GdSmfFAsx2', - '=kyeP', - '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); - -var passphrase = 'hello world'; -var plaintext = 'short message\nnext line\n한국어/조선말'; -var password1 = 'I am a password'; -var password2 = 'I am another password'; - -var fuckedUp = ['-----BEGIN PGP MESSAGE-----', -'Version: OpenPGP.js v3.0.0', -'Comment: https://openpgpjs.org', -'', -'wy4ECQMIWjj3WEfWxGpgrfb3vXu0TS9L8UNTBvNZFIjltGjMVkLFD+/afgs5', -'aXt0wy4ECQMIrFo3TFN5xqtgtB+AaAjBcWJrA4bvIPBpJ38PbMWeF0JQgrqg', -'j3uehxXy0mUB5i7B61g0ho+YplyFGM0s9XayJCnu40tWmr5LqqsRxuwrhJKR', -'migslOF/l6Y9F0F9xGIZWGhxp3ugQPjVKjj8fOH7ap14mLm60C8q8AOxiSmL', -'ubsd/hL7FPZatUYAAZVA0a6hmQ==', -'=cHCV', -'-----END PGP MESSAGE-----'].join('\n'); - -describe('encrypt, decrypt, sign, verify - integration tests', function() { - var privateKey, publicKey, zero_copyVal, use_nativeVal, aead_protectVal; - - beforeEach(function(done) { - publicKey = openpgp.key.readArmored(pub_key); - expect(publicKey.keys).to.have.length(1); - expect(publicKey.err).to.not.exist; - privateKey = openpgp.key.readArmored(priv_key); - expect(privateKey.keys).to.have.length(1); - expect(privateKey.err).to.not.exist; - zero_copyVal = openpgp.config.zero_copy; - use_nativeVal = openpgp.config.use_native; - aead_protectVal = openpgp.config.aead_protect; - privateKey.keys[0].verifyPrimaryUser().then(() => done()); - }); - - afterEach(function() { - openpgp.config.zero_copy = zero_copyVal; - openpgp.config.use_native = use_nativeVal; - openpgp.config.aead_protect = aead_protectVal; - }); - - it('should encrypt and decrypt with two passwords', function() { - var encOpt = { - data: plaintext, - passwords: [password1, password2] - }; - var decOpt = { - password: password2 - }; - return openpgp.encrypt(encOpt).then(function(encrypted) { - encrypted.data = fuckedUp; - //console.log(encrypted.data); - decOpt.message = openpgp.message.readArmored(encrypted.data); - return openpgp.decrypt(decOpt); - }).then(function(decrypted) { - expect(decrypted.data).to.equal(plaintext); - expect(decrypted.signatures.length).to.equal(0); - }); - }); -}); From 572abadc91363569541a5fcfcf3ea33edf4f101c Mon Sep 17 00:00:00 2001 From: Bart Butler Date: Mon, 5 Mar 2018 17:57:35 -0800 Subject: [PATCH 4/6] random number web worker buffer automatic refill --- src/crypto/random.js | 17 +++++++++++------ src/worker/async_proxy.js | 3 --- test/crypto/crypto.js | 18 +++++++++--------- test/crypto/random.js | 10 +++++----- test/general/openpgp.js | 23 ++--------------------- test/worker/async_proxy.js | 15 ++++++--------- 6 files changed, 33 insertions(+), 53 deletions(-) diff --git a/src/crypto/random.js b/src/crypto/random.js index e00af509..e714df31 100644 --- a/src/crypto/random.js +++ b/src/crypto/random.js @@ -37,7 +37,7 @@ export default { * @param {Integer} length Length in bytes to generate * @return {Uint8Array} Random byte array */ - getRandomBytes: function(length) { + getRandomBytes: async function(length) { const buf = new Uint8Array(length); if (typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues) { window.crypto.getRandomValues(buf); @@ -47,7 +47,7 @@ export default { const bytes = nodeCrypto.randomBytes(buf.length); buf.set(bytes); } else if (this.randomBuffer.buffer) { - this.randomBuffer.get(buf); + await this.randomBuffer.get(buf); } else { throw new Error('No secure random number generator available.'); } @@ -60,7 +60,7 @@ export default { * @param {module:type/mpi} max Upper bound, excluded * @return {module:BN} Random MPI */ - getRandomBN: function(min, max) { + getRandomBN: async function(min, max) { if (max.cmp(min) <= 0) { throw new Error('Illegal parameter value: max <= min'); } @@ -71,7 +71,7 @@ export default { // 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. // 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); }, @@ -121,7 +121,7 @@ RandomBuffer.prototype.set = function(buf) { * Take numbers out of buffer and copy to array * @param {Uint8Array} buf the destination array */ -RandomBuffer.prototype.get = function(buf) { +RandomBuffer.prototype.get = async function(buf) { if (!this.buffer) { throw new Error('RandomBuffer is not initialized'); } @@ -129,7 +129,12 @@ RandomBuffer.prototype.get = function(buf) { throw new Error('Invalid type: buf not an Uint8Array'); } 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++) { buf[i] = this.buffer[--this.size]; diff --git a/src/worker/async_proxy.js b/src/worker/async_proxy.js index eb404ed3..e5f3f45a 100644 --- a/src/worker/async_proxy.js +++ b/src/worker/async_proxy.js @@ -19,8 +19,6 @@ import util from '../util.js'; import crypto from '../crypto'; import packet from '../packet'; -const INITIAL_RANDOM_SEED = 50000; // random bytes seeded to worker - /** * Initializes a new proxy and loads the web worker * @constructor @@ -35,7 +33,6 @@ export default function AsyncProxy({ path='openpgp.worker.js', worker, config } 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) { this.worker.postMessage({ event:'configure', config }); diff --git a/test/crypto/crypto.js b/test/crypto/crypto.js index d63c5231..212a38a3 100644 --- a/test/crypto/crypto.js +++ b/test/crypto/crypto.js @@ -278,19 +278,19 @@ describe('API functional testing', function() { }); function testCFB(plaintext, resync) { - symmAlgos.forEach(function(algo) { - const symmKey = crypto.generateSessionKey(algo); - const symmencData = crypto.cfb.encrypt(crypto.getPrefixRandom(algo), algo, util.str_to_Uint8Array(plaintext), symmKey, resync); + symmAlgos.forEach(async function(algo) { + const symmKey = await crypto.generateSessionKey(algo); + 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)); expect(text).to.equal(plaintext); }); } function testAESCFB(plaintext) { - symmAlgos.forEach(function(algo) { + symmAlgos.forEach(async function(algo) { if(algo.substr(0,3) === 'aes') { - const symmKey = crypto.generateSessionKey(algo); - const rndm = crypto.getPrefixRandom(algo); + const symmKey = await crypto.generateSessionKey(algo); + const rndm = await crypto.getPrefixRandom(algo); const repeat = new Uint8Array([rndm[rndm.length - 2], rndm[rndm.length - 1]]); const prefix = util.concatUint8Array([rndm, repeat]); @@ -307,9 +307,9 @@ describe('API functional testing', function() { function testAESGCM(plaintext) { symmAlgos.forEach(function(algo) { if(algo.substr(0,3) === 'aes') { - it(algo, function() { - const key = crypto.generateSessionKey(algo); - const iv = crypto.random.getRandomBytes(crypto.gcm.ivLength); + it(algo, async function() { + const key = await crypto.generateSessionKey(algo); + const iv = await crypto.random.getRandomBytes(crypto.gcm.ivLength); return crypto.gcm.encrypt( algo, util.str_to_Uint8Array(plaintext), key, iv diff --git a/test/crypto/random.js b/test/crypto/random.js index 3e53fa12..4c8056d7 100644 --- a/test/crypto/random.js +++ b/test/crypto/random.js @@ -12,9 +12,9 @@ describe('Random Buffer', function() { 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.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 () { @@ -56,13 +56,13 @@ describe('Random Buffer', function() { expect(randomBuffer.size).to.equal(1); }); - it('Get Method', function () { + it('Get Method', async function () { randomBuffer.init(5); let buf = new Uint8Array(5); buf[0] = 1; buf[1] = 2; buf[2] = 5; buf[3] = 7; buf[4] = 8; randomBuffer.set(buf); 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); randomBuffer.get(buf); 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(equal(randomBuffer.buffer, [1,0,0,0,0])).to.be.true; 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'); }); }); diff --git a/test/general/openpgp.js b/test/general/openpgp.js index 5bbe68ee..fb72aebb 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -896,25 +896,6 @@ describe('OpenPGP.js public api tests', function() { }); }); - 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 with multiple private keys', function () { const privKeyDE = openpgp.key.readArmored(priv_key_de).keys[0]; privKeyDE.decrypt(passphrase); @@ -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 = { - data: openpgp.crypto.generateSessionKey('aes256'), + data: await openpgp.crypto.generateSessionKey('aes256'), algorithm: 'aes256' }; const encOpt = { diff --git a/test/worker/async_proxy.js b/test/worker/async_proxy.js index f7bcd4de..6fb765c6 100644 --- a/test/worker/async_proxy.js +++ b/test/worker/async_proxy.js @@ -47,15 +47,12 @@ tryTests('Async Proxy', tests, { function tests() { - describe('Error handling', function() { - it('Depleted random buffer in worker gives error', function() { - const wProxy = new openpgp.AsyncProxy({ path:'../dist/openpgp.worker.js' }); - wProxy.worker = new Worker('../dist/openpgp.worker.js'); - wProxy.worker.onmessage = wProxy.onMessage.bind(wProxy); - wProxy.seedRandom(10); - return wProxy.delegate('encrypt', { publicKeys:[pubKey], data:plaintext }).catch(function(err) { - expect(err.message).to.match(/Random number buffer depleted/); - }); + describe('Random number pipeline', function() { + it('Random number buffer automatically reseeded', function() { + const worker = new Worker('../dist/openpgp.worker.js'); + const wProxy = new openpgp.AsyncProxy({ path:'../dist/openpgp.worker.js', worker }); + + return wProxy.delegate('encrypt', { publicKeys:[pubKey], data:plaintext }); }); }); From 2bb5db2cf414e1cea17273fb2dbeab4aaf036dbc Mon Sep 17 00:00:00 2001 From: Bart Butler Date: Mon, 5 Mar 2018 21:36:53 -0800 Subject: [PATCH 5/6] multiple web workers --- src/openpgp.js | 11 ++-- src/worker/async_proxy.js | 116 +++++++++++++++++++++++-------------- test/general/openpgp.js | 4 +- test/worker/async_proxy.js | 2 +- 4 files changed, 82 insertions(+), 51 deletions(-) diff --git a/src/openpgp.js b/src/openpgp.js index a7c0d048..e7c9c64e 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -55,12 +55,13 @@ let asyncProxy; // instance of the asyncproxy /** * 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 {Object} worker alternative to path parameter: web worker initialized with 'openpgp.worker.js' + * @param {String} path relative path to the worker scripts, default: 'openpgp.worker.js' + * @param {Number} n number of workers to initialize + * @param {Array} workers alternative to path parameter: web workers initialized with 'openpgp.worker.js' */ -export function initWorker({ path='openpgp.worker.js', worker } = {}) { - if (worker || (typeof window !== 'undefined' && window.Worker)) { - asyncProxy = new AsyncProxy({ path, worker, config }); +export function initWorker({ path='openpgp.worker.js', n = 1, workers = [] } = {}) { + if (workers.length || (typeof window !== 'undefined' && window.Worker)) { + asyncProxy = new AsyncProxy({ path, n, workers, config }); return true; } } diff --git a/src/worker/async_proxy.js b/src/worker/async_proxy.js index e5f3f45a..d44f87e4 100644 --- a/src/worker/async_proxy.js +++ b/src/worker/async_proxy.js @@ -19,24 +19,69 @@ import util from '../util.js'; import crypto from '../crypto'; import packet from '../packet'; +/** + * Message handling + */ +function handleMessage(id) { + 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[id].requests--; + break; + case 'request-seed': + this.seedRandom(id, msg.amount); + break; + default: + throw new Error('Unknown Worker Event.'); + } + }; +} + /** * Initializes a new proxy and loads the web worker * @constructor - * @param {String} path The path to the worker or 'openpgp.worker.js' by default - * @param {Object} config config The worker configuration - * @param {Object} worker alternative to path parameter: web worker initialized with 'openpgp.worker.js' + * @param {String} path The path to the worker or 'openpgp.worker.js' by default + * @param {Number} n number of workers to initialize + * @param {Object} config config The worker configuration + * @param {Array} worker alternative to path parameter: web worker initialized with 'openpgp.worker.js' * @return {Promise} */ -export default function AsyncProxy({ path='openpgp.worker.js', worker, 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 + ')'); - }; +export default function AsyncProxy({ path='openpgp.worker.js', n = 1, workers = [], config } = {}) { - if (config) { - this.worker.postMessage({ event:'configure', config }); + if (workers.length) { + 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 this.tasks = {}; @@ -51,47 +96,22 @@ AsyncProxy.prototype.getID = function() { 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(msg.amount); - break; - default: - throw new Error('Unknown Worker Event.'); - } -}; - /** * Send message to worker with random data * @param {Integer} size Number of bytes to send */ -AsyncProxy.prototype.seedRandom = async function(size) { +AsyncProxy.prototype.seedRandom = async function(id, size) { const buf = await crypto.random.getRandomBytes(size); - this.worker.postMessage({ event:'seed-random', buf }, util.getTransferables(buf)); + this.workers[id].postMessage({ event:'seed-random', buf }, util.getTransferables(buf)); }; /** - * Terminates the worker + * Terminates the workers */ AsyncProxy.prototype.terminate = function() { - this.worker.terminate(); + this.workers.forEach(worker => { + worker.terminate(); + }); }; /** @@ -101,11 +121,21 @@ AsyncProxy.prototype.terminate = function() { * @return {Promise} see the corresponding public api functions for their return types */ AsyncProxy.prototype.delegate = function(method, options) { + 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) => { // 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 this.tasks[id] = { resolve: data => resolve(packet.clone.parseClonedPackets(data, method)), reject }; diff --git a/test/general/openpgp.js b/test/general/openpgp.js index fb72aebb..e678d292 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -363,7 +363,7 @@ describe('OpenPGP.js public api tests', function() { postMessage: function() {} }; openpgp.initWorker({ - worker: workerStub + workers: [workerStub] }); expect(openpgp.getWorker()).to.exist; openpgp.destroyWorker(); @@ -522,7 +522,7 @@ describe('OpenPGP.js public api tests', function() { postMessage: function() {} }; openpgp.initWorker({ - worker: workerStub + workers: [workerStub] }); const proxyGenStub = stub(openpgp.getWorker(), 'delegate'); getWebCryptoAllStub.returns(); diff --git a/test/worker/async_proxy.js b/test/worker/async_proxy.js index 6fb765c6..6c6d1c0b 100644 --- a/test/worker/async_proxy.js +++ b/test/worker/async_proxy.js @@ -50,7 +50,7 @@ function tests() { describe('Random number pipeline', function() { it('Random number buffer automatically reseeded', function() { const worker = new Worker('../dist/openpgp.worker.js'); - const wProxy = new openpgp.AsyncProxy({ path:'../dist/openpgp.worker.js', worker }); + const wProxy = new openpgp.AsyncProxy({ path:'../dist/openpgp.worker.js', workers: [worker] }); return wProxy.delegate('encrypt', { publicKeys:[pubKey], data:plaintext }); }); From 1cd90183464d214ef22eca2d6669ee6f9560638a Mon Sep 17 00:00:00 2001 From: Bart Butler Date: Tue, 6 Mar 2018 10:04:33 -0800 Subject: [PATCH 6/6] some cleanup --- src/key.js | 73 ++-- src/message.js | 351 +++++++++--------- src/openpgp.js | 30 +- src/packet/packetlist.js | 6 +- .../public_key_encrypted_session_key.js | 13 +- src/packet/secret_key.js | 14 +- src/packet/signature.js | 4 +- src/packet/sym_encrypted_aead_protected.js | 6 +- .../sym_encrypted_integrity_protected.js | 5 +- src/packet/sym_encrypted_session_key.js | 14 +- src/packet/symmetrically_encrypted.js | 19 +- src/worker/async_proxy.js | 13 +- 12 files changed, 286 insertions(+), 262 deletions(-) diff --git a/src/key.js b/src/key.js index 5af81553..8c989b96 100644 --- a/src/key.js +++ b/src/key.js @@ -153,7 +153,7 @@ Key.prototype.packetlist2structure = function(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() { 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 * @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)} */ Key.prototype.getKeyPackets = function(packetKeyId) { @@ -229,7 +229,7 @@ Key.prototype.getKeyPackets = function(packetKeyId) { /** * Returns userids - * @return {Array} array of userids + * @returns {Array} array of userids */ Key.prototype.getUserIds = function() { const userids = []; @@ -243,7 +243,7 @@ Key.prototype.getUserIds = function() { /** * Returns true if this is a public key - * @return {Boolean} + * @returns {Boolean} */ Key.prototype.isPublic = function() { return this.primaryKey.tag === enums.packet.publicKey; @@ -251,7 +251,7 @@ Key.prototype.isPublic = function() { /** * Returns true if this is a private key - * @return {Boolean} + * @returns {Boolean} */ Key.prototype.isPrivate = function() { return this.primaryKey.tag === enums.packet.secretKey; @@ -259,7 +259,7 @@ Key.prototype.isPrivate = function() { /** * 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() { const packetlist = new packet.List(); @@ -290,7 +290,7 @@ Key.prototype.toPublic = function() { /** * Returns ASCII armored text of key - * @return {String} ASCII armor + * @returns {String} ASCII armor */ Key.prototype.armor = function() { 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 * @param {module:type/keyid} keyId, optional * @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()) { const primaryUser = this.getPrimaryUser(date); @@ -379,6 +379,7 @@ Key.prototype.getEncryptionKeyPacket = function(keyId, date=new Date()) { /** * Encrypts all secret key and subkey packets * @param {String} passphrase + * @returns {Promise} */ Key.prototype.encrypt = async function(passphrase) { if (!this.isPrivate()) { @@ -397,7 +398,7 @@ Key.prototype.encrypt = async function(passphrase) { /** * Decrypts all secret key and subkey packets * @param {String} passphrase - * @return {Promise} true if all key and subkey packets decrypted successfully + * @returns {Promise} true if all key and subkey packets decrypted successfully */ Key.prototype.decrypt = async function(passphrase) { if (!this.isPrivate()) { @@ -413,7 +414,7 @@ Key.prototype.decrypt = async function(passphrase) { * Decrypts specific key packets by key ID * @param {Array} keyIds * @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) { if (this.isPrivate()) { @@ -439,7 +440,7 @@ Key.prototype.decryptKeyPacket = function(keyIds, passphrase) { * Verify primary key. Checks for revocation signatures, expiration time * and valid self signature * @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()) { // TODO clarify OpenPGP's behavior given an expired revocation signature @@ -481,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 - * @return {Date|null} + * @returns {Date|null} */ Key.prototype.getExpirationTime = function() { if (this.primaryKey.version === 3) { @@ -516,7 +517,7 @@ function getExpirationTime(keyPacket, selfCertificate, defaultValue=null) { * * NOTE: call verifyPrimaryUser before calling this function. * @param {Date} date use the given date for verification instead of the current time - * @return {{user: Array, selfCertificate: Array}|null} The primary user and the self signature + * @returns {{user: Array, selfCertificate: Array}|null} The primary user and the self signature */ Key.prototype.getPrimaryUser = function(date=new Date()) { let primaryUsers = []; @@ -649,7 +650,7 @@ Key.prototype.revoke = function() { /** * Signs primary user of key * @param {Array} privateKey decrypted private keys for signing - * @return {module:key~Key} new public key with new certificate signature + * @returns {Promise} new public key with new certificate signature */ Key.prototype.signPrimaryUser = async function(privateKeys) { await this.verifyPrimaryUser(); @@ -666,7 +667,7 @@ Key.prototype.signPrimaryUser = async function(privateKeys) { /** * Signs all users of key * @param {Array} privateKeys decrypted private keys for signing - * @return {module:key~Key} new public key with new certificate signature + * @returns {Promise} new public key with new certificate signature */ Key.prototype.signAllUsers = async function(privateKeys) { const that = this; @@ -682,7 +683,7 @@ Key.prototype.signAllUsers = async function(privateKeys) { * - if no arguments are given, verifies the self certificates; * - otherwise, verifies all certificates signed with given keys. * @param {Array} 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>} list of signer's keyid and validity of signature */ Key.prototype.verifyPrimaryUser = async function(keys) { const { primaryKey } = this; @@ -732,7 +733,7 @@ Key.prototype.verifyPrimaryUser = async function(keys) { * - if no arguments are given, verifies the self certificates; * - otherwise, verifies all certificates signed with given keys. * @param {Array} 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>} list of userid, signer's keyid and validity of signature */ Key.prototype.verifyAllUsers = async function(keys) { const results = []; @@ -768,7 +769,7 @@ function User(userPacket) { /** * Transforms structured user data to packetlist - * @return {module:packet/packetlist} + * @returns {module:packet/packetlist} */ User.prototype.toPacketlist = function() { const packetlist = new packet.List(); @@ -785,7 +786,7 @@ User.prototype.toPacketlist = function() { * @param {module:packet/signature} certificate The certificate to verify * @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 - * @return {Boolean} True if the certificate is revoked + * @returns {Promise} True if the certificate is revoked */ User.prototype.isRevoked = async function(primaryKey, certificate, key) { certificate.revoked = null; @@ -807,7 +808,7 @@ User.prototype.isRevoked = async function(primaryKey, certificate, key) { * Signs user * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet * @param {Array} privateKeys decrypted private keys for signing - * @return {module:key~Key} new user with new certificate signatures + * @returns {Promise} new user with new certificate signatures */ User.prototype.sign = async function(primaryKey, privateKeys) { const dataToSign = { userid: this.userId || this.userAttribute, key: primaryKey }; @@ -848,7 +849,7 @@ User.prototype.sign = async function(primaryKey, privateKeys) { * @param {module:packet/signature} certificate A certificate of this user * @param {Array} keys array of keys to verify certificate signatures * @param {Date} date use the given date for verification instead of the current time - * @return {module:enums.keyStatus} status of the certificate + * @returns {Promise} status of the certificate */ User.prototype.verifyCertificate = async function(primaryKey, certificate, keys, date=new Date()) { const that = this; @@ -876,7 +877,7 @@ User.prototype.verifyCertificate = async function(primaryKey, certificate, keys, * Verifies all user certificates * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet * @param {Array} 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>} list of signer's keyid and validity of signature */ User.prototype.verifyAllCertifications = async function(primaryKey, keys) { const that = this; @@ -894,7 +895,7 @@ User.prototype.verifyAllCertifications = async function(primaryKey, keys) { * Verify User. Checks for existence of self signatures, revocation signatures * and validity of self signature * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet - * @return {module:enums.keyStatus} status of user + * @returns {Promise} status of user */ User.prototype.verify = async function(primaryKey) { if (!this.selfCertifications) { @@ -922,7 +923,7 @@ User.prototype.verify = async function(primaryKey) { /** * Update user with new components from specified user * @param {module:key~User} user source user to merge - * @param {module:packet/signature} primaryKey primary key used for validation + * @param {Promise} primaryKey primary key used for validation */ User.prototype.update = async function(user, primaryKey) { const dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; @@ -951,7 +952,7 @@ function SubKey(subKeyPacket) { /** * Transforms structured subkey data to packetlist - * @return {module:packet/packetlist} + * @returns {module:packet/packetlist} */ SubKey.prototype.toPacketlist = function() { const packetlist = new packet.List(); @@ -967,7 +968,7 @@ SubKey.prototype.toPacketlist = function() { * 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 {Date} date use the given date for verification instead of the current time - * @return {Boolean} + * @returns {Promise} */ SubKey.prototype.isValidEncryptionKey = async function(primaryKey, date=new Date()) { if (await this.verify(primaryKey, date) !== enums.keyStatus.valid) { @@ -985,7 +986,7 @@ SubKey.prototype.isValidEncryptionKey = async function(primaryKey, date=new Date * 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 {Date} date use the given date for verification instead of the current time - * @return {Boolean} + * @returns {Promise} */ SubKey.prototype.isValidSigningKey = async function(primaryKey, date=new Date()) { if (await this.verify(primaryKey, date) !== enums.keyStatus.valid) { @@ -1004,7 +1005,7 @@ SubKey.prototype.isValidSigningKey = async function(primaryKey, date=new Date()) * and valid binding signature * @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 - * @return {module:enums.keyStatus} The status of the subkey + * @returns {Promise} The status of the subkey */ SubKey.prototype.verify = async function(primaryKey, date=new Date()) { const that = this; @@ -1051,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 - * @return {Date|null} + * @returns {Date|null} */ SubKey.prototype.getExpirationTime = function() { let highest; @@ -1070,7 +1071,7 @@ SubKey.prototype.getExpirationTime = function() { /** * Update subkey with new components from specified subkey * @param {module:key~SubKey} subKey source subkey to merge - * @param {module:packet/signature} primaryKey primary key used for validation + * @param {Promise} primaryKey primary key used for validation */ SubKey.prototype.update = async function(subKey, primaryKey) { if (await subKey.verify(primaryKey) === enums.keyStatus.invalid) { @@ -1112,7 +1113,7 @@ SubKey.prototype.update = async function(subKey, primaryKey) { /** * Reads an unarmored OpenPGP key list and returns one or multiple key objects * @param {Uint8Array} data to be parsed - * @return {{keys: Array, err: (Array|null)}} result object with key and error arrays + * @returns {{keys: Array, err: (Array|null)}} result object with key and error arrays * @static */ export function read(data) { @@ -1145,7 +1146,7 @@ export function read(data) { /** * Reads an OpenPGP armored text and returns one or multiple key objects * @param {String} armoredText text to be parsed - * @return {{keys: Array, err: (Array|null)}} result object with key and error arrays + * @returns {{keys: Array, err: (Array|null)}} result object with key and error arrays * @static */ export function readArmored(armoredText) { @@ -1173,7 +1174,7 @@ export function readArmored(armoredText) { * @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 {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 */ export function generate(options) { @@ -1248,7 +1249,7 @@ export function generate(options) { * @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 {Number} [options.keyExpirationTime=0] The number of seconds after the key creation time that the key expires - * @return {module:key~Key} + * @returns {Promise} * @static */ export async function reformat(options) { @@ -1382,7 +1383,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { /** * Returns the preferred signature hash algorithm of a key * @param {object} key - * @return {String} + * @returns {String} */ export function getPreferredHashAlgo(key) { let hash_algo = config.prefer_hash_algorithm; @@ -1416,7 +1417,7 @@ export function getPreferredHashAlgo(key) { /** * Returns the preferred symmetric algorithm for a set of keys * @param {Array} keys Set of keys - * @return {enums.symmetric} Preferred symmetric algorithm + * @returns {enums.symmetric} Preferred symmetric algorithm */ export function getPreferredSymAlgo(keys) { const prioMap = {}; diff --git a/src/message.js b/src/message.js index 40788a4a..2c39b1e1 100644 --- a/src/message.js +++ b/src/message.js @@ -55,7 +55,7 @@ export function Message(packetlist) { /** * Returns the key IDs of the keys to which the session key is encrypted - * @return {Array} array of keyid objects + * @returns {Array} array of keyid objects */ Message.prototype.getEncryptionKeyIds = function() { const keyIds = []; @@ -68,7 +68,7 @@ Message.prototype.getEncryptionKeyIds = function() { /** * Returns the key IDs of the keys that signed the message - * @return {Array} array of keyid objects + * @returns {Array} array of keyid objects */ Message.prototype.getSigningKeyIds = function() { const keyIds = []; @@ -93,7 +93,7 @@ Message.prototype.getSigningKeyIds = function() { * @param {Array} privateKeys (optional) private keys with decrypted secret data * @param {Array} passwords (optional) passwords used to decrypt * @param {Array} sessionKeys (optional) session keys in the form: { data:Uint8Array, algorithm:String } - * @return {Message} new message with decrypted content + * @returns {Promise} new message with decrypted content */ Message.prototype.decrypt = async function(privateKeys, passwords, sessionKeys) { const keyObjs = sessionKeys || await this.decryptSessionKeys(privateKeys, passwords); @@ -105,7 +105,7 @@ Message.prototype.decrypt = async function(privateKeys, passwords, sessionKeys) ); if (symEncryptedPacketlist.length === 0) { - return; + return this; } 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. * @param {Array} privateKeys (optional) private keys with decrypted secret data * @param {Array} 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 = []; - 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) { - const pkESKeyPacketlist = this.packets.filterByTag(enums.packet.publicKeyEncryptedSessionKey); - if (!pkESKeyPacketlist) { - throw new Error('No public key encrypted session key packet found.'); - } - 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) {} - })); + 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 { - 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 - * @return {(Uint8Array|null)} literal body of the message as Uint8Array + * @returns {(Uint8Array|null)} literal body of the message as Uint8Array */ Message.prototype.getLiteralData = function() { const literal = this.packets.findPacket(enums.packet.literal); @@ -214,7 +213,7 @@ Message.prototype.getLiteralData = function() { /** * 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() { const literal = this.packets.findPacket(enums.packet.literal); @@ -223,7 +222,7 @@ Message.prototype.getFilename = function() { /** * 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() { 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 {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 - * @return {Message} new message with encrypted content + * @returns {Promise} 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 msg; let symEncryptedPacket; - return Promise.resolve().then(async () => { - if (sessionKey) { - if (!util.isUint8Array(sessionKey.data) || !util.isString(sessionKey.algorithm)) { - 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.'); + + if (sessionKey) { + if (!util.isUint8Array(sessionKey.data) || !util.isString(sessionKey.algorithm)) { + 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.'); + } - if (!sessionKey) { - sessionKey = await crypto.generateSessionKey(symAlgo); + if (!sessionKey) { + 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} passwords (optional) for message encryption * @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 - * @return {Message} new message with encrypted content + * @returns {Promise} 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(); - return Promise.resolve().then(async () => { - if (publicKeys) { - const results = await Promise.all(publicKeys.map(async function(key) { - await key.verifyPrimaryUser(); - const encryptionKeyPacket = key.getEncryptionKeyPacket(undefined, date); - if (!encryptionKeyPacket) { - throw new Error('Could not find valid key packet for encryption in key ' + key.primaryKey.getKeyId().toHex()); + if (publicKeys) { + const results = await Promise.all(publicKeys.map(async function(key) { + await key.verifyPrimaryUser(); + const encryptionKeyPacket = key.getEncryptionKeyPacket(undefined, date); + if (!encryptionKeyPacket) { + 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) { - const testDecrypt = async function(keyPacket, password) { - try { - await keyPacket.decrypt(password); - return 1; - } catch (e) { - return 0; - } - }; + delete symEncryptedSessionKeyPacket.sessionKey; // delete plaintext session key after encryption + return symEncryptedSessionKeyPacket; + }; - 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) { - 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)); + return new Message(packetlist); } /** @@ -363,7 +360,7 @@ export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords, wi * @param {Array} privateKeys private keys with decrypted secret key data for signing * @param {Signature} signature (optional) any existing detached signature to add to the message * @param {Date} date} (optional) override the creation time of the signature - * @return {module:message~Message} new message with signed content + * @returns {Promise} new message with signed content */ Message.prototype.sign = async function(privateKeys=[], signature=null, date=new Date()) { 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) * @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) { 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) - * @param {Array} privateKeys private keys with decrypted secret key data for signing - * @param {Signature} signature (optional) any existing detached signature - * @param {Date} date (optional) override the creation time of the signature - * @return {module:signature~Signature} new detached signature of message content + * @param {Array} privateKeys private keys with decrypted secret key data for signing + * @param {Signature} signature (optional) any existing detached signature + * @param {Date} date (optional) override the creation time of the signature + * @returns {Promise} new detached signature of message content */ Message.prototype.signDetached = async function(privateKeys=[], signature=null, date=new Date()) { 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 - * @param {module:packet/literal} literalDataPacket the literal data packet to sign - * @param {Array} privateKeys private keys with decrypted secret key data for signing - * @param {Signature} signature (optional) any existing detached signature to append - * @param {Date} date (optional) override the creationtime of the signature - * @return {module:packet/packetlist} list of signature packets + * @param {module:packet/literal} literalDataPacket the literal data packet to sign + * @param {Array} privateKeys private keys with decrypted secret key data for signing + * @param {Signature} signature (optional) any existing detached signature to append + * @param {Date} date (optional) override the creationtime of the signature + * @returns {Promise} list of signature packets */ export async function createSignaturePackets(literalDataPacket, privateKeys, signature=null, date=new Date()) { const packetlist = new packet.List(); @@ -507,7 +504,7 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig * Verify message signatures * @param {Array} 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 - * @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()) { const msg = this.unwrapCompressed(); @@ -524,7 +521,7 @@ Message.prototype.verify = function(keys, date=new Date()) { * @param {Array} keys array of keys to verify signatures * @param {Signature} signature * @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()) { const msg = this.unwrapCompressed(); @@ -542,7 +539,7 @@ Message.prototype.verifyDetached = function(signature, keys, date=new Date()) { * @param {Array} literalDataList array of literal data packets * @param {Array} 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 - * @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()) { return Promise.all(signatureList.map(async function(signature) { @@ -572,7 +569,7 @@ export async function createVerificationObjects(signatureList, literalDataList, /** * 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() { const compressed = this.packets.filterByTag(enums.packet.compressed); @@ -592,7 +589,7 @@ Message.prototype.appendSignature = function(detachedSignature) { /** * Returns ASCII armored text of message - * @return {String} ASCII armor + * @returns {String} ASCII armor */ Message.prototype.armor = function() { 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 * @param {String} armoredText text to be parsed - * @return {module:message~Message} new message object + * @returns {module:message~Message} new message object * @static */ export function readArmored(armoredText) { @@ -614,7 +611,7 @@ export function readArmored(armoredText) { /** * reads an OpenPGP message as byte array and returns a message object * @param {Uint8Array} input binary message - * @return {Message} new message object + * @returns {Message} new message object * @static */ export function read(input) { @@ -628,7 +625,7 @@ export function read(input) { * @param {String} text * @param {String} filename (optional) * @param {Date} date (optional) - * @return {module:message~Message} new message object + * @returns {module:message~Message} new message object * @static */ export function fromText(text, filename, date=new Date()) { @@ -648,7 +645,7 @@ export function fromText(text, filename, date=new Date()) { * @param {Uint8Array} bytes * @param {String} filename (optional) * @param {Date} date (optional) - * @return {module:message~Message} new message object + * @returns {module:message~Message} new message object * @static */ export function fromBinary(bytes, filename, date=new Date()) { diff --git a/src/openpgp.js b/src/openpgp.js index e7c9c64e..4f73229a 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -68,7 +68,7 @@ export function initWorker({ path='openpgp.worker.js', n = 1, workers = [] } = { /** * 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() { return asyncProxy; @@ -97,7 +97,7 @@ export function destroyWorker() { * @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 {Number} keyExpirationTime (optional) The number of seconds after the key creation time that the key expires - * @return {Promise} The generated key object in the form: + * @returns {Promise} The generated key object in the form: * { key:Key, privateKeyArmored:String, publicKeyArmored:String } * @static */ @@ -134,7 +134,7 @@ export function generateKey({ * @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 {Number} keyExpirationTime (optional) The number of seconds after the key creation time that the key expires - * @return {Promise} The generated key object in the form: + * @returns {Promise} The generated key object in the form: * { key:Key, privateKeyArmored:String, publicKeyArmored:String } * @static */ @@ -164,7 +164,7 @@ export function reformatKey({ * Unlock 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 - * @return {Key} the unlocked private key + * @returns {Promise} the unlocked key object in the form: { key:Key } */ export function decryptKey({ privateKey, passphrase }) { if (asyncProxy) { // use web worker if available @@ -184,7 +184,7 @@ export function decryptKey({ privateKey, passphrase }) { * 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 - * @return {Key} the locked private key + * @returns {Promise} the locked key object in the form: { key:Key } */ export function encryptKey({ privateKey, passphrase }) { if (asyncProxy) { // use web worker if available @@ -224,7 +224,7 @@ export function encryptKey({ privateKey, passphrase }) { * @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 {Date} date (optional) override the creation date of the message and the message signature - * @return {Promise} encrypted (and optionally signed message) in the form: + * @returns {Promise} encrypted (and optionally signed message) in the form: * {data: ASCII armored message if 'armor' is true, * message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true} * @static @@ -276,7 +276,7 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, sessionKey, * @param {String} format (optional) return data format either as 'utf8' or 'binary' * @param {Signature} signature (optional) detached signature for verification * @param {Date} date (optional) use the given date for verification instead of the current time - * @return {Promise} decrypted and verified message in the form: + * @returns {Promise} decrypted and verified message in the form: * { data:Uint8Array|String, filename:String, signatures:[{ keyid:String, valid:Boolean }] } * @static */ @@ -315,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} detached (optional) if the return value should contain a detached signature * @param {Date} date (optional) override the creation date signature - * @return {Promise} signed cleartext in the form: + * @returns {Promise} signed cleartext in the form: * {data: ASCII armored message if 'armor' is true, * message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true} * @static @@ -357,7 +357,7 @@ export function sign({ * @param {CleartextMessage} message cleartext message object with signatures * @param {Signature} signature (optional) detached signature for verification * @param {Date} date (optional) use the given date for verification instead of the current time - * @return {Promise} cleartext with status of verified signatures in the form of: + * @returns {Promise} cleartext with status of verified signatures in the form of: * { data:String, signatures: [{ keyid:String, valid:Boolean }] } * @static */ @@ -393,7 +393,7 @@ export function verify({ message, publicKeys, signature=null, date=new Date() }) * @param {Key|Array} publicKeys (optional) array of public keys or single key, used to encrypt the key * @param {String|Array} passwords (optional) passwords for the message * @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs - * @return {Promise} the encrypted session key packets contained in a message object + * @returns {Promise} the encrypted session key packets contained in a message object * @static */ export function encryptSessionKey({ data, algorithm, publicKeys, passwords, wildcard=false }) { @@ -416,7 +416,7 @@ export function encryptSessionKey({ data, algorithm, publicKeys, passwords, wild * @param {Message} message a message object containing the encrypted session key packets * @param {Key|Array} privateKeys (optional) private keys with decrypted secret key data * @param {String|Array} passwords (optional) passwords to decrypt the session key - * @return {Promise} Array of decrypted session key, algorithm pairs in form: + * @returns {Promise} Array of decrypted session key, algorithm pairs in form: * { data:Uint8Array, algorithm:String } * or 'undefined' if no key packets found * @static @@ -505,7 +505,7 @@ function formatUserIds(userIds) { /** * Normalize parameter to an array if it is not undefined. * @param {Object} param the parameter to be normalized - * @return {Array|undefined} the resulting array or undefined + * @returns {Array|undefined} the resulting array or undefined */ function toArray(param) { if (param && !util.isArray(param)) { @@ -519,7 +519,7 @@ function toArray(param) { * @param {String|Uint8Array} data the payload for the message * @param {String} filename the literal data packet's filename * @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()) { let msg; @@ -537,7 +537,7 @@ function createMessage(data, filename, date=new Date()) { * Parse the message given a certain format. * @param {Message} message the message object to be parse * @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) { if (format === 'binary') { @@ -572,7 +572,7 @@ function onError(message, error) { /** * Check for AES-GCM support and configuration by the user. Only browsers that * 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() { return util.getWebCrypto() && config.aead_protect; diff --git a/src/packet/packetlist.js b/src/packet/packetlist.js index 41946213..35c561c7 100644 --- a/src/packet/packetlist.js +++ b/src/packet/packetlist.js @@ -91,7 +91,7 @@ Packetlist.prototype.push = function (packet) { /** * 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() { if (this.length === 0) { @@ -164,6 +164,8 @@ Packetlist.prototype.map = function (callback) { /** * Executes the callback function once for each element * until it finds one where callback returns a truthy value +* @param {Function} callback +* @returns {Promise} */ Packetlist.prototype.some = async function (callback) { 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 * @param {module:enums.packet} type The packet type - * @return {module:packet/packet|null} + * @returns {module:packet/packet|null} */ Packetlist.prototype.findPacket = function (type) { const packetlist = this.filterByTag(type); diff --git a/src/packet/public_key_encrypted_session_key.js b/src/packet/public_key_encrypted_session_key.js index a14a67cb..23a90fdb 100644 --- a/src/packet/public_key_encrypted_session_key.js +++ b/src/packet/public_key_encrypted_session_key.js @@ -67,7 +67,7 @@ export default function PublicKeyEncryptedSessionKey() { * @param {Integer} position Position to start reading from the input string * @param {Integer} len Length of the packet or the remaining length of * 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) { this.version = bytes[0]; @@ -88,7 +88,7 @@ PublicKeyEncryptedSessionKey.prototype.read = function (bytes) { /** * Create a string representation of a tag 1 packet * - * @return {Uint8Array} The Uint8Array representation + * @returns {Uint8Array} The Uint8Array representation */ PublicKeyEncryptedSessionKey.prototype.write = function () { 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); }; +/** + * Encrypt session key packet + * @param {module:packet/public_key} key Public key + * @returns {Promise} + */ PublicKeyEncryptedSessionKey.prototype.encrypt = async function (key) { let data = String.fromCharCode(enums.write(enums.symmetric, this.sessionKeyAlgorithm)); @@ -117,6 +122,7 @@ PublicKeyEncryptedSessionKey.prototype.encrypt = async function (key) { this.encrypted = await crypto.publicKeyEncrypt( algo, key.params, toEncrypt, key.fingerprint); + return true; }; /** @@ -125,7 +131,7 @@ PublicKeyEncryptedSessionKey.prototype.encrypt = async function (key) { * * @param {module:packet/secret_key} key * Private key with secret params unlocked - * @return {String} The unencrypted session key + * @returns {Promise} */ PublicKeyEncryptedSessionKey.prototype.decrypt = async function (key) { const algo = enums.write(enums.publicKey, this.publicKeyAlgorithm); @@ -150,6 +156,7 @@ PublicKeyEncryptedSessionKey.prototype.decrypt = async function (key) { this.sessionKey = key; this.sessionKeyAlgorithm = enums.read(enums.symmetric, decoded.charCodeAt(0)); } + return true; }; /** diff --git a/src/packet/secret_key.js b/src/packet/secret_key.js index e7046f52..13fd624d 100644 --- a/src/packet/secret_key.js +++ b/src/packet/secret_key.js @@ -151,7 +151,7 @@ SecretKey.prototype.read = function (bytes) { }; /** 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 () { const arr = [this.writePublicKey()]; @@ -172,11 +172,12 @@ SecretKey.prototype.write = function () { * 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(). * @param {String} passphrase + * @returns {Promise} */ SecretKey.prototype.encrypt = async function (passphrase) { if (this.isDecrypted && !passphrase) { this.encrypted = null; - return; + return false; } else if (!passphrase) { throw new Error('The key must be decrypted before removing passphrase protection.'); } @@ -195,6 +196,7 @@ SecretKey.prototype.encrypt = async function (passphrase) { arr.push(crypto.cfb.normalEncrypt(symmetric, key, cleartext, iv)); this.encrypted = util.concatUint8Array(arr); + return true; }; function produceEncryptionKey(s2k, passphrase, algorithm) { @@ -209,10 +211,8 @@ function produceEncryptionKey(s2k, passphrase, algorithm) { * @link module:packet/secret_key.isDecrypted should be * false otherwise a call to this function is not needed * - * @param {String} str_passphrase The passphrase for this private key - * as string - * @return {Boolean} True if the passphrase was correct or param already - * decrypted; false if not + * @param {String} passphrase The passphrase for this private key as string + * @returns {Promise} */ SecretKey.prototype.decrypt = async function (passphrase) { if (this.isDecrypted) { @@ -267,6 +267,8 @@ SecretKey.prototype.decrypt = async function (passphrase) { this.params = this.params.concat(privParams); this.isDecrypted = true; this.encrypted = null; + + return true; }; SecretKey.prototype.generate = function (bits, curve) { diff --git a/src/packet/signature.js b/src/packet/signature.js index b1449a17..4e0fd88d 100644 --- a/src/packet/signature.js +++ b/src/packet/signature.js @@ -211,6 +211,7 @@ Signature.prototype.write = function () { * 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 {Object} data Contains packets to be signed. + * @returns {Promise} */ Signature.prototype.sign = async function (key, data) { 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( 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 {module:packet/public_subkey|module:packet/public_key| * 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} True if message is verified, else false. */ Signature.prototype.verify = async function (key, data) { const signatureType = enums.write(enums.signature, this.signatureType); diff --git a/src/packet/sym_encrypted_aead_protected.js b/src/packet/sym_encrypted_aead_protected.js index a6fbe934..af469913 100644 --- a/src/packet/sym_encrypted_aead_protected.js +++ b/src/packet/sym_encrypted_aead_protected.js @@ -64,19 +64,21 @@ SymEncryptedAEADProtected.prototype.write = function () { * Decrypt the encrypted payload. * @param {String} sessionKeyAlgorithm The session key's cipher algorithm e.g. 'aes128' * @param {Uint8Array} key The session key used to encrypt the payload - * @return {Promise} Nothing is returned + * @return {Promise} */ SymEncryptedAEADProtected.prototype.decrypt = async function (sessionKeyAlgorithm, key) { this.packets.read(await crypto.gcm.decrypt(sessionKeyAlgorithm, this.encrypted, key, this.iv)); + return true; }; /** * Encrypt the packet list payload. * @param {String} sessionKeyAlgorithm The session key's cipher algorithm e.g. 'aes128' * @param {Uint8Array} key The session key used to encrypt the payload - * @return {Promise} Nothing is returned + * @return {Promise} */ SymEncryptedAEADProtected.prototype.encrypt = async function (sessionKeyAlgorithm, key) { this.iv = await crypto.random.getRandomBytes(IV_LEN); // generate new random IV this.encrypted = await crypto.gcm.encrypt(sessionKeyAlgorithm, this.packets.write(), key, this.iv); + return true; }; diff --git a/src/packet/sym_encrypted_integrity_protected.js b/src/packet/sym_encrypted_integrity_protected.js index 6b8c7c29..335dfd5b 100644 --- a/src/packet/sym_encrypted_integrity_protected.js +++ b/src/packet/sym_encrypted_integrity_protected.js @@ -79,7 +79,7 @@ SymEncryptedIntegrityProtected.prototype.write = function () { * Encrypt the payload in the packet. * @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 - * @return {Promise} + * @return {Promise} */ SymEncryptedIntegrityProtected.prototype.encrypt = async function (sessionKeyAlgorithm, key) { const bytes = this.packets.write(); @@ -98,13 +98,14 @@ SymEncryptedIntegrityProtected.prototype.encrypt = async function (sessionKeyAlg this.encrypted = crypto.cfb.encrypt(prefixrandom, sessionKeyAlgorithm, tohash, key, false); this.encrypted = this.encrypted.subarray(0, prefix.length + tohash.length); } + return true; }; /** * Decrypts the encrypted data contained in the packet. * @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 - * @return {Promise} + * @return {Promise} */ SymEncryptedIntegrityProtected.prototype.decrypt = async function (sessionKeyAlgorithm, key) { let decrypted; diff --git a/src/packet/sym_encrypted_session_key.js b/src/packet/sym_encrypted_session_key.js index 248b13cc..3884f072 100644 --- a/src/packet/sym_encrypted_session_key.js +++ b/src/packet/sym_encrypted_session_key.js @@ -102,10 +102,9 @@ SymEncryptedSessionKey.prototype.write = function() { }; /** - * Decrypts the session key (only for public key encrypted session key - * packets (tag 1) - * - * @return {Uint8Array} The unencrypted session key + * Decrypts the session key + * @param {String} passphrase The passphrase in string form + * @return {Promise} */ SymmetricallyEncrypted.prototype.decrypt = async function (sessionKeyAlgorithm, key) { const decrypted = crypto.cfb.decrypt(sessionKeyAlgorithm, key, this.encrypted, true); @@ -73,13 +71,20 @@ SymmetricallyEncrypted.prototype.decrypt = async function (sessionKeyAlgorithm, } this.packets.read(decrypted); - return Promise.resolve(); + return true; }; +/** + * 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} + */ SymmetricallyEncrypted.prototype.encrypt = async function (algo, key) { const data = this.packets.write(); this.encrypted = crypto.cfb.encrypt(await crypto.getPrefixRandom(algo), algo, data, key, true); - return Promise.resolve(); + return true; }; diff --git a/src/worker/async_proxy.js b/src/worker/async_proxy.js index d44f87e4..2d8469bb 100644 --- a/src/worker/async_proxy.js +++ b/src/worker/async_proxy.js @@ -22,7 +22,7 @@ import packet from '../packet'; /** * Message handling */ -function handleMessage(id) { +function handleMessage(workerId) { return function(event) { const msg = event.data; switch (msg.event) { @@ -38,10 +38,10 @@ function handleMessage(id) { this.tasks[msg.id].resolve(msg.data); } delete this.tasks[msg.id]; - this.workers[id].requests--; + this.workers[workerId].requests--; break; case 'request-seed': - this.seedRandom(id, msg.amount); + this.seedRandom(workerId, msg.amount); break; default: throw new Error('Unknown Worker Event.'); @@ -53,10 +53,9 @@ function handleMessage(id) { * Initializes a new proxy and loads the web worker * @constructor * @param {String} path The path to the worker or 'openpgp.worker.js' by default - * @param {Number} n number of workers to initialize + * @param {Number} n number of workers to initialize if path given * @param {Object} config config The worker configuration * @param {Array} worker alternative to path parameter: web worker initialized with 'openpgp.worker.js' - * @return {Promise} */ export default function AsyncProxy({ path='openpgp.worker.js', n = 1, workers = [], config } = {}) { @@ -100,9 +99,9 @@ AsyncProxy.prototype.getID = function() { * Send message to worker with random data * @param {Integer} size Number of bytes to send */ -AsyncProxy.prototype.seedRandom = async function(id, size) { +AsyncProxy.prototype.seedRandom = async function(workerId, size) { const buf = await crypto.random.getRandomBytes(size); - this.workers[id].postMessage({ event:'seed-random', buf }, util.getTransferables(buf)); + this.workers[workerId].postMessage({ event:'seed-random', buf }, util.getTransferables(buf)); }; /**