From 3612fc12dc04d4d092f37de5b2bd7994f73d3a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Obernd=C3=B6rfer?= Date: Mon, 13 Jan 2014 13:56:06 +0100 Subject: [PATCH 1/9] Add web worker support. Load the whole library in a web worker and make the high-level API accessible from an asynchronous proxy. Entropy is seeded to worker on each generateKeyPair() call. Allow serialization of packets and custom types for messaging API. --- src/crypto/random.js | 24 +- src/index.js | 5 + src/packet/all_packets.js | 26 ++- src/packet/compressed.js | 5 + src/packet/literal.js | 1 + src/packet/marker.js | 4 + src/packet/one_pass_signature.js | 8 + src/packet/packetlist.js | 19 ++ src/packet/public_key.js | 10 + .../public_key_encrypted_session_key.js | 11 + src/packet/public_subkey.js | 7 +- src/packet/secret_key.js | 1 + src/packet/secret_subkey.js | 5 +- src/packet/signature.js | 10 +- .../sym_encrypted_integrity_protected.js | 5 +- src/packet/sym_encrypted_session_key.js | 9 +- src/packet/symmetrically_encrypted.js | 5 +- src/packet/trust.js | 6 +- src/packet/user_attribute.js | 5 +- src/packet/userid.js | 5 +- src/type/keyid.js | 6 + src/type/mpi.js | 9 + src/type/s2k.js | 9 + src/worker/async_proxy.js | 211 ++++++++++++++++++ src/worker/worker.js | 113 ++++++++++ 25 files changed, 499 insertions(+), 20 deletions(-) create mode 100644 src/worker/async_proxy.js create mode 100644 src/worker/worker.js diff --git a/src/crypto/random.js b/src/crypto/random.js index 6f4b551d..9c85ca5f 100644 --- a/src/crypto/random.js +++ b/src/crypto/random.js @@ -24,6 +24,8 @@ var type_mpi = require('../type/mpi.js'); var nodeCrypto = null; +var randomBuffer = null; +var randomBufferIndex = 0; if (typeof window === 'undefined') { nodeCrypto = require('crypto'); @@ -79,11 +81,20 @@ module.exports = { * @param {Uint32Array} buf */ getRandomValues: function(buf) { - if (nodeCrypto === null) { + if (typeof window !== 'undefined' && window.crypto) { window.crypto.getRandomValues(buf); - } else { + } else if (nodeCrypto) { var bytes = nodeCrypto.randomBytes(4); buf[0] = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]; + } else if (randomBuffer) { + if (randomBufferIndex < buf.length) { + throw new Error('Random number buffer depleted.') + } + for (var i = 0; i < buf.length; i++) { + buf[i] = randomBuffer[--randomBufferIndex]; + } + } else { + throw new Error('No secure random number generator available.'); } }, @@ -122,6 +133,15 @@ module.exports = { r = this.getRandomBigInteger(range.bitLength()); } return min.add(r); + }, + + /** + * Set array of random numbers to buffer + * @param {Uint32Array} buf + */ + seedRandom: function(buf) { + randomBuffer = buf; + randomBufferIndex = buf.length; } }; diff --git a/src/index.js b/src/index.js index 711a8a55..4a2a0972 100644 --- a/src/index.js +++ b/src/index.js @@ -65,3 +65,8 @@ module.exports.crypto = require('./crypto'); * @name module:openpgp.Keyring */ module.exports.Keyring = require('./keyring'); +/** + * @see module:worker/async_proxy + * @name module:openpgp.AsyncProxy + */ +module.exports.AsyncProxy = require('./worker/async_proxy.js'); diff --git a/src/packet/all_packets.js b/src/packet/all_packets.js index 9f743830..bf8a6c94 100644 --- a/src/packet/all_packets.js +++ b/src/packet/all_packets.js @@ -46,6 +46,25 @@ module.exports = { */ newPacketFromTag: function (tag) { return new this[packetClassFromTagName(tag)](); + }, + /** + * Allocate a new packet from structured packet clone + * See {@link http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#safe-passing-of-structured-data} + * @param {Object} packetClone packet clone + * @returns {Object} new packet object with data from packet clone + */ + fromStructuredClone: function(packetClone) { + var tagName = enums.read(enums.packet, packetClone.tag) + var packet = this.newPacketFromTag(tagName); + for (var attr in packetClone) { + if (packetClone.hasOwnProperty(attr)) { + packet[attr] = packetClone[attr]; + } + } + if (packet.postCloneTypeFix) { + packet.postCloneTypeFix(); + } + return packet; } }; @@ -57,10 +76,3 @@ module.exports = { function packetClassFromTagName(tag) { return tag.substr(0, 1).toUpperCase() + tag.substr(1); } - -for (var i in enums.packet) { - var packetClass = module.exports[packetClassFromTagName(i)]; - - if (packetClass !== undefined) - packetClass.prototype.tag = enums.packet[i]; -} diff --git a/src/packet/compressed.js b/src/packet/compressed.js index 3b80e9ba..e464e91f 100644 --- a/src/packet/compressed.js +++ b/src/packet/compressed.js @@ -37,6 +37,11 @@ var enums = require('../enums.js'), * @constructor */ function Compressed() { + /** + * Packet type + * @type {module:enums.packet} + */ + this.tag = enums.packet.compressed; /** * List of packets * @type {module:packet/packetlist} diff --git a/src/packet/literal.js b/src/packet/literal.js index 40de1257..1a58d680 100644 --- a/src/packet/literal.js +++ b/src/packet/literal.js @@ -34,6 +34,7 @@ var util = require('../util.js'), * @constructor */ function Literal() { + this.tag = enums.packet.literal; this.format = 'utf8'; // default format for literal data packets this.data = ''; // literal data representation as native JavaScript string or bytes this.date = new Date(); diff --git a/src/packet/marker.js b/src/packet/marker.js index 75ee8a77..9c43a221 100644 --- a/src/packet/marker.js +++ b/src/packet/marker.js @@ -25,15 +25,19 @@ * the Marker packet.
*
* Such a packet MUST be ignored when received. + * @requires enums * @module packet/marker */ module.exports = Marker; +var enums = require('../enums.js'); + /** * @constructor */ function Marker() { + this.tag = enums.packet.marker; } /** diff --git a/src/packet/one_pass_signature.js b/src/packet/one_pass_signature.js index d5f53a9d..f330e728 100644 --- a/src/packet/one_pass_signature.js +++ b/src/packet/one_pass_signature.js @@ -37,6 +37,7 @@ var enums = require('../enums.js'), * @constructor */ function OnePassSignature() { + this.tag = enums.packet.onePassSignature; // The packet type this.version = null; // A one-octet version number. The current version is 3. this.type = null; // A one-octet signature type. Signature types are described in {@link http://tools.ietf.org/html/rfc4880#section-5.2.1|RFC4880 Section 5.2.1}. this.hashAlgorithm = null; // A one-octet number describing the hash algorithm used. (See {@link http://tools.ietf.org/html/rfc4880#section-9.4|RFC4880 9.4}) @@ -94,3 +95,10 @@ OnePassSignature.prototype.write = function () { return result; }; + +/** + * Fix custom types after cloning + */ +OnePassSignature.prototype.postCloneTypeFix = function() { + this.signingKeyId = type_keyid.fromClone(this.signingKeyId); +}; diff --git a/src/packet/packetlist.js b/src/packet/packetlist.js index 07284b7f..ab2ecdcc 100644 --- a/src/packet/packetlist.js +++ b/src/packet/packetlist.js @@ -175,3 +175,22 @@ Packetlist.prototype.concat = function (packetlist) { } } }; + +/** + * Allocate a new packetlist from structured packetlist clone + * See {@link http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#safe-passing-of-structured-data} + * @param {Object} packetClone packetlist clone + * @returns {Object} new packetlist object with data from packetlist clone + */ +module.exports.fromStructuredClone = function(packetlistClone) { + var packetlist = new Packetlist(); + for (var i = 0; i < packetlistClone.length; i++) { + packetlist.push(packets.fromStructuredClone(packetlistClone[i])); + if (packetlist[i].packets.length !== 0) { + packetlist[i].packets = this.fromStructuredClone(packetlist[i].packets); + } else { + packetlist[i].packets = new Packetlist(); + } + } + return packetlist; +}; \ No newline at end of file diff --git a/src/packet/public_key.js b/src/packet/public_key.js index 30935f52..f751ea60 100644 --- a/src/packet/public_key.js +++ b/src/packet/public_key.js @@ -42,6 +42,7 @@ var util = require('../util.js'), * @constructor */ function PublicKey() { + this.tag = enums.packet.publicKey; this.version = 4; /** Key creation date. * @type {Date} */ @@ -183,3 +184,12 @@ PublicKey.prototype.getFingerprint = function () { return crypto.hash.md5(toHash); } }; + +/** + * Fix custom types after cloning + */ +PublicKey.prototype.postCloneTypeFix = function() { + for (var i = 0; i < this.mpi.length; i++) { + this.mpi[i] = type_mpi.fromClone(this.mpi[i]); + } +}; diff --git a/src/packet/public_key_encrypted_session_key.js b/src/packet/public_key_encrypted_session_key.js index 676046bb..dc16e30e 100644 --- a/src/packet/public_key_encrypted_session_key.js +++ b/src/packet/public_key_encrypted_session_key.js @@ -49,6 +49,7 @@ var type_keyid = require('../type/keyid.js'), * @constructor */ function PublicKeyEncryptedSessionKey() { + this.tag = enums.packet.publicKeyEncryptedSessionKey; this.version = 3; this.publicKeyId = new type_keyid(); @@ -182,3 +183,13 @@ PublicKeyEncryptedSessionKey.prototype.decrypt = function (key) { enums.read(enums.symmetric, decoded.charCodeAt(0)); } }; + +/** + * Fix custom types after cloning + */ +PublicKeyEncryptedSessionKey.prototype.postCloneTypeFix = function() { + this.publicKeyId = type_keyid.fromClone(this.publicKeyId); + for (var i = 0; i < this.encrypted.length; i++) { + this.encrypted[i] = type_mpi.fromClone(this.encrypted[i]); + } +}; diff --git a/src/packet/public_subkey.js b/src/packet/public_subkey.js index 966db261..6e198712 100644 --- a/src/packet/public_subkey.js +++ b/src/packet/public_subkey.js @@ -17,12 +17,14 @@ /** * @requires packet/public_key + * @requires enums * @module packet/public_subkey */ module.exports = PublicSubkey; -var publicKey = require('./public_key.js'); +var publicKey = require('./public_key.js'), + enums = require('../enums.js'); /** * @constructor @@ -30,7 +32,8 @@ var publicKey = require('./public_key.js'); */ function PublicSubkey() { publicKey.call(this); -}; + this.tag = enums.packet.publicSubkey; +} PublicSubkey.prototype = new publicKey(); PublicSubkey.prototype.constructor = PublicSubkey; diff --git a/src/packet/secret_key.js b/src/packet/secret_key.js index 6324ec7b..74a17aa6 100644 --- a/src/packet/secret_key.js +++ b/src/packet/secret_key.js @@ -46,6 +46,7 @@ var publicKey = require('./public_key.js'), */ function SecretKey() { publicKey.call(this); + this.tag = enums.packet.secretKey; // encrypted secret-key data this.encrypted = null; // indicator if secret-key data is available in decrypted form diff --git a/src/packet/secret_subkey.js b/src/packet/secret_subkey.js index 614c6288..839d774c 100644 --- a/src/packet/secret_subkey.js +++ b/src/packet/secret_subkey.js @@ -17,12 +17,14 @@ /** * @requires packet/secret_key + * @requires enums * @module packet/secret_subkey */ module.exports = SecretSubkey; -var secretKey = require('./secret_key.js'); +var secretKey = require('./secret_key.js'), + enums = require('../enums.js'); /** * @constructor @@ -30,6 +32,7 @@ var secretKey = require('./secret_key.js'); */ function SecretSubkey() { secretKey.call(this); + this.tag = enums.packet.secretSubkey; } SecretSubkey.prototype = new secretKey(); diff --git a/src/packet/signature.js b/src/packet/signature.js index 657f1ed2..dee37696 100644 --- a/src/packet/signature.js +++ b/src/packet/signature.js @@ -44,7 +44,7 @@ var util = require('../util.js'), * @constructor */ function Signature() { - + this.tag = enums.packet.signature; this.version = 4; this.signatureType = null; this.hashAlgorithm = null; @@ -52,7 +52,6 @@ function Signature() { this.signatureData = null; this.signedHashValue = null; - this.mpi = null; this.created = new Date(); this.signatureExpirationTime = null; @@ -638,3 +637,10 @@ Signature.prototype.isExpired = function () { } return false; }; + +/** + * Fix custom types after cloning + */ +Signature.prototype.postCloneTypeFix = function() { + this.issuerKeyId = type_keyid.fromClone(this.issuerKeyId); +}; diff --git a/src/packet/sym_encrypted_integrity_protected.js b/src/packet/sym_encrypted_integrity_protected.js index 353946d1..39b1f71b 100644 --- a/src/packet/sym_encrypted_integrity_protected.js +++ b/src/packet/sym_encrypted_integrity_protected.js @@ -27,18 +27,21 @@ * packet. * @requires crypto * @requires util + * @requires enums * @module packet/sym_encrypted_integrity_protected */ module.exports = SymEncryptedIntegrityProtected; var util = require('../util.js'), - crypto = require('../crypto'); + crypto = require('../crypto'), + enums = require('../enums.js'); /** * @constructor */ function SymEncryptedIntegrityProtected() { + this.tag = enums.packet.symEncryptedIntegrityProtected; /** The encrypted payload. */ this.encrypted = null; // string /** diff --git a/src/packet/sym_encrypted_session_key.js b/src/packet/sym_encrypted_session_key.js index d4ffe624..b66f2f26 100644 --- a/src/packet/sym_encrypted_session_key.js +++ b/src/packet/sym_encrypted_session_key.js @@ -45,7 +45,7 @@ module.exports = SymEncryptedSessionKey; * @constructor */ function SymEncryptedSessionKey() { - this.tag = 3; + this.tag = enums.packet.symEncryptedSessionKey; this.sessionKeyEncryptionAlgorithm = null; this.sessionKeyAlgorithm = 'aes256'; this.encrypted = null; @@ -140,3 +140,10 @@ SymEncryptedSessionKey.prototype.encrypt = function(passphrase) { crypto.getPrefixRandom(this.sessionKeyEncryptionAlgorithm), this.sessionKeyEncryptionAlgorithm, key, private_key, true); }; + +/** + * Fix custom types after cloning + */ +SymEncryptedSessionKey.prototype.postCloneTypeFix = function() { + this.s2k = type_s2k.fromClone(this.s2k); +}; diff --git a/src/packet/symmetrically_encrypted.js b/src/packet/symmetrically_encrypted.js index 3c3020fe..faaa38a0 100644 --- a/src/packet/symmetrically_encrypted.js +++ b/src/packet/symmetrically_encrypted.js @@ -24,17 +24,20 @@ * theory other Symmetrically Encrypted Data packets or sequences of packets * that form whole OpenPGP messages). * @requires crypto + * @requires enums * @module packet/symmetrically_encrypted */ module.exports = SymmetricallyEncrypted; -var crypto = require('../crypto'); +var crypto = require('../crypto'), + enums = require('../enums.js'); /** * @constructor */ function SymmetricallyEncrypted() { + this.tag = enums.packet.symmetricallyEncrypted; this.encrypted = null; /** Decrypted packets contained within. * @type {module:packet/packetlist} */ diff --git a/src/packet/trust.js b/src/packet/trust.js index e96b3c33..73914ae5 100644 --- a/src/packet/trust.js +++ b/src/packet/trust.js @@ -1,11 +1,15 @@ /** + * @requires enums * @module packet/trust */ module.exports = Trust; +var enums = require('../enums.js'); + /** * @constructor */ function Trust() { -}; + this.tag = enums.packet.trust; +} diff --git a/src/packet/user_attribute.js b/src/packet/user_attribute.js index 42c41a65..d14727d3 100644 --- a/src/packet/user_attribute.js +++ b/src/packet/user_attribute.js @@ -32,11 +32,13 @@ * User Attribute packet as a User ID packet with opaque contents, but * an implementation may use any method desired. * module packet/user_attribute + * @requires enums * @module packet/user_attribute */ var util = require('../util.js'), - packet = require('./packet.js'); + packet = require('./packet.js'), + enums = require('../enums.js'); module.exports = UserAttribute; @@ -44,6 +46,7 @@ module.exports = UserAttribute; * @constructor */ function UserAttribute() { + this.tag = enums.packet.userAttribute; this.attributes = []; } diff --git a/src/packet/userid.js b/src/packet/userid.js index 285fdcb5..73a00d11 100644 --- a/src/packet/userid.js +++ b/src/packet/userid.js @@ -24,17 +24,20 @@ * restrictions on its content. The packet length in the header * specifies the length of the User ID. * @requires util + * @requires enums * @module packet/userid */ module.exports = Userid; -var util = require('../util.js'); +var util = require('../util.js'), + enums = require('../enums.js'); /** * @constructor */ function Userid() { + this.tag = enums.packet.userid; /** A string containing the user id. Usually in the form * John Doe * @type {String} diff --git a/src/type/keyid.js b/src/type/keyid.js index 78aa0c31..c9dcdaab 100644 --- a/src/type/keyid.js +++ b/src/type/keyid.js @@ -65,3 +65,9 @@ Keyid.prototype.isNull = function() { module.exports.mapToHex = function (keyId) { return keyId.toHex(); }; + +module.exports.fromClone = function (clone) { + var keyid = new Keyid(); + keyid.bytes = clone.bytes; + return keyid; +}; diff --git a/src/type/mpi.js b/src/type/mpi.js index 6ea7a2e3..85bd8c4e 100644 --- a/src/type/mpi.js +++ b/src/type/mpi.js @@ -100,3 +100,12 @@ MPI.prototype.toBigInteger = function () { MPI.prototype.fromBigInteger = function (bn) { this.data = bn.clone(); }; + +module.exports.fromClone = function (clone) { + clone.data.copyTo = BigInteger.prototype.copyTo; + var bn = new BigInteger(); + clone.data.copyTo(bn); + var mpi = new MPI(); + mpi.data = bn; + return mpi; +}; diff --git a/src/type/s2k.js b/src/type/s2k.js index 91701ac2..b5f71002 100644 --- a/src/type/s2k.js +++ b/src/type/s2k.js @@ -178,3 +178,12 @@ S2K.prototype.produce_key = function (passphrase, numBytes) { return result.substr(0, numBytes); }; + +module.exports.fromClone = function (clone) { + var s2k = new S2K(); + this.algorithm = clone.algorithm; + this.type = clone.type; + this.c = clone.c; + this.salt = clone.salt; + return s2k; +}; diff --git a/src/worker/async_proxy.js b/src/worker/async_proxy.js new file mode 100644 index 00000000..7bedf69d --- /dev/null +++ b/src/worker/async_proxy.js @@ -0,0 +1,211 @@ +// GPG4Browsers - An OpenPGP implementation in javascript +// Copyright (C) 2011 Recurity Labs GmbH +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +var crypto = require('../crypto'), + packet = require('../packet'), + key = require('../key.js'), + type_keyid = require('../type/keyid.js'); + +function AsyncProxy(path) { + this.worker = new Worker(path || 'openpgp.worker.js'); + this.worker.onmessage = this.onMessage.bind(this); + // FIFO + this.tasks = []; +} + +AsyncProxy.prototype.onMessage = function(event) { + switch (event.data.event) { + case 'method-return': + this.tasks.shift()(event.data.err, event.data.data); + break; + default: + throw new Error('Unknown Worker Event.'); + } +}; + +AsyncProxy.prototype.seedRandom = function(size) { + var buf = new Uint32Array(size); + crypto.random.getRandomValues(buf); + this.worker.postMessage({event: 'seed-random', buf: buf}); +}; + +/** + * Terminates the worker + */ +AsyncProxy.prototype.terminate = function() { + this.worker.terminate(); +}; + +/** + * Encrypts message text with keys + * @param {Array} keys array of keys, used to encrypt the message + * @param {String} text message as native JavaScript string + * @param {Function} callback receives encrypted ASCII armored message + */ +AsyncProxy.prototype.encryptMessage = function(keys, text, callback) { + keys = keys.map(function(key) { + return key.toPacketlist(); + }); + this.worker.postMessage({ + event: 'encrypt-message', + keys: keys, + text: text + }); + this.tasks.push(callback); +}; + +/** + * Signs message text and encrypts it + * @param {Array} publicKeys array of keys, used to encrypt the message + * @param {module:key~Key} privateKey private key with decrypted secret key data for signing + * @param {String} text message as native JavaScript string + * @param {Function} callback receives encrypted ASCII armored message + */ +AsyncProxy.prototype.signAndEncryptMessage = function(publicKeys, privateKey, text, callback) { + publicKeys = publicKeys.map(function(key) { + return key.toPacketlist(); + }); + privateKey = privateKey.toPacketlist(); + this.worker.postMessage({ + event: 'sign-and-encrypt-message', + publicKeys: publicKeys, + privateKey: privateKey, + text: text + }); + this.tasks.push(callback); +}; + +/** + * Decrypts message + * @param {module:key~Key} privateKey private key with decrypted secret key data + * @param {module:message~Message} message the message object with the encrypted data + * @param {Function} callback receives decrypted message as as native JavaScript string + * or null if no literal data found + */ +AsyncProxy.prototype.decryptMessage = function(privateKey, message, callback) { + privateKey = privateKey.toPacketlist(); + this.worker.postMessage({ + event: 'decrypt-message', + privateKey: privateKey, + message: message + }); + this.tasks.push(callback); +}; + +/** + * Decrypts message and verifies signatures + * @param {module:key~Key} privateKey private key with decrypted secret key data + * @param {Array} publicKeys public keys to verify signatures + * @param {module:message~Message} message the message object with signed and encrypted data + * @param {Function} callback receives decrypted message as as native JavaScript string + * with verified signatures or null if no literal data found + */ +AsyncProxy.prototype.decryptAndVerifyMessage = function(privateKey, publicKeys, message, callback) { + privateKey = privateKey.toPacketlist(); + publicKeys = publicKeys.map(function(key) { + return key.toPacketlist(); + }); + this.worker.postMessage({ + event: 'decrypt-and-verify-message', + privateKey: privateKey, + publicKeys: publicKeys, + message: message + }); + this.tasks.push(function(err, data) { + if (data) { + data.signatures = data.signatures.map(function(sig) { + sig.keyid = type_keyid.fromClone(sig.keyid); + return sig; + }); + } + callback(err, data); + }); +}; + +/** + * Signs a cleartext message + * @param {Array} privateKeys private key with decrypted secret key data to sign cleartext + * @param {String} text cleartext + * @param {Function} callback receives ASCII armored message + */ +AsyncProxy.prototype.signClearMessage = function(privateKeys, text, callback) { + privateKeys = privateKeys.map(function(key) { + return key.toPacketlist(); + }); + this.worker.postMessage({ + event: 'sign-clear-message', + privateKeys: privateKeys, + text: text + }); + this.tasks.push(callback); +}; + +/** + * Verifies signatures of cleartext signed message + * @param {Array} publicKeys public keys to verify signatures + * @param {module:cleartext~CleartextMessage} message cleartext message object with signatures + * @param {Function} callback receives cleartext with status of verified signatures + */ +AsyncProxy.prototype.verifyClearSignedMessage = function(publicKeys, message, callback) { + publicKeys = publicKeys.map(function(key) { + return key.toPacketlist(); + }); + this.worker.postMessage({ + event: 'verify-clear-signed-message', + publicKeys: publicKeys, + message: message + }); + this.tasks.push(function(err, data) { + if (data) { + data.signatures = data.signatures.map(function(sig) { + sig.keyid = type_keyid.fromClone(sig.keyid); + return sig; + }); + } + callback(err, data); + }); +}; + +/** + * Generates a new OpenPGP key pair. Currently only supports RSA keys. + * Primary and subkey will be of same type. + * @param {module:enums.publicKey} keyType to indicate what type of key to make. + * RSA is 1. See {@link http://tools.ietf.org/html/rfc4880#section-9.1} + * @param {Integer} numBits number of bits for the key creation. (should be 1024+, generally) + * @param {String} userId assumes already in form of "User Name " + * @param {String} passphrase The passphrase used to encrypt the resulting private key + * @param {Function} callback receives object with key and public and private armored texts + */ +AsyncProxy.prototype.generateKeyPair = function(keyType, numBits, userId, passphrase, callback) { + this.seedRandom(Math.round(numBits / 2)); + this.worker.postMessage({ + event: 'generate-key-pair', + keyType: keyType, + numBits: numBits, + userId: userId, + passphrase: passphrase + }); + this.tasks.push(function(err, data) { + if (data) { + var packetlist = packet.List.fromStructuredClone(data.key); + data.key = new key.Key(packetlist); + } + callback(err, data); + }); +}; + +module.exports = AsyncProxy; diff --git a/src/worker/worker.js b/src/worker/worker.js new file mode 100644 index 00000000..1a9ff2b2 --- /dev/null +++ b/src/worker/worker.js @@ -0,0 +1,113 @@ +// GPG4Browsers - An OpenPGP implementation in javascript +// Copyright (C) 2011 Recurity Labs GmbH +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +var window = {}; // to make UMD bundles work + +importScripts('openpgp.js'); + + +onmessage = function (event) { + var data = null, + err = null, + msg = event.data; + switch (msg.event) { + case 'seed-random': + window.openpgp.crypto.random.seedRandom(msg.buf); + break; + case 'encrypt-message': + try { + msg.keys = msg.keys.map(packetlistCloneToKey); + data = window.openpgp.encryptMessage(msg.keys, msg.text); + } catch (e) { + err = e; + } + postMessage({event: 'method-return', data: data, err: err}); + break; + case 'sign-and-encrypt-message': + try { + msg.publicKeys = msg.publicKeys.map(packetlistCloneToKey); + msg.privateKey = packetlistCloneToKey(msg.privateKey); + data = window.openpgp.signAndEncryptMessage(msg.publicKeys, msg.privateKey, msg.text); + } catch (e) { + err = e; + } + postMessage({event: 'method-return', data: data, err: err}); + break; + case 'decrypt-message': + try { + msg.privateKey = packetlistCloneToKey(msg.privateKey); + msg.message = packetlistCloneToMessage(msg.message.packets); + data = window.openpgp.decryptMessage(msg.privateKey, msg.message); + } catch (e) { + err = e; + } + postMessage({event: 'method-return', data: data, err: err}); + break; + case 'decrypt-and-verify-message': + try { + msg.privateKey = packetlistCloneToKey(msg.privateKey); + msg.publicKeys = msg.publicKeys.map(packetlistCloneToKey); + msg.message = packetlistCloneToMessage(msg.message.packets); + data = window.openpgp.decryptAndVerifyMessage(msg.privateKey, msg.publicKeys, msg.message); + } catch (e) { + err = e; + } + postMessage({event: 'method-return', data: data, err: err}); + break; + case 'sign-clear-message': + try { + msg.privateKeys = msg.privateKeys.map(packetlistCloneToKey); + data = window.openpgp.signClearMessage(msg.privateKeys, msg.text); + } catch (e) { + err = e; + } + postMessage({event: 'method-return', data: data, err: err}); + break; + case 'verify-clear-signed-message': + try { + msg.publicKeys = msg.publicKeys.map(packetlistCloneToKey); + var packetlist = window.openpgp.packet.List.fromStructuredClone(msg.message.packets); + msg.message = new window.openpgp.cleartext.CleartextMessage(msg.message.text, packetlist); + data = window.openpgp.verifyClearSignedMessage(msg.publicKeys, msg.message); + } catch (e) { + err = e; + } + postMessage({event: 'method-return', data: data, err: err}); + break; + case 'generate-key-pair': + try { + data = window.openpgp.generateKeyPair(msg.keyType, msg.numBits, msg.userId, msg.passphrase); + data.key = data.key.toPacketlist(); + } catch (e) { + err = e; + } + postMessage({event: 'method-return', data: data, err: err}); + break; + default: + throw new Error('Unknown Worker Event.'); + } +}; + +function packetlistCloneToKey(packetlistClone) { + var packetlist = window.openpgp.packet.List.fromStructuredClone(packetlistClone); + return new window.openpgp.key.Key(packetlist); +} + +function packetlistCloneToMessage(packetlistClone) { + var packetlist = window.openpgp.packet.List.fromStructuredClone(packetlistClone); + return new window.openpgp.message.Message(packetlist); +} \ No newline at end of file From 17c6f90113d81f67125b3433634ba1a2c1addb94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Obernd=C3=B6rfer?= Date: Mon, 13 Jan 2014 15:42:03 +0100 Subject: [PATCH 2/9] Unit tests for web worker. Only active for browsers. --- Gruntfile.js | 5 ++ src/worker/async_proxy.js | 5 +- src/worker/worker.js | 16 ++--- test/unittests.js | 3 + test/worker/api.js | 137 ++++++++++++++++++++++++++++++++++++++ test/worker/index.js | 4 ++ 6 files changed, 160 insertions(+), 10 deletions(-) create mode 100644 test/worker/api.js create mode 100644 test/worker/index.js diff --git a/Gruntfile.js b/Gruntfile.js index 6eff3230..1e0ec693 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -23,6 +23,11 @@ module.exports = function(grunt) { external: [ 'crypto', 'node-localstorage' ] } }, + worker: { + files: { + 'dist/openpgp.worker.js': [ './src/worker/worker.js' ] + } + }, unittests: { files: { 'test/lib/unittests-bundle.js': [] diff --git a/src/worker/async_proxy.js b/src/worker/async_proxy.js index 7bedf69d..6b5b7852 100644 --- a/src/worker/async_proxy.js +++ b/src/worker/async_proxy.js @@ -28,9 +28,10 @@ function AsyncProxy(path) { } AsyncProxy.prototype.onMessage = function(event) { - switch (event.data.event) { + var msg = event.data; + switch (msg.event) { case 'method-return': - this.tasks.shift()(event.data.err, event.data.data); + this.tasks.shift()(msg.err ? new Error(msg.err) : null, msg.data); break; default: throw new Error('Unknown Worker Event.'); diff --git a/src/worker/worker.js b/src/worker/worker.js index 1a9ff2b2..a8b9c9a4 100644 --- a/src/worker/worker.js +++ b/src/worker/worker.js @@ -15,7 +15,7 @@ // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -var window = {}; // to make UMD bundles work +window = {}; // to make UMD bundles work importScripts('openpgp.js'); @@ -33,7 +33,7 @@ onmessage = function (event) { msg.keys = msg.keys.map(packetlistCloneToKey); data = window.openpgp.encryptMessage(msg.keys, msg.text); } catch (e) { - err = e; + err = e.message; } postMessage({event: 'method-return', data: data, err: err}); break; @@ -43,7 +43,7 @@ onmessage = function (event) { msg.privateKey = packetlistCloneToKey(msg.privateKey); data = window.openpgp.signAndEncryptMessage(msg.publicKeys, msg.privateKey, msg.text); } catch (e) { - err = e; + err = e.message; } postMessage({event: 'method-return', data: data, err: err}); break; @@ -53,7 +53,7 @@ onmessage = function (event) { msg.message = packetlistCloneToMessage(msg.message.packets); data = window.openpgp.decryptMessage(msg.privateKey, msg.message); } catch (e) { - err = e; + err = e.message; } postMessage({event: 'method-return', data: data, err: err}); break; @@ -64,7 +64,7 @@ onmessage = function (event) { msg.message = packetlistCloneToMessage(msg.message.packets); data = window.openpgp.decryptAndVerifyMessage(msg.privateKey, msg.publicKeys, msg.message); } catch (e) { - err = e; + err = e.message; } postMessage({event: 'method-return', data: data, err: err}); break; @@ -73,7 +73,7 @@ onmessage = function (event) { msg.privateKeys = msg.privateKeys.map(packetlistCloneToKey); data = window.openpgp.signClearMessage(msg.privateKeys, msg.text); } catch (e) { - err = e; + err = e.message; } postMessage({event: 'method-return', data: data, err: err}); break; @@ -84,7 +84,7 @@ onmessage = function (event) { msg.message = new window.openpgp.cleartext.CleartextMessage(msg.message.text, packetlist); data = window.openpgp.verifyClearSignedMessage(msg.publicKeys, msg.message); } catch (e) { - err = e; + err = e.message; } postMessage({event: 'method-return', data: data, err: err}); break; @@ -93,7 +93,7 @@ onmessage = function (event) { data = window.openpgp.generateKeyPair(msg.keyType, msg.numBits, msg.userId, msg.passphrase); data.key = data.key.toPacketlist(); } catch (e) { - err = e; + err = e.message; } postMessage({event: 'method-return', data: data, err: err}); break; diff --git a/test/unittests.js b/test/unittests.js index 4b66c6de..5705bf17 100644 --- a/test/unittests.js +++ b/test/unittests.js @@ -1,4 +1,7 @@ describe('Unit Tests', function () { require('./general'); require('./crypto'); + if (typeof window !== 'undefined') { + require('./worker'); + } }); diff --git a/test/worker/api.js b/test/worker/api.js new file mode 100644 index 00000000..8826312b --- /dev/null +++ b/test/worker/api.js @@ -0,0 +1,137 @@ +'use strict'; + +var openpgp = typeof window != 'undefined' && window.openpgp ? window.openpgp : require('../../src/index'); + +var chai = require('chai'), + expect = chai.expect; + +describe('High level API', function() { + + var proxy; + + this.timeout(50000); + + var pub_key = + ['-----BEGIN PGP PUBLIC KEY BLOCK-----', + 'Version: GnuPG v2.0.19 (GNU/Linux)', + 'Type: RSA/RSA', + '', + '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)', + 'Type: RSA/RSA', + 'Pwd: hello world', + '', + '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', + '=lw5e', + '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); + + var plaintext = 'short message\nnext line\n한국어/조선말'; + + var pubKey, privKey; + + it('Test initialization', function (done) { + pubKey = openpgp.key.readArmored(pub_key).keys[0]; + expect(pubKey).to.exist; + + privKey = openpgp.key.readArmored(priv_key).keys[0]; + expect(privKey).to.exist; + + done(); + }); + + it('Initialize AsyncProxy', function (done) { + proxy = new openpgp.AsyncProxy('../dist/openpgp.worker.js'); + expect(proxy).to.exist; + done(); + }); + + it('Generate key', function (done) { + proxy.generateKeyPair(3, 1024, 'Test McTestington ', 'hello world', function(err, data) { + expect(err).to.not.exist; + expect(data).to.exist; + expect(data.publicKeyArmored).to.match(/^-----BEGIN PGP PUBLIC/); + expect(data.privateKeyArmored).to.match(/^-----BEGIN PGP PRIVATE/); + expect(data.key).to.be.an.instanceof(openpgp.key.Key); + done(); + }); + }); + + it('Encrypt and decrypt text', function (done) { + proxy.encryptMessage([pubKey], plaintext, function(err, data) { + expect(err).to.not.exist; + expect(data).to.exist; + expect(data).to.match(/^-----BEGIN PGP MESSAGE/); + var msg = openpgp.message.readArmored(data); + expect(msg).to.be.an.instanceof(openpgp.message.Message); + proxy.decryptMessage(privKey, msg, function(err, data) { + expect(data).to.not.exist; + expect(err).to.exist; + expect(err.message).to.equal('Private key is not decrypted.'); + privKey.decrypt('hello world'); + proxy.decryptMessage(privKey, msg, function(err, data) { + expect(err).to.not.exist; + expect(data).to.exist; + expect(data).to.equal(plaintext); + done(); + }); + }); + }); + }); + +}); + + diff --git a/test/worker/index.js b/test/worker/index.js new file mode 100644 index 00000000..341bd56a --- /dev/null +++ b/test/worker/index.js @@ -0,0 +1,4 @@ +describe('Web Worker', function () { + require('./api.js'); +}); + From 1fa47f0b4b2704010203ede5d894dfc379647953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Obernd=C3=B6rfer?= Date: Wed, 15 Jan 2014 19:40:57 +0100 Subject: [PATCH 3/9] Web worker: add entropy estimation --- src/crypto/random.js | 69 ++++++++++++++++++------ src/packet/public_key.js | 8 +++ src/worker/async_proxy.js | 108 +++++++++++++++++++++++++++++++++++--- src/worker/worker.js | 32 +++++++---- 4 files changed, 184 insertions(+), 33 deletions(-) diff --git a/src/crypto/random.js b/src/crypto/random.js index 9c85ca5f..51caad81 100644 --- a/src/crypto/random.js +++ b/src/crypto/random.js @@ -24,8 +24,6 @@ var type_mpi = require('../type/mpi.js'); var nodeCrypto = null; -var randomBuffer = null; -var randomBufferIndex = 0; if (typeof window === 'undefined') { nodeCrypto = require('crypto'); @@ -86,13 +84,8 @@ module.exports = { } else if (nodeCrypto) { var bytes = nodeCrypto.randomBytes(4); buf[0] = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]; - } else if (randomBuffer) { - if (randomBufferIndex < buf.length) { - throw new Error('Random number buffer depleted.') - } - for (var i = 0; i < buf.length; i++) { - buf[i] = randomBuffer[--randomBufferIndex]; - } + } else if (this.randomBuffer.buffer) { + this.randomBuffer.get(buf); } else { throw new Error('No secure random number generator available.'); } @@ -135,13 +128,55 @@ module.exports = { return min.add(r); }, - /** - * Set array of random numbers to buffer - * @param {Uint32Array} buf - */ - seedRandom: function(buf) { - randomBuffer = buf; - randomBufferIndex = buf.length; - } + randomBuffer: new RandomBuffer() }; + +/** + * Buffer for secure random numbers + */ +function RandomBuffer() { + this.buffer = null; + this.size = null; +} + +/** + * Initialize buffer + * @param {Integer} size size of buffer + */ +RandomBuffer.prototype.init = function(size) { + this.buffer = new Uint32Array(size); + this.size = 0; +}; + +/** + * Concat array of secure random numbers to buffer + * @param {Uint32Array} buf + */ +RandomBuffer.prototype.set = function(buf) { + if (!this.buffer) { + throw new Error('RandomBuffer is not initialized'); + } + var freeSpace = this.buffer.length - this.size; + if (buf.length > freeSpace) { + buf = buf.subarray(0, freeSpace); + } + this.buffer.set(buf, this.size); + this.size += buf.length; +}; + +/** + * Take numbers out of buffer and copy to array + * @param {Uint32Array} buf the destination array + */ +RandomBuffer.prototype.get = function(buf) { + if (!this.buffer) { + throw new Error('RandomBuffer is not initialized'); + } + if (this.size < buf.length) { + throw new Error('Random number buffer depleted.') + } + for (var i = 0; i < buf.length; i++) { + buf[i] = this.buffer[--this.size]; + } +}; diff --git a/src/packet/public_key.js b/src/packet/public_key.js index f751ea60..35965c47 100644 --- a/src/packet/public_key.js +++ b/src/packet/public_key.js @@ -185,6 +185,14 @@ PublicKey.prototype.getFingerprint = function () { } }; +/** + * Returns bit size of key + * @return {int} Number of bits + */ +PublicKey.prototype.getBitSize = function () { + return this.mpi[0].byteLength() * 8; +}; + /** * Fix custom types after cloning */ diff --git a/src/worker/async_proxy.js b/src/worker/async_proxy.js index 6b5b7852..ec08c5c6 100644 --- a/src/worker/async_proxy.js +++ b/src/worker/async_proxy.js @@ -15,33 +15,76 @@ // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +/** + * @requires crypto + * @requires enums + * @requires packet + * @requires type_keyid + * @requires key + * @module async_proxy + */ + var crypto = require('../crypto'), packet = require('../packet'), key = require('../key.js'), - type_keyid = require('../type/keyid.js'); + type_keyid = require('../type/keyid.js'), + enums = require('../enums.js'); +var INITIAL_SEED = 4096, // bytes seeded to worker + SEED_REQUEST = 4096, // bytes seeded after worker request + RSA_FACTOR = 2, + DSA_FACTOR = 2, + ELG_FACTOR = 2; + +/** + * Initializes a new proxy and loads the web worker + * @constructor + * @param {String} path The path to the worker or 'openpgp.worker.js' by default + */ function AsyncProxy(path) { this.worker = new Worker(path || 'openpgp.worker.js'); this.worker.onmessage = this.onMessage.bind(this); + this.seedRandom(INITIAL_SEED); // FIFO this.tasks = []; } +/** + * Message handling + */ AsyncProxy.prototype.onMessage = function(event) { var msg = event.data; switch (msg.event) { case 'method-return': this.tasks.shift()(msg.err ? new Error(msg.err) : null, msg.data); break; + case 'request-seed': + this.seedRandom(SEED_REQUEST); + 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 = function(size) { + var buf = this.getRandomBuffer(size); + this.worker.postMessage({event: 'seed-random', buf: buf}); +}; + +/** + * Get Uint32Array with random numbers + * @param {Integer} size Length of buffer + * @return {Uint32Array} + */ +AsyncProxy.prototype.getRandomBuffer = function(size) { + if (!size) return null; var buf = new Uint32Array(size); crypto.random.getRandomValues(buf); - this.worker.postMessage({event: 'seed-random', buf: buf}); + return buf; }; /** @@ -51,6 +94,50 @@ AsyncProxy.prototype.terminate = function() { this.worker.terminate(); }; +/** + * Estimation on how much random bytes are required to process the operation + * @param {String} op 'enc', 'sig' or 'gen' + * @param {Array} publicKeys + * @param {Array} privateKeys + * @param {Object} options + * @return {Integer} number of bytes required + */ +AsyncProxy.prototype.entropyEstimation = function(op, publicKeys, privateKeys, options) { + var requ = 0; // required entropy in bytes + switch (op) { + case 'enc': + requ += 32; // max. size of session key + requ += 16; // max. size CFB prefix random + publicKeys && publicKeys.forEach(function(key) { + var subKeyPackets = key.getSubkeyPackets(); + for (var i = 0; i < subKeyPackets.length; i++) { + if (enums.write(enums.publicKey, subKeyPackets[i].algorithm) == enums.publicKey.elgamal) { + var keyByteSize = subKeyPackets[i].mpi[0].byteLength(); + requ += keyByteSize * ELG_FACTOR; // key byte size for ElGamal keys + break; + } + } + }); + break; + case 'sig': + privateKeys && privateKeys.forEach(function(key) { + if (enums.write(enums.publicKey, key.primaryKey.algorithm) == enums.publicKey.dsa) { + requ += 32 * DSA_FACTOR; // 32 bytes for DSA keys + } + }); + break; + case 'gen': + requ += 8; // salt for S2K; + requ += 16; // CFB initialization vector + requ += (Math.ceil(options.numBits / 8) + 1) * RSA_FACTOR; + requ = requ * 2; // * number of key packets + break; + default: + throw new Error('Unknown operation.'); + } + return requ; +}; + /** * Encrypts message text with keys * @param {Array} keys array of keys, used to encrypt the message @@ -58,13 +145,15 @@ AsyncProxy.prototype.terminate = function() { * @param {Function} callback receives encrypted ASCII armored message */ AsyncProxy.prototype.encryptMessage = function(keys, text, callback) { + var estimation = this.entropyEstimation('enc', keys); keys = keys.map(function(key) { return key.toPacketlist(); }); this.worker.postMessage({ event: 'encrypt-message', keys: keys, - text: text + text: text, + seed: this.getRandomBuffer(estimation) }); this.tasks.push(callback); }; @@ -77,6 +166,8 @@ AsyncProxy.prototype.encryptMessage = function(keys, text, callback) { * @param {Function} callback receives encrypted ASCII armored message */ AsyncProxy.prototype.signAndEncryptMessage = function(publicKeys, privateKey, text, callback) { + var estimation = this.entropyEstimation('enc', publikKeys) + + this.entropyEstimation('sig', null, [privateKey]); publicKeys = publicKeys.map(function(key) { return key.toPacketlist(); }); @@ -85,7 +176,8 @@ AsyncProxy.prototype.signAndEncryptMessage = function(publicKeys, privateKey, te event: 'sign-and-encrypt-message', publicKeys: publicKeys, privateKey: privateKey, - text: text + text: text, + seed: this.getRandomBuffer(estimation) }); this.tasks.push(callback); }; @@ -144,13 +236,15 @@ AsyncProxy.prototype.decryptAndVerifyMessage = function(privateKey, publicKeys, * @param {Function} callback receives ASCII armored message */ AsyncProxy.prototype.signClearMessage = function(privateKeys, text, callback) { + var estimation = this.entropyEstimation('sig', null, privateKeys); privateKeys = privateKeys.map(function(key) { return key.toPacketlist(); }); this.worker.postMessage({ event: 'sign-clear-message', privateKeys: privateKeys, - text: text + text: text, + seed: this.getRandomBuffer(estimation) }); this.tasks.push(callback); }; @@ -192,13 +286,13 @@ AsyncProxy.prototype.verifyClearSignedMessage = function(publicKeys, message, ca * @param {Function} callback receives object with key and public and private armored texts */ AsyncProxy.prototype.generateKeyPair = function(keyType, numBits, userId, passphrase, callback) { - this.seedRandom(Math.round(numBits / 2)); this.worker.postMessage({ event: 'generate-key-pair', keyType: keyType, numBits: numBits, userId: userId, - passphrase: passphrase + passphrase: passphrase, + seed: this.getRandomBuffer(this.entropyEstimation('gen', null, null, {numBits: numBits})) }); this.tasks.push(function(err, data) { if (data) { diff --git a/src/worker/worker.js b/src/worker/worker.js index a8b9c9a4..664dc4e3 100644 --- a/src/worker/worker.js +++ b/src/worker/worker.js @@ -19,14 +19,21 @@ window = {}; // to make UMD bundles work importScripts('openpgp.js'); +var MIN_SIZE_RANDOM_BUFFER = 8192; +var MAX_SIZE_RANDOM_BUFFER = 16384; + +window.openpgp.crypto.random.randomBuffer.init(MAX_SIZE_RANDOM_BUFFER); onmessage = function (event) { var data = null, - err = null, + err = null, msg = event.data; + if (msg.seed) { + window.openpgp.crypto.random.randomBuffer.set(msg.seed); + } switch (msg.event) { case 'seed-random': - window.openpgp.crypto.random.seedRandom(msg.buf); + window.openpgp.crypto.random.randomBuffer.set(msg.buf); break; case 'encrypt-message': try { @@ -35,7 +42,7 @@ onmessage = function (event) { } catch (e) { err = e.message; } - postMessage({event: 'method-return', data: data, err: err}); + response({event: 'method-return', data: data, err: err}); break; case 'sign-and-encrypt-message': try { @@ -45,7 +52,7 @@ onmessage = function (event) { } catch (e) { err = e.message; } - postMessage({event: 'method-return', data: data, err: err}); + response({event: 'method-return', data: data, err: err}); break; case 'decrypt-message': try { @@ -55,7 +62,7 @@ onmessage = function (event) { } catch (e) { err = e.message; } - postMessage({event: 'method-return', data: data, err: err}); + response({event: 'method-return', data: data, err: err}); break; case 'decrypt-and-verify-message': try { @@ -66,7 +73,7 @@ onmessage = function (event) { } catch (e) { err = e.message; } - postMessage({event: 'method-return', data: data, err: err}); + response({event: 'method-return', data: data, err: err}); break; case 'sign-clear-message': try { @@ -75,7 +82,7 @@ onmessage = function (event) { } catch (e) { err = e.message; } - postMessage({event: 'method-return', data: data, err: err}); + response({event: 'method-return', data: data, err: err}); break; case 'verify-clear-signed-message': try { @@ -86,7 +93,7 @@ onmessage = function (event) { } catch (e) { err = e.message; } - postMessage({event: 'method-return', data: data, err: err}); + response({event: 'method-return', data: data, err: err}); break; case 'generate-key-pair': try { @@ -95,13 +102,20 @@ onmessage = function (event) { } catch (e) { err = e.message; } - postMessage({event: 'method-return', data: data, err: err}); + response({event: 'method-return', data: data, err: err}); break; default: throw new Error('Unknown Worker Event.'); } }; +function response(event) { + if (window.openpgp.crypto.random.randomBuffer.size < MIN_SIZE_RANDOM_BUFFER) { + postMessage({event: 'request-seed'}); + } + postMessage(event); +} + function packetlistCloneToKey(packetlistClone) { var packetlist = window.openpgp.packet.List.fromStructuredClone(packetlistClone); return new window.openpgp.key.Key(packetlist); From 8cd956c9bcf9c7e3d9660449acaa6f0908a63bf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Obernd=C3=B6rfer?= Date: Wed, 15 Jan 2014 19:42:03 +0100 Subject: [PATCH 4/9] Web worker: add more unit tests --- test/worker/api.js | 323 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 284 insertions(+), 39 deletions(-) diff --git a/test/worker/api.js b/test/worker/api.js index 8826312b..d082cd46 100644 --- a/test/worker/api.js +++ b/test/worker/api.js @@ -9,9 +9,9 @@ describe('High level API', function() { var proxy; - this.timeout(50000); + this.timeout(0); - var pub_key = + var pub_key_rsa = ['-----BEGIN PGP PUBLIC KEY BLOCK-----', 'Version: GnuPG v2.0.19 (GNU/Linux)', 'Type: RSA/RSA', @@ -37,10 +37,10 @@ describe('High level API', function() { '=h/aX', '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); - var priv_key = + var priv_key_rsa = ['-----BEGIN PGP PRIVATE KEY BLOCK-----', 'Version: GnuPG v2.0.19 (GNU/Linux)', - 'Type: RSA/RSA', + 'Type: RSA/RSA 1024', 'Pwd: hello world', '', 'lQH+BFJhL04BBADclrUEDDsm0PSZbQ6pml9FpzTyXiyCyDN+rMOsy9J300Oc10kt', @@ -79,57 +79,302 @@ describe('High level API', function() { '=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 plaintext = 'short message\nnext line\n한국어/조선말'; - var pubKey, privKey; + var pubKeyRSA, privKeyRSA, pubKeyDE, privKeyDE; - it('Test initialization', function (done) { - pubKey = openpgp.key.readArmored(pub_key).keys[0]; - expect(pubKey).to.exist; + function initKeys() { + pubKeyRSA = openpgp.key.readArmored(pub_key_rsa).keys[0]; + expect(pubKeyRSA).to.exist; + privKeyRSA = openpgp.key.readArmored(priv_key_rsa).keys[0]; + expect(privKeyRSA).to.exist; + pubKeyDE = openpgp.key.readArmored(pub_key_de).keys[0]; + expect(pubKeyDE).to.exist; + privKeyDE = openpgp.key.readArmored(priv_key_de).keys[0]; + expect(privKeyDE).to.exist; + } - privKey = openpgp.key.readArmored(priv_key).keys[0]; - expect(privKey).to.exist; - - done(); - }); - - it('Initialize AsyncProxy', function (done) { + before(function() { proxy = new openpgp.AsyncProxy('../dist/openpgp.worker.js'); expect(proxy).to.exist; - done(); + initKeys(); }); - it('Generate key', function (done) { - proxy.generateKeyPair(3, 1024, 'Test McTestington ', 'hello world', function(err, data) { - expect(err).to.not.exist; - expect(data).to.exist; - expect(data.publicKeyArmored).to.match(/^-----BEGIN PGP PUBLIC/); - expect(data.privateKeyArmored).to.match(/^-----BEGIN PGP PRIVATE/); - expect(data.key).to.be.an.instanceof(openpgp.key.Key); - done(); + describe('Encryption', function() { + + it('RSA: encryptMessage async', function (done) { + proxy.encryptMessage([pubKeyRSA], plaintext, function(err, data) { + expect(err).to.not.exist; + expect(data).to.exist; + expect(data).to.match(/^-----BEGIN PGP MESSAGE/); + var msg = openpgp.message.readArmored(data); + expect(msg).to.be.an.instanceof(openpgp.message.Message); + done(); + }); }); + + it('RSA: encryptMessage sync', function () { + var msg = openpgp.encryptMessage([pubKeyRSA], plaintext); + expect(msg).to.exist; + expect(msg).to.match(/^-----BEGIN PGP MESSAGE/); + msg = openpgp.message.readArmored(msg); + expect(msg).to.be.an.instanceof(openpgp.message.Message); + }); + + it('ELG: encryptMessage async', function (done) { + proxy.encryptMessage([pubKeyDE], plaintext, function(err, data) { + expect(err).to.not.exist; + expect(data).to.exist; + expect(data).to.match(/^-----BEGIN PGP MESSAGE/); + var msg = openpgp.message.readArmored(data); + expect(msg).to.be.an.instanceof(openpgp.message.Message); + done(); + }); + }); + + it('ELG: encryptMessage sync', function () { + var msg = openpgp.encryptMessage([pubKeyDE], plaintext); + expect(msg).to.exist; + expect(msg).to.match(/^-----BEGIN PGP MESSAGE/); + msg = openpgp.message.readArmored(msg); + expect(msg).to.be.an.instanceof(openpgp.message.Message); + }); + }); - it('Encrypt and decrypt text', function (done) { - proxy.encryptMessage([pubKey], plaintext, function(err, data) { - expect(err).to.not.exist; - expect(data).to.exist; - expect(data).to.match(/^-----BEGIN PGP MESSAGE/); - var msg = openpgp.message.readArmored(data); - expect(msg).to.be.an.instanceof(openpgp.message.Message); - proxy.decryptMessage(privKey, msg, function(err, data) { + describe('Decryption', function() { + + var msgRSA, msgDE; + + before(function() { + privKeyRSA.decrypt('hello world'); + privKeyDE.decrypt('hello world'); + msgRSA = openpgp.message.fromText(plaintext).encrypt([pubKeyRSA]); + msgDE = openpgp.message.fromText(plaintext).encrypt([pubKeyDE]); + }); + + it('RSA: decryptMessage async', function (done) { + proxy.decryptMessage(privKeyRSA, msgRSA, function(err, data) { + expect(err).to.not.exist; + expect(data).to.exist; + expect(data).to.equal(plaintext); + done(); + }); + }); + + it('RSA: decryptMessage sync', function () { + var text = openpgp.decryptMessage(privKeyRSA, msgRSA); + expect(text).to.exist; + expect(text).to.equal(plaintext); + }); + + it('ELG: decryptMessage async', function (done) { + proxy.decryptMessage(privKeyDE, msgDE, function(err, data) { + expect(err).to.not.exist; + expect(data).to.exist; + expect(data).to.equal(plaintext); + done(); + }); + }); + + it('ELG: decryptMessage sync', function () { + var text = openpgp.decryptMessage(privKeyDE, msgDE); + expect(text).to.exist; + expect(text).to.equal(plaintext); + }); + + }); + + describe('Decrypt and Verify', function() { + + var msgRSA, msgDE; + + before(function() { + privKeyRSA.decrypt('hello world'); + privKeyDE.decrypt('hello world'); + msgRSA = openpgp.message.fromText(plaintext).sign([privKeyRSA]).encrypt([pubKeyRSA]); + msgDE = openpgp.message.fromText(plaintext).sign([privKeyDE]).encrypt([pubKeyDE]); + }); + + it('RSA: decryptAndVerifyMessage async', function (done) { + proxy.decryptAndVerifyMessage(privKeyRSA, [pubKeyRSA], msgRSA, function(err, data) { + expect(err).to.not.exist; + expect(data).to.exist; + expect(data.text).to.equal(plaintext); + expect(data.signatures).to.have.length(1); + expect(data.signatures[0].valid).to.be.true; + expect(data.signatures[0].keyid.equals(privKeyRSA.getSigningKeyPacket().getKeyId())).to.be.true; + done(); + }); + }); + + it('ELG: decryptAndVerifyMessage async', function (done) { + proxy.decryptAndVerifyMessage(privKeyDE, [pubKeyDE], msgDE, function(err, data) { + expect(err).to.not.exist; + expect(data).to.exist; + expect(data.text).to.equal(plaintext); + expect(data.signatures).to.have.length(1); + expect(data.signatures[0].valid).to.be.true; + expect(data.signatures[0].keyid.equals(privKeyDE.getSigningKeyPacket().getKeyId())).to.be.true; + done(); + }); + }); + + }); + + describe('Signing', function() { + + before(function() { + privKeyRSA.decrypt('hello world'); + privKeyDE.decrypt('hello world'); + }); + + it('RSA: signClearMessage async', function (done) { + proxy.signClearMessage([privKeyRSA], plaintext, function(err, data) { + expect(err).to.not.exist; + expect(data).to.exist; + expect(data).to.match(/-----BEGIN PGP SIGNED MESSAGE-----/); + var msg = openpgp.message.readArmored(data); + expect(msg).to.be.an.instanceof(openpgp.message.Message); + done(); + }); + }); + + it('DSA: signClearMessage async', function (done) { + proxy.signClearMessage([privKeyDE], plaintext, function(err, data) { + expect(err).to.not.exist; + expect(data).to.exist; + expect(data).to.match(/-----BEGIN PGP SIGNED MESSAGE-----/); + var msg = openpgp.message.readArmored(data); + expect(msg).to.be.an.instanceof(openpgp.message.Message); + done(); + }); + }); + + }); + + describe('Error handling', function() { + + before(initKeys); + + it('Signing with not decrypted key gives error', function (done) { + proxy.signClearMessage([privKeyRSA], plaintext, function(err, data) { expect(data).to.not.exist; expect(err).to.exist; expect(err.message).to.equal('Private key is not decrypted.'); - privKey.decrypt('hello world'); - proxy.decryptMessage(privKey, msg, function(err, data) { - expect(err).to.not.exist; - expect(data).to.exist; - expect(data).to.equal(plaintext); - done(); - }); + done(); }); }); + + }); + + describe('Key generation', function() { + + it('Generate 1024-bit RSA/RSA key async', function (done) { + proxy.generateKeyPair(3, 1024, 'Test McTestington ', 'hello world', function(err, data) { + expect(err).to.not.exist; + expect(data).to.exist; + expect(data.publicKeyArmored).to.match(/^-----BEGIN PGP PUBLIC/); + expect(data.privateKeyArmored).to.match(/^-----BEGIN PGP PRIVATE/); + expect(data.key).to.be.an.instanceof(openpgp.key.Key); + done(); + }); + }); + + it('Generate 1024-bit RSA/RSA key sync', function () { + var key = openpgp.generateKeyPair(3, 1024, 'Test McTestington ', 'hello world'); + expect(key).to.exist; + expect(key.publicKeyArmored).to.match(/^-----BEGIN PGP PUBLIC/); + expect(key.privateKeyArmored).to.match(/^-----BEGIN PGP PRIVATE/); + expect(key.key).to.be.an.instanceof(openpgp.key.Key); + }); + }); }); From c9910929dfb234ca0893b405a202a1b18fdd47df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Obernd=C3=B6rfer?= Date: Wed, 15 Jan 2014 19:44:00 +0100 Subject: [PATCH 5/9] Fix: clear subpackets of symmetrically encrypted packet after encryption --- src/message.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/message.js b/src/message.js index 7fee348c..8813cb0c 100644 --- a/src/message.js +++ b/src/message.js @@ -168,6 +168,8 @@ Message.prototype.encrypt = function(keys) { //TODO get preferred algo from signature symEncryptedPacket.encrypt(enums.read(enums.symmetric, config.encryption_cipher), sessionKey); packetlist.push(symEncryptedPacket); + // remove packets after encryption + symEncryptedPacket.packets = new packet.List(); return new Message(packetlist); }; From a7773714187d3990d9fcedcac88ebb2518259d5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Obernd=C3=B6rfer?= Date: Thu, 16 Jan 2014 13:34:31 +0100 Subject: [PATCH 6/9] Web worker: unit tests for random buffer and entropy estimation --- src/worker/async_proxy.js | 21 +- test/worker/api.js | 479 +++++++++++++++++++++++++------------- 2 files changed, 329 insertions(+), 171 deletions(-) diff --git a/src/worker/async_proxy.js b/src/worker/async_proxy.js index ec08c5c6..bad2b422 100644 --- a/src/worker/async_proxy.js +++ b/src/worker/async_proxy.js @@ -30,11 +30,11 @@ var crypto = require('../crypto'), type_keyid = require('../type/keyid.js'), enums = require('../enums.js'); -var INITIAL_SEED = 4096, // bytes seeded to worker - SEED_REQUEST = 4096, // bytes seeded after worker request - RSA_FACTOR = 2, - DSA_FACTOR = 2, - ELG_FACTOR = 2; +var INITIAL_SEED = 4096, // random bytes seeded to worker + SEED_REQUEST = 4096, // random bytes seeded after worker request + RSA_FACTOR = 2, // estimated rounds required to find BigInt for p + rounds required to find BigInt for q + DSA_FACTOR = 2 // estimated rounds required in random.getRandomBigIntegerInRange(2, q-1) + ELG_FACTOR = 2; // estimated rounds required in random.getRandomBigIntegerInRange(2, p-2) /** * Initializes a new proxy and loads the web worker @@ -106,9 +106,10 @@ AsyncProxy.prototype.entropyEstimation = function(op, publicKeys, privateKeys, o var requ = 0; // required entropy in bytes switch (op) { case 'enc': + if (!publicKeys) throw new Error('publicKeys required for operation enc'); requ += 32; // max. size of session key requ += 16; // max. size CFB prefix random - publicKeys && publicKeys.forEach(function(key) { + publicKeys.forEach(function(key) { var subKeyPackets = key.getSubkeyPackets(); for (var i = 0; i < subKeyPackets.length; i++) { if (enums.write(enums.publicKey, subKeyPackets[i].algorithm) == enums.publicKey.elgamal) { @@ -120,13 +121,15 @@ AsyncProxy.prototype.entropyEstimation = function(op, publicKeys, privateKeys, o }); break; case 'sig': - privateKeys && privateKeys.forEach(function(key) { + if (!privateKeys) throw new Error('privateKeys required for operation sig'); + privateKeys.forEach(function(key) { if (enums.write(enums.publicKey, key.primaryKey.algorithm) == enums.publicKey.dsa) { - requ += 32 * DSA_FACTOR; // 32 bytes for DSA keys + requ += 32 * DSA_FACTOR; // 32 bytes for DSA N value } }); break; case 'gen': + if (!options.numBits) throw new Error('options.numBits required for operation gen'); requ += 8; // salt for S2K; requ += 16; // CFB initialization vector requ += (Math.ceil(options.numBits / 8) + 1) * RSA_FACTOR; @@ -166,7 +169,7 @@ AsyncProxy.prototype.encryptMessage = function(keys, text, callback) { * @param {Function} callback receives encrypted ASCII armored message */ AsyncProxy.prototype.signAndEncryptMessage = function(publicKeys, privateKey, text, callback) { - var estimation = this.entropyEstimation('enc', publikKeys) + + var estimation = this.entropyEstimation('enc', publicKeys) + this.entropyEstimation('sig', null, [privateKey]); publicKeys = publicKeys.map(function(key) { return key.toPacketlist(); diff --git a/test/worker/api.js b/test/worker/api.js index d082cd46..15dd6d57 100644 --- a/test/worker/api.js +++ b/test/worker/api.js @@ -5,164 +5,159 @@ var openpgp = typeof window != 'undefined' && window.openpgp ? window.openpgp : var chai = require('chai'), expect = chai.expect; -describe('High level API', function() { - var proxy; +var pub_key_rsa = + ['-----BEGIN PGP PUBLIC KEY BLOCK-----', + 'Version: GnuPG v2.0.19 (GNU/Linux)', + 'Type: RSA/RSA', + '', + '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'); - this.timeout(0); +var priv_key_rsa = + ['-----BEGIN PGP PRIVATE KEY BLOCK-----', + 'Version: GnuPG v2.0.19 (GNU/Linux)', + 'Type: RSA/RSA 1024', + 'Pwd: hello world', + '', + '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', + '=lw5e', + '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); - var pub_key_rsa = - ['-----BEGIN PGP PUBLIC KEY BLOCK-----', - 'Version: GnuPG v2.0.19 (GNU/Linux)', - 'Type: RSA/RSA', - '', - '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_rsa = - ['-----BEGIN PGP PRIVATE KEY BLOCK-----', - 'Version: GnuPG v2.0.19 (GNU/Linux)', - 'Type: RSA/RSA 1024', - 'Pwd: hello world', - '', - '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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 pub_key_de = + ['-----BEGIN PGP PUBLIC KEY BLOCK-----', + 'Version: GnuPG v2.0.22 (GNU/Linux)', + '', + 'mQMuBFLVgdQRCACOlpq0cd1IazNjOEpWPZvx/O3JMbdDs3B3iCG0Mo5OUZ8lpKU5', + 'EslVgTd8IcUU14ZMOO7y91dw0KP4q61b4OIy7oVxzfFfKCC1s0Dc7GTay+qo5afJ', + 'wbWcgTyCIahTRmi5UepU7xdRHRMlqAclOwY2no8fw0JRQfFwRFCjbMdmvzC/k+Wo', + 'A42nn8YaSAG2v7OqF3rkYjkv/7iak48PO/l0Q13USAJLIWdHvRTir78mQUsEY0qR', + 'VoNqz5sMqakzhTvTav07EVy/1xC6GKoWXA9sdB/4r7+blVuu9M4yD40GkE69oAXO', + 'mz6tG3lRq41S0OSzNyDWtUQgMVF6wYqVxUGrAQDJM5A1rF1RKzFiHdkyy57E8LC1', + 'SIJyIXWJ0c5b8/olWQf9G5a17fMjkRTC3FO+ZHwFE1jIM6znYOF2GltDToLuJPq9', + 'lWrI7zVP9AJPwrUt7FK2MBNAvd1jKyIhdU98PBQ2pr+jmyqIycl9iDGXLDO7D7E/', + 'TBnxwQzoL/5b7UnPImuXOwv5JhVmyV2t003xjzb1EGggOnpKugUtVLps8JiLl9n+', + 'Nkj5wpU7NXbuHj2XGkkGmKkCIz4l0dJQR9V6svJV9By0RPgfGPXlN1VR6f2ounNy', + '6REnDCQP9S3Li5eNcxlSGDIxIZL22j63sU/68GVlzqhVdGXxofv5jGtajiNSpPot', + 'ElZU0dusna4PzYmiBCsyN8jENWSzHLJ37N4ScN4b/gf6Axf9FU0PjzPBN1o9W6zj', + 'kpfhlSWDjE3BK8jJ7KvzecM2QE/iJsbuyKEsklw1v0MsRDsox5QlQJcKOoUHC+OT', + 'iKm8cnPckLQNPOw/kb+5Auz7TXBQ63dogDuqO8QGGOpjh8SIYbblYQI5ueo1Tix3', + 'PlSU36SzOQfxSOCeIomEmaFQcU57O1CLsRl//+5lezMFDovJyQHQZfiTxSGfPHij', + 'oQzEUyEWYHKQhIRV6s5VGvF3hN0t8fo0o57bzhV6E7IaSz2Cnm0O0S2PZt8DBN9l', + 'LYNw3cFgzMb/qdFJGR0JXz+moyAYh/fYMiryb6d8ghhvrRy0CrRlC3U5K6qiYfKu', + 'lLQURFNBL0VMRyA8ZHNhQGVsZy5qcz6IewQTEQgAIwUCUtWB1AIbAwcLCQgHAwIB', + 'BhUIAgkKCwQWAgMBAh4BAheAAAoJELqZP8Ku4Yo6Aa0A/1Kz5S8d9czLiDbrhSa/', + 'C1rQ5qiWpFq9UNTFg2P/gASvAP92TzUMLK2my8ew1xXShtrfXked5fkSuFrPlZBs', + 'b4Ta67kCDQRS1YHUEAgAxOKx4y5QD78uPLlgNBHXrcncUNBIt4IXBGjQTxpFcn5j', + 'rSuj+ztvXJQ8wCkx+TTb2yuL5M+nXd7sx4s+M4KZ/MZfI6ZX4lhcoUdAbB9FWiV7', + 'uNntyeFo8qgGM5at/Q0EsyzMSqbeBxk4bpd5MfYGThn0Ae2xaw3X94KaZ3LjtHo2', + 'V27FD+jvmmoAj9b1+zcO/pJ8SuojQmcnS4VDVV+Ba5WPTav0LzDdQXyGMZI9PDxC', + 'jAI2f1HjTuxIt8X8rAQSQdoMIcQRYEjolsXS6iob1eVigyL86hLJjI3VPn6kBCv3', + 'Tb+WXX+9LgSAt9yvv4HMwBLK33k6IH7M72SqQulZywADBQgAt2xVTMjdVyMniMLj', + 'Ed4HbUgwyCPkVkcA4zTXqfKu+dAe4dK5tre0clkXZVtR1V8RDAD0zaVyM030e2zb', + 'zn4cGKDL2dmwk2ZBeXWZDgGKoKvGKYf8PRpTAYweFzol3OUdfXH5SngOylCD4OCL', + 's4RSVkSsllIWqLpnS5IJFgt6PDVcQgGXo2ZhVYkoLNhWTIEBuJWIyc4Vj20YpTms', + 'lgHnjeq5rP6781MwAJQnViyJ2SziGK4/+3CoDiQLO1zId42otXBvsbUuLSL5peX4', + 'v2XNVMLJMY5iSfzbBWczecyapiQ3fbVtWgucgrqlrqM3546v+GdATBhGOu8ppf5j', + '7d1A7ohhBBgRCAAJBQJS1YHUAhsMAAoJELqZP8Ku4Yo6SgoBAIVcZstwz4lyA2et', + 'y61IhKbJCOlQxyem+kepjNapkhKDAQDIDL38bZWU4Rm0nq82Xb4yaI0BCWDcFkHV', + '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'); + ['-----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 plaintext = 'short message\nnext line\n한국어/조선말'; @@ -180,6 +175,12 @@ var priv_key_de = expect(privKeyDE).to.exist; } +describe('High level API', function() { + + var proxy; + + this.timeout(0); + before(function() { proxy = new openpgp.AsyncProxy('../dist/openpgp.worker.js'); expect(proxy).to.exist; @@ -271,6 +272,13 @@ var priv_key_de = }); + function verifySignature(data, privKey) { + expect(data.text).to.equal(plaintext); + expect(data.signatures).to.have.length(1); + expect(data.signatures[0].valid).to.be.true; + expect(data.signatures[0].keyid.equals(privKey.getSigningKeyPacket().getKeyId())).to.be.true; + } + describe('Decrypt and Verify', function() { var msgRSA, msgDE; @@ -286,10 +294,7 @@ var priv_key_de = proxy.decryptAndVerifyMessage(privKeyRSA, [pubKeyRSA], msgRSA, function(err, data) { expect(err).to.not.exist; expect(data).to.exist; - expect(data.text).to.equal(plaintext); - expect(data.signatures).to.have.length(1); - expect(data.signatures[0].valid).to.be.true; - expect(data.signatures[0].keyid.equals(privKeyRSA.getSigningKeyPacket().getKeyId())).to.be.true; + verifySignature(data, privKeyRSA); done(); }); }); @@ -298,10 +303,28 @@ var priv_key_de = proxy.decryptAndVerifyMessage(privKeyDE, [pubKeyDE], msgDE, function(err, data) { expect(err).to.not.exist; expect(data).to.exist; - expect(data.text).to.equal(plaintext); - expect(data.signatures).to.have.length(1); - expect(data.signatures[0].valid).to.be.true; - expect(data.signatures[0].keyid.equals(privKeyDE.getSigningKeyPacket().getKeyId())).to.be.true; + verifySignature(data, privKeyDE); + done(); + }); + }); + + }); + + describe('Sign and Encrypt', function() { + + before(function() { + privKeyRSA.decrypt('hello world'); + }); + + it('RSA: signAndEncryptMessage async', function (done) { + proxy.signAndEncryptMessage([pubKeyRSA], privKeyRSA, plaintext, function(err, data) { + expect(err).to.not.exist; + expect(data).to.exist; + expect(data).to.match(/^-----BEGIN PGP MESSAGE/); + var msg = openpgp.message.readArmored(data); + expect(msg).to.be.an.instanceof(openpgp.message.Message); + var decrypted = openpgp.decryptAndVerifyMessage(privKeyRSA, [pubKeyRSA], msg); + verifySignature(decrypted, privKeyRSA); done(); }); }); @@ -337,6 +360,17 @@ var priv_key_de = }); }); + it('RSA: verifyClearSignedMessage async', function (done) { + var signed = openpgp.signClearMessage([privKeyRSA], plaintext); + signed = openpgp.cleartext.readArmored(signed); + proxy.verifyClearSignedMessage([pubKeyRSA], signed, function(err, data) { + expect(err).to.not.exist; + expect(data).to.exist; + verifySignature(data, privKeyRSA); + done(); + }); + }); + }); describe('Error handling', function() { @@ -378,5 +412,126 @@ var priv_key_de = }); }); + +describe('Random Buffer', function() { + + var randomBuffer; + + before(function() { + randomBuffer = new openpgp.crypto.random.randomBuffer.constructor(); + expect(randomBuffer).to.exist; + }); + + it('Throw error if not initialized', function () { + expect(randomBuffer.set).to.throw('RandomBuffer is not initialized'); + expect(randomBuffer.get).to.throw('RandomBuffer is not initialized'); + }); + + it('Initialization', function () { + randomBuffer.init(5); + expect(randomBuffer.buffer).to.exist; + expect(randomBuffer.buffer).to.have.length(5); + expect(randomBuffer.size).to.equal(0); + }); + + function equal(buf, arr) { + for (var i = 0; i < buf.length; i++) { + if (buf[i] !== arr[i]) return false; + } + return true; + } + + it('Set Method', function () { + randomBuffer.init(5); + var buf = new Uint32Array(2); + buf[0] = 1; buf[1] = 2; + randomBuffer.set(buf); + expect(equal(randomBuffer.buffer, [1,2,0,0,0])).to.be.true; + expect(randomBuffer.size).to.equal(2); + randomBuffer.set(buf); + expect(equal(randomBuffer.buffer, [1,2,1,2,0])).to.be.true; + expect(randomBuffer.size).to.equal(4); + randomBuffer.set(buf); + expect(equal(randomBuffer.buffer, [1,2,1,2,1])).to.be.true; + expect(randomBuffer.size).to.equal(5); + randomBuffer.init(1); + var buf = new Uint32Array(2); + buf[0] = 1; buf[1] = 2; + randomBuffer.set(buf); + expect(buf).to.to.have.property('0', 1); + expect(randomBuffer.size).to.equal(1); + }); + + it('Get Method', function () { + randomBuffer.init(5); + var buf = new Uint32Array(5); + buf[0] = 1; buf[1] = 2; buf[2] = 5; buf[3] = 7; buf[4] = 8; + randomBuffer.set(buf); + var buf = new Uint32Array(2); + randomBuffer.get(buf); + expect(equal(randomBuffer.buffer, [1,2,5,7,8])).to.be.true; + expect(randomBuffer.size).to.equal(3); + expect(buf).to.to.have.property('0', 8); + expect(buf).to.to.have.property('1', 7); + expect(equal(randomBuffer.buffer, [1,2,5,7,8])).to.be.true; + randomBuffer.get(buf); + expect(buf).to.to.have.property('0', 5); + expect(buf).to.to.have.property('1', 2); + expect(randomBuffer.size).to.equal(1); + expect(function() { randomBuffer.get(buf) }).to.throw('Random number buffer depleted.'); + }); + +}); - +describe('Entropy Estimation', function() { + + var proxy; + + before(function() { + proxy = new openpgp.AsyncProxy('../dist/openpgp.worker.js'); + expect(proxy).to.exist; + initKeys(); + }); + + it('RSA encrypt', function () { + var oneRSA = proxy.entropyEstimation('enc', [pubKeyRSA]); + // 32 byte session key + 16 byte CFB prefix + expect(oneRSA).to.equal(48); + var twoRSA = proxy.entropyEstimation('enc', [pubKeyRSA, pubKeyRSA]); + // only number of symmetrically encrypted packets relevant (we expect 1) + expect(twoRSA).to.equal(oneRSA); + }); + + it('RSA sign', function () { + var oneRSA = proxy.entropyEstimation('sig', null, [privKeyRSA]); + // no entropy required for RSA signing + expect(oneRSA).to.equal(0); + }); + + it('RSA generate', function () { + var oneRSA = proxy.entropyEstimation('gen', null, null, {numBits: 2048}); + // 8 salt for S2K + // 16 CFB initialization vector + // 256 byte size of key + // 1 ? + // 2 at least one round required for p and one for and q required + // 2 number of key packets + expect(oneRSA).to.equal((8 + 16 + (256 + 1) * 2) * 2); + }); + + it('ELG encrypt', function () { + var oneELG = proxy.entropyEstimation('enc', [pubKeyDE]); + // 32 byte session key + 16 byte CFB prefix + // 2 estimated rounds required in random.getRandomBigIntegerInRange(2, p-2) + expect(oneELG).to.equal(48 + (pubKeyDE.subKeys[0].subKey.getBitSize() / 8) * 2); + + }); + + it('DSA sign', function () { + var oneDSA = proxy.entropyEstimation('sig', null, [privKeyDE]); + // 32 bytes for DSA N value + // 2 estimated rounds required in random.getRandomBigIntegerInRange(2, q-1) + expect(oneDSA).to.equal(32 * 2); + }); + +}); From 37213e165498c6702795e09df9e0d4b28628be81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Obernd=C3=B6rfer?= Date: Thu, 16 Jan 2014 16:44:05 +0100 Subject: [PATCH 7/9] Web worker: add decryptKey and decryptKeyPacket methods to proxy --- src/worker/async_proxy.js | 46 +++++++++++++++++++++++++++++++ src/worker/worker.js | 32 +++++++++++++++++++++- test/worker/api.js | 57 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 1 deletion(-) diff --git a/src/worker/async_proxy.js b/src/worker/async_proxy.js index bad2b422..6a8212a4 100644 --- a/src/worker/async_proxy.js +++ b/src/worker/async_proxy.js @@ -306,4 +306,50 @@ AsyncProxy.prototype.generateKeyPair = function(keyType, numBits, userId, passph }); }; +/** + * Decrypts secret part of all secret key packets of key. + * @param {module:key~Key} privateKey private key with encrypted secret key data + * @param {String} password password to unlock the key + * @param {Function} callback receives decrypted key + */ +AsyncProxy.prototype.decryptKey = function(privateKey, password, callback) { + privateKey = privateKey.toPacketlist(); + this.worker.postMessage({ + event: 'decrypt-key', + privateKey: privateKey, + password: password + }); + this.tasks.push(function(err, data) { + if (data) { + var packetlist = packet.List.fromStructuredClone(data); + data = new key.Key(packetlist); + } + callback(err, data); + }); +}; + +/** + * Decrypts secret part of key packets matching array of keyids. + * @param {module:key~Key} privateKey private key with encrypted secret key data + * @param {Array} keyIds + * @param {String} password password to unlock the key + * @param {Function} callback receives decrypted key + */ +AsyncProxy.prototype.decryptKeyPacket = function(privateKey, keyIds, password, callback) { + privateKey = privateKey.toPacketlist(); + this.worker.postMessage({ + event: 'decrypt-key-packet', + privateKey: privateKey, + keyIds: keyIds, + password: password + }); + this.tasks.push(function(err, data) { + if (data) { + var packetlist = packet.List.fromStructuredClone(data); + data = new key.Key(packetlist); + } + callback(err, data); + }); +}; + module.exports = AsyncProxy; diff --git a/src/worker/worker.js b/src/worker/worker.js index 664dc4e3..2938ec3a 100644 --- a/src/worker/worker.js +++ b/src/worker/worker.js @@ -27,7 +27,8 @@ window.openpgp.crypto.random.randomBuffer.init(MAX_SIZE_RANDOM_BUFFER); onmessage = function (event) { var data = null, err = null, - msg = event.data; + msg = event.data, + correct = false; if (msg.seed) { window.openpgp.crypto.random.randomBuffer.set(msg.seed); } @@ -104,6 +105,35 @@ onmessage = function (event) { } response({event: 'method-return', data: data, err: err}); break; + case 'decrypt-key': + try { + msg.privateKey = packetlistCloneToKey(msg.privateKey); + correct = msg.privateKey.decrypt(msg.password); + if (correct) { + data = msg.privateKey.toPacketlist(); + } else { + err = 'Wrong password'; + } + } catch (e) { + err = e.message; + } + response({event: 'method-return', data: data, err: err}); + break; + case 'decrypt-key-packet': + try { + msg.privateKey = packetlistCloneToKey(msg.privateKey); + msg.keyIds = msg.keyIds.map(window.openpgp.Keyid.fromClone); + correct = msg.privateKey.decryptKeyPacket(msg.keyIds, msg.password); + if (correct) { + data = msg.privateKey.toPacketlist(); + } else { + err = 'Wrong password'; + } + } catch (e) { + err = e.message; + } + response({event: 'method-return', data: data, err: err}); + break; default: throw new Error('Unknown Worker Event.'); } diff --git a/test/worker/api.js b/test/worker/api.js index 15dd6d57..30e764e5 100644 --- a/test/worker/api.js +++ b/test/worker/api.js @@ -411,6 +411,63 @@ describe('High level API', function() { }); + describe('Decrypt secret key', function() { + + var msg; + + beforeEach(function() { + initKeys(); + msg = openpgp.message.fromText(plaintext).encrypt([pubKeyRSA]); + }); + + it('Decrypt key', function (done) { + expect(privKeyRSA.primaryKey.isDecrypted).to.be.false; + expect(privKeyRSA.subKeys[0].subKey.isDecrypted).to.be.false; + proxy.decryptKey(privKeyRSA, 'hello world', function(err, data) { + expect(err).to.not.exist; + expect(data).to.exist; + expect(data).to.be.an.instanceof(openpgp.key.Key); + expect(data.primaryKey.isDecrypted).to.be.true; + expect(data.subKeys[0].subKey.isDecrypted).to.be.true; + var text = openpgp.decryptMessage(data, msg); + expect(text).to.equal(plaintext); + done(); + }); + }); + + it('Decrypt key packet', function (done) { + expect(privKeyRSA.primaryKey.isDecrypted).to.be.false; + expect(privKeyRSA.subKeys[0].subKey.isDecrypted).to.be.false; + var keyid = privKeyRSA.subKeys[0].subKey.getKeyId(); + proxy.decryptKeyPacket(privKeyRSA, [keyid], 'hello world', function(err, data) { + expect(err).to.not.exist; + expect(data).to.exist; + expect(data).to.be.an.instanceof(openpgp.key.Key); + expect(data.primaryKey.isDecrypted).to.be.false; + expect(data.subKeys[0].subKey.isDecrypted).to.be.true; + var text = openpgp.decryptMessage(data, msg); + expect(text).to.equal(plaintext); + done(); + }); + }); + + it('Error on wrong password decryptKey', function (done) { + proxy.decryptKey(privKeyRSA, 'what?', function(err, data) { + expect(err).to.eql(new Error('Wrong password')); + done(); + }); + }); + + it('Error on wrong password decryptKeyPacket', function (done) { + var keyid = privKeyRSA.subKeys[0].subKey.getKeyId(); + proxy.decryptKeyPacket(privKeyRSA, [keyid], 'what?', function(err, data) { + expect(err).to.eql(new Error('Wrong password')); + done(); + }); + }); + + }); + }); describe('Random Buffer', function() { From abf054520802f83e57cc98b85229d41259ffabaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Obernd=C3=B6rfer?= Date: Sat, 18 Jan 2014 16:37:15 +0100 Subject: [PATCH 8/9] Change internal representation of random data from Uint32Array to Uint8Array --- src/crypto/random.js | 27 +++++++++++++++++---------- src/worker/async_proxy.js | 6 +++--- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/crypto/random.js b/src/crypto/random.js index 51caad81..cbe76bd5 100644 --- a/src/crypto/random.js +++ b/src/crypto/random.js @@ -60,30 +60,37 @@ module.exports = { * @return {Integer} A secure random number */ getSecureRandom: function(from, to) { - var buf = new Uint32Array(1); - this.getRandomValues(buf); + var randUint = this.getSecureRandomUint(); var bits = ((to - from)).toString(2).length; - while ((buf[0] & (Math.pow(2, bits) - 1)) > (to - from)) - this.getRandomValues(buf); - return from + (Math.abs(buf[0] & (Math.pow(2, bits) - 1))); + while ((randUint & (Math.pow(2, bits) - 1)) > (to - from)) { + randUint = this.getSecureRandomUint(); + } + return from + (Math.abs(randUint & (Math.pow(2, bits) - 1))); }, getSecureRandomOctet: function() { - var buf = new Uint32Array(1); + var buf = new Uint8Array(1); this.getRandomValues(buf); - return buf[0] & 0xFF; + return buf[0]; + }, + + getSecureRandomUint: function() { + var buf = new Uint8Array(4); + var dv = new DataView(buf.buffer); + this.getRandomValues(buf); + return dv.getUint32(0); }, /** * Helper routine which calls platform specific crypto random generator - * @param {Uint32Array} buf + * @param {Uint8Array} buf */ getRandomValues: function(buf) { if (typeof window !== 'undefined' && window.crypto) { window.crypto.getRandomValues(buf); } else if (nodeCrypto) { - var bytes = nodeCrypto.randomBytes(4); - buf[0] = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]; + var bytes = nodeCrypto.randomBytes(buf.length); + buf.set(bytes); } else if (this.randomBuffer.buffer) { this.randomBuffer.get(buf); } else { diff --git a/src/worker/async_proxy.js b/src/worker/async_proxy.js index 6a8212a4..2b44880e 100644 --- a/src/worker/async_proxy.js +++ b/src/worker/async_proxy.js @@ -76,13 +76,13 @@ AsyncProxy.prototype.seedRandom = function(size) { }; /** - * Get Uint32Array with random numbers + * Get Uint8Array with random numbers * @param {Integer} size Length of buffer - * @return {Uint32Array} + * @return {Uint8Array} */ AsyncProxy.prototype.getRandomBuffer = function(size) { if (!size) return null; - var buf = new Uint32Array(size); + var buf = new Uint8Array(size); crypto.random.getRandomValues(buf); return buf; }; From 040ccbaf2c9c06975beb1ace1b5de74c6b57ff0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Obernd=C3=B6rfer?= Date: Mon, 20 Jan 2014 09:56:02 +0100 Subject: [PATCH 9/9] Web worker: simplify random data supply --- src/worker/async_proxy.js | 74 +++++---------------------------------- src/worker/worker.js | 7 ++-- test/worker/api.js | 66 +++++++--------------------------- 3 files changed, 23 insertions(+), 124 deletions(-) diff --git a/src/worker/async_proxy.js b/src/worker/async_proxy.js index 2b44880e..e5235644 100644 --- a/src/worker/async_proxy.js +++ b/src/worker/async_proxy.js @@ -30,11 +30,8 @@ var crypto = require('../crypto'), type_keyid = require('../type/keyid.js'), enums = require('../enums.js'); -var INITIAL_SEED = 4096, // random bytes seeded to worker - SEED_REQUEST = 4096, // random bytes seeded after worker request - RSA_FACTOR = 2, // estimated rounds required to find BigInt for p + rounds required to find BigInt for q - DSA_FACTOR = 2 // estimated rounds required in random.getRandomBigIntegerInRange(2, q-1) - ELG_FACTOR = 2; // estimated rounds required in random.getRandomBigIntegerInRange(2, p-2) +var INITIAL_RANDOM_SEED = 50000, // random bytes seeded to worker + RANDOM_SEED_REQUEST = 20000; // random bytes seeded after worker request /** * Initializes a new proxy and loads the web worker @@ -44,7 +41,7 @@ var INITIAL_SEED = 4096, // random bytes seeded to worker function AsyncProxy(path) { this.worker = new Worker(path || 'openpgp.worker.js'); this.worker.onmessage = this.onMessage.bind(this); - this.seedRandom(INITIAL_SEED); + this.seedRandom(INITIAL_RANDOM_SEED); // FIFO this.tasks = []; } @@ -59,7 +56,7 @@ AsyncProxy.prototype.onMessage = function(event) { this.tasks.shift()(msg.err ? new Error(msg.err) : null, msg.data); break; case 'request-seed': - this.seedRandom(SEED_REQUEST); + this.seedRandom(RANDOM_SEED_REQUEST); break; default: throw new Error('Unknown Worker Event.'); @@ -94,53 +91,6 @@ AsyncProxy.prototype.terminate = function() { this.worker.terminate(); }; -/** - * Estimation on how much random bytes are required to process the operation - * @param {String} op 'enc', 'sig' or 'gen' - * @param {Array} publicKeys - * @param {Array} privateKeys - * @param {Object} options - * @return {Integer} number of bytes required - */ -AsyncProxy.prototype.entropyEstimation = function(op, publicKeys, privateKeys, options) { - var requ = 0; // required entropy in bytes - switch (op) { - case 'enc': - if (!publicKeys) throw new Error('publicKeys required for operation enc'); - requ += 32; // max. size of session key - requ += 16; // max. size CFB prefix random - publicKeys.forEach(function(key) { - var subKeyPackets = key.getSubkeyPackets(); - for (var i = 0; i < subKeyPackets.length; i++) { - if (enums.write(enums.publicKey, subKeyPackets[i].algorithm) == enums.publicKey.elgamal) { - var keyByteSize = subKeyPackets[i].mpi[0].byteLength(); - requ += keyByteSize * ELG_FACTOR; // key byte size for ElGamal keys - break; - } - } - }); - break; - case 'sig': - if (!privateKeys) throw new Error('privateKeys required for operation sig'); - privateKeys.forEach(function(key) { - if (enums.write(enums.publicKey, key.primaryKey.algorithm) == enums.publicKey.dsa) { - requ += 32 * DSA_FACTOR; // 32 bytes for DSA N value - } - }); - break; - case 'gen': - if (!options.numBits) throw new Error('options.numBits required for operation gen'); - requ += 8; // salt for S2K; - requ += 16; // CFB initialization vector - requ += (Math.ceil(options.numBits / 8) + 1) * RSA_FACTOR; - requ = requ * 2; // * number of key packets - break; - default: - throw new Error('Unknown operation.'); - } - return requ; -}; - /** * Encrypts message text with keys * @param {Array} keys array of keys, used to encrypt the message @@ -148,15 +98,13 @@ AsyncProxy.prototype.entropyEstimation = function(op, publicKeys, privateKeys, o * @param {Function} callback receives encrypted ASCII armored message */ AsyncProxy.prototype.encryptMessage = function(keys, text, callback) { - var estimation = this.entropyEstimation('enc', keys); keys = keys.map(function(key) { return key.toPacketlist(); }); this.worker.postMessage({ event: 'encrypt-message', keys: keys, - text: text, - seed: this.getRandomBuffer(estimation) + text: text }); this.tasks.push(callback); }; @@ -169,8 +117,6 @@ AsyncProxy.prototype.encryptMessage = function(keys, text, callback) { * @param {Function} callback receives encrypted ASCII armored message */ AsyncProxy.prototype.signAndEncryptMessage = function(publicKeys, privateKey, text, callback) { - var estimation = this.entropyEstimation('enc', publicKeys) + - this.entropyEstimation('sig', null, [privateKey]); publicKeys = publicKeys.map(function(key) { return key.toPacketlist(); }); @@ -179,8 +125,7 @@ AsyncProxy.prototype.signAndEncryptMessage = function(publicKeys, privateKey, te event: 'sign-and-encrypt-message', publicKeys: publicKeys, privateKey: privateKey, - text: text, - seed: this.getRandomBuffer(estimation) + text: text }); this.tasks.push(callback); }; @@ -239,15 +184,13 @@ AsyncProxy.prototype.decryptAndVerifyMessage = function(privateKey, publicKeys, * @param {Function} callback receives ASCII armored message */ AsyncProxy.prototype.signClearMessage = function(privateKeys, text, callback) { - var estimation = this.entropyEstimation('sig', null, privateKeys); privateKeys = privateKeys.map(function(key) { return key.toPacketlist(); }); this.worker.postMessage({ event: 'sign-clear-message', privateKeys: privateKeys, - text: text, - seed: this.getRandomBuffer(estimation) + text: text }); this.tasks.push(callback); }; @@ -294,8 +237,7 @@ AsyncProxy.prototype.generateKeyPair = function(keyType, numBits, userId, passph keyType: keyType, numBits: numBits, userId: userId, - passphrase: passphrase, - seed: this.getRandomBuffer(this.entropyEstimation('gen', null, null, {numBits: numBits})) + passphrase: passphrase }); this.tasks.push(function(err, data) { if (data) { diff --git a/src/worker/worker.js b/src/worker/worker.js index 2938ec3a..f8d6a2ef 100644 --- a/src/worker/worker.js +++ b/src/worker/worker.js @@ -19,8 +19,8 @@ window = {}; // to make UMD bundles work importScripts('openpgp.js'); -var MIN_SIZE_RANDOM_BUFFER = 8192; -var MAX_SIZE_RANDOM_BUFFER = 16384; +var MIN_SIZE_RANDOM_BUFFER = 40000; +var MAX_SIZE_RANDOM_BUFFER = 60000; window.openpgp.crypto.random.randomBuffer.init(MAX_SIZE_RANDOM_BUFFER); @@ -29,9 +29,6 @@ onmessage = function (event) { err = null, msg = event.data, correct = false; - if (msg.seed) { - window.openpgp.crypto.random.randomBuffer.set(msg.seed); - } switch (msg.event) { case 'seed-random': window.openpgp.crypto.random.randomBuffer.set(msg.buf); diff --git a/test/worker/api.js b/test/worker/api.js index 30e764e5..0edc0596 100644 --- a/test/worker/api.js +++ b/test/worker/api.js @@ -386,6 +386,19 @@ describe('High level API', function() { }); }); + it('Depleted random buffer in worker gives error', function (done) { + var wProxy = new openpgp.AsyncProxy('../dist/openpgp.worker.js'); + wProxy.worker = new Worker('../dist/openpgp.worker.js'); + wProxy.worker.onmessage = wProxy.onMessage.bind(wProxy); + wProxy.seedRandom(10); + wProxy.encryptMessage([pubKeyRSA], plaintext, function(err, data) { + expect(data).to.not.exist; + expect(err).to.exist; + expect(err).to.eql(new Error('Random number buffer depleted.')); + done(); + }); + }); + }); describe('Key generation', function() { @@ -539,56 +552,3 @@ describe('Random Buffer', function() { }); }); - -describe('Entropy Estimation', function() { - - var proxy; - - before(function() { - proxy = new openpgp.AsyncProxy('../dist/openpgp.worker.js'); - expect(proxy).to.exist; - initKeys(); - }); - - it('RSA encrypt', function () { - var oneRSA = proxy.entropyEstimation('enc', [pubKeyRSA]); - // 32 byte session key + 16 byte CFB prefix - expect(oneRSA).to.equal(48); - var twoRSA = proxy.entropyEstimation('enc', [pubKeyRSA, pubKeyRSA]); - // only number of symmetrically encrypted packets relevant (we expect 1) - expect(twoRSA).to.equal(oneRSA); - }); - - it('RSA sign', function () { - var oneRSA = proxy.entropyEstimation('sig', null, [privKeyRSA]); - // no entropy required for RSA signing - expect(oneRSA).to.equal(0); - }); - - it('RSA generate', function () { - var oneRSA = proxy.entropyEstimation('gen', null, null, {numBits: 2048}); - // 8 salt for S2K - // 16 CFB initialization vector - // 256 byte size of key - // 1 ? - // 2 at least one round required for p and one for and q required - // 2 number of key packets - expect(oneRSA).to.equal((8 + 16 + (256 + 1) * 2) * 2); - }); - - it('ELG encrypt', function () { - var oneELG = proxy.entropyEstimation('enc', [pubKeyDE]); - // 32 byte session key + 16 byte CFB prefix - // 2 estimated rounds required in random.getRandomBigIntegerInRange(2, p-2) - expect(oneELG).to.equal(48 + (pubKeyDE.subKeys[0].subKey.getBitSize() / 8) * 2); - - }); - - it('DSA sign', function () { - var oneDSA = proxy.entropyEstimation('sig', null, [privKeyDE]); - // 32 bytes for DSA N value - // 2 estimated rounds required in random.getRandomBigIntegerInRange(2, q-1) - expect(oneDSA).to.equal(32 * 2); - }); - -});