From a44e1e502422edde08a65fdf312f64096f9b8861 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Mon, 8 Feb 2016 19:32:42 +0700 Subject: [PATCH] Write tests for new api: openpgp.generateKey --- Gruntfile.js | 3 +- src/key.js | 8 +- src/openpgp.js | 89 +++++++++---- src/worker/async_proxy.js | 215 +++++++++--------------------- src/worker/worker.js | 23 +++- test/general/index.js | 1 + test/general/openpgp.js | 267 ++++++++++++++++++++++++++++++++++++++ test/general/util.js | 4 - 8 files changed, 416 insertions(+), 194 deletions(-) create mode 100644 test/general/openpgp.js diff --git a/Gruntfile.js b/Gruntfile.js index c95055b9..3a3f559e 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -223,11 +223,10 @@ module.exports = function(grunt) { } }, }, - watch: { src: { files: ['src/**/*.js'], - tasks: ['browserify:openpgp'] + tasks: ['browserify:openpgp', 'browserify:worker'] }, test: { files: ['test/*.js', 'test/crypto/**/*.js', 'test/general/**/*.js', 'test/worker/**/*.js'], diff --git a/src/key.js b/src/key.js index 436d642d..31130995 100644 --- a/src/key.js +++ b/src/key.js @@ -924,7 +924,7 @@ export function readArmored(armoredText) { * @param {module:enums.publicKey} [options.keyType=module:enums.publicKey.rsa_encrypt_sign] to indicate what type of key to make. * RSA is 1. See {@link http://tools.ietf.org/html/rfc4880#section-9.1} * @param {Integer} options.numBits number of bits for the key creation. - * @param {String|Array} options.userId assumes already in form of "User Name " + * @param {String|Array} options.userIds assumes already in form of "User Name " If array is used, the first userId is set as primary user Id * @param {String} options.passphrase The passphrase used to encrypt the resulting private key * @param {Boolean} [options.unlocked=false] The secret part of the generated key is unlocked @@ -943,8 +943,8 @@ export function generate(options) { if (!options.passphrase) { options.unlocked = true; } - if (String.prototype.isPrototypeOf(options.userId) || typeof options.userId === 'string') { - options.userId = [options.userId]; + if (String.prototype.isPrototypeOf(options.userIds) || typeof options.userIds === 'string') { + options.userIds = [options.userIds]; } // generate @@ -975,7 +975,7 @@ export function generate(options) { packetlist.push(secretKeyPacket); - options.userId.forEach(function(userId, index) { + options.userIds.forEach(function(userId, index) { userIdPacket = new packet.Userid(); userIdPacket.read(util.str2Uint8Array(userId)); diff --git a/src/openpgp.js b/src/openpgp.js index 13503764..5f45978d 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -42,7 +42,7 @@ es6Promise.polyfill(); // load ES6 Promises polyfill ////////////////////////// -let asyncProxy = null; // instance of the asyncproxy +let asyncProxy; // instance of the asyncproxy /** * Set the path for the web worker script and create an instance of the async proxy @@ -65,6 +65,13 @@ export function getWorker() { return asyncProxy; } +/** + * Cleanup the current instance of the web worker. + */ +export function destroyWorker() { + asyncProxy = undefined; +} + //////////////////////////// // // @@ -84,8 +91,8 @@ export function getWorker() { * @static */ export function generateKey({ userIds=[], passphrase, numBits=2048, unlocked=false } = {}) { - userIds = userIds.map(id => id.name + ' <' + id.email + '>'); // format user ids for internal use const options = { userIds, passphrase, numBits, unlocked }; + formatUserIds(options); if (!util.getWebCrypto() && asyncProxy) { // use web worker if web crypto apis are not supported return asyncProxy.generateKey(options); @@ -100,12 +107,12 @@ export function generateKey({ userIds=[], passphrase, numBits=2048, unlocked=fal })).catch(err => { // js fallback already tried - console.error(err); + if (config.debug) { console.error(err); } if (!util.getWebCrypto()) { throw new Error('Error generating keypair using js fallback!'); } // fall back to js keygen in a worker - console.log('Error generating keypair using native WebCrypto... falling back back to js!'); + if (config.debug) { console.log('Error generating keypair using native WebCrypto... falling back back to js!'); } return asyncProxy.generateKey(options); }).catch(onError.bind(null, 'Error generating keypair!')); @@ -132,8 +139,8 @@ export function generateKey({ userIds=[], passphrase, numBits=2048, unlocked=fal * @static */ export function encrypt({ data, publicKeys, privateKeys, passwords, filename, packets }) { - publicKeys = publicKeys ? (publicKeys.length ? publicKeys : [publicKeys]) : undefined; // normalize key objects to arrays - privateKeys = privateKeys ? (privateKeys.length ? privateKeys : [privateKeys]) : undefined; + publicKeys = toArray(publicKeys); + privateKeys = toArray(privateKeys); if (asyncProxy) { // use web worker if available return asyncProxy.encrypt({ data, publicKeys, privateKeys, passwords, filename, packets }); @@ -170,7 +177,7 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, filename, pa * @static */ export function decrypt({ message, privateKey, publickeys, sessionKey, password, format='utf8' }) { - publickeys = publickeys ? (publickeys.length ? publickeys : [publickeys]) : undefined; // normalize key objects to arrays + publickeys = toArray(publickeys); if (asyncProxy) { // use web worker if available return asyncProxy.decrypt({ message, privateKey, publickeys, sessionKey, password, format }); @@ -204,7 +211,10 @@ export function decrypt({ message, privateKey, publickeys, sessionKey, password, * @static */ export function sign({ data, privateKeys }) { - privateKeys = privateKeys.length ? privateKeys : [privateKeys]; + privateKeys = toArray(privateKeys); + if (!util.isString(data)) { + throw new Error('Only cleartext data of type String supported!'); + } if (asyncProxy) { // use web worker if available return asyncProxy.sign({ data, privateKeys }); @@ -230,23 +240,21 @@ export function sign({ data, privateKeys }) { * @static */ export function verify({ message, publicKeys }) { - publicKeys = publicKeys.length ? publicKeys : [publicKeys]; + publicKeys = toArray(publicKeys); + if (!(message instanceof cleartext.CleartextMessage)) { + throw new Error('Parameter [message] needs to be of type CleartextMessage.'); + } if (asyncProxy) { // use web worker if available return asyncProxy.verify({ message, publicKeys }); } - return execute(() => { + return execute(() => ({ - if (!(message instanceof cleartext.CleartextMessage)) { - throw new Error('Parameter [message] needs to be of type CleartextMessage.'); - } - return { - data: message.getText(), - signatures: message.verify(publicKeys) - }; + data: message.getText(), + signatures: message.verify(publicKeys) - }, 'Error verifying cleartext signed message!'); + }), 'Error verifying cleartext signed message!'); } @@ -306,6 +314,43 @@ export function decryptSessionKey({ message, privateKey, sessionKey, password }) ////////////////////////// +/** + * Format user ids for internal use. + */ +function formatUserIds(options) { + if (!options.userIds) { + return; + } + options.userIds = toArray(options.userIds); // normalize to array + options.userIds = options.userIds.map(id => { + if (util.isString(id) && !util.isUserId(id)) { + throw new Error('Invalid user id format!'); + } + if (util.isUserId(id)) { + return id; // user id is already in correct format... no conversion necessary + } + // name and email address can be empty but must be of type the correct type + id.name = id.name || ''; + id.email = id.email || ''; + if (!util.isString(id.name) || (id.email && !util.isEmailAddress(id.email))) { + throw new Error('Invalid user id format!'); + } + return id.name + ' <' + id.email + '>'; + }); +} + +/** + * Normalize parameter to an array if it is not undefined. + * @param {Object} param the parameter to be normalized + * @return {Array|undefined} the resulting array or undefined + */ +function toArray(param) { + if (param && !util.isArray(param)) { + param = [param]; + } + return param; +} + /** * Creates a message obejct either from a Uint8Array or a string. * @param {String|Uint8Array} data the payload for the message @@ -314,9 +359,9 @@ export function decryptSessionKey({ message, privateKey, sessionKey, password }) */ function createMessage(data, filename) { let msg; - if (data instanceof Uint8Array) { + if (util.isUint8Array(data)) { msg = messageLib.fromBinary(data, filename); - } else if (typeof data === 'string') { + } else if (util.isString(data)) { msg = messageLib.fromText(data, filename); } else { throw new Error('Data must be of type String or Uint8Array!'); @@ -391,9 +436,7 @@ function execute(cmd, message) { */ function onError(message, error) { // log the stack trace - if (config.debug) { - console.error(error.stack); - } + if (config.debug) { console.error(error.stack); } // rethrow new high level error for api users throw new Error(message); } diff --git a/src/worker/async_proxy.js b/src/worker/async_proxy.js index f0430ded..4256c757 100644 --- a/src/worker/async_proxy.js +++ b/src/worker/async_proxy.js @@ -31,58 +31,49 @@ import packet from '../packet'; import * as key from '../key.js'; import type_keyid from '../type/keyid.js'; -var INITIAL_RANDOM_SEED = 50000, // random bytes seeded to worker +const 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 * @constructor - * @param {String} path The path to the worker or 'openpgp.worker.js' by default - * @param {Object} [options.config=Object] config The worker configuration - * @param {Object} [options.worker=Object] alternative to path parameter: - * web worker initialized with 'openpgp.worker.js' + * @param {String} path The path to the worker or 'openpgp.worker.js' by default + * @param {Object} config config The worker configuration + * @param {Object} worker alternative to path parameter: web worker initialized with 'openpgp.worker.js' + * @return {Promise} */ -export default function AsyncProxy(path, options) { - if (options && options.worker) { - this.worker = options.worker; - } else { - this.worker = new Worker(path || 'openpgp.worker.js'); - } +export default function AsyncProxy({ path='openpgp.worker.js', worker, config } = {}) { + this.worker = worker || new Worker(path); this.worker.onmessage = this.onMessage.bind(this); - this.worker.onerror = function(e) { + this.worker.onerror = e => { throw new Error('Unhandled error in openpgp worker: ' + e.message + ' (' + e.filename + ':' + e.lineno + ')'); }; this.seedRandom(INITIAL_RANDOM_SEED); // FIFO this.tasks = []; - if (options && options.config) { - this.worker.postMessage({event: 'configure', config: options.config}); + if (config) { + this.worker.postMessage({ event:'configure', config }); } } /** * Command pattern that wraps synchronous code into a promise - * @param {Object} self The current this * @param {function} cmd The synchronous function with a return value * to be wrapped in a promise * @return {Promise} The promise wrapped around cmd */ AsyncProxy.prototype.execute = function(cmd) { - var self = this; - - var promise = new Promise(function(resolve, reject) { + return new Promise((resolve, reject) => { cmd(); - self.tasks.push({ resolve:resolve, reject:reject }); + this.tasks.push({ resolve, reject }); }); - - return promise; }; /** * Message handling */ AsyncProxy.prototype.onMessage = function(event) { - var msg = event.data; + const msg = event.data; switch (msg.event) { case 'method-return': if (msg.err) { @@ -106,8 +97,8 @@ AsyncProxy.prototype.onMessage = function(event) { * @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}); + const buf = this.getRandomBuffer(size); + this.worker.postMessage({ event:'seed-random', buf }); }; /** @@ -119,7 +110,7 @@ AsyncProxy.prototype.getRandomBuffer = function(size) { if (!size) { return null; } - var buf = new Uint8Array(size); + const buf = new Uint8Array(size); crypto.random.getRandomValues(buf); return buf; }; @@ -131,71 +122,56 @@ AsyncProxy.prototype.terminate = function() { this.worker.terminate(); }; -/** - * Encrypts message text/data with keys or passwords - * @param {(Array|module:key~Key)} keys array of keys or single key, used to encrypt the message - * @param {Uint8Array} data message as Uint8Array - * @param {(Array|String)} passwords passwords for the message - * @param {Object} params parameter object with optional properties binary {Boolean}, - * filename {String}, and packets {Boolean} - */ -AsyncProxy.prototype.encryptMessage = function(keys, data, passwords, params) { - var self = this; - return self.execute(function() { - if(keys) { - if (!Array.prototype.isPrototypeOf(keys)) { - keys = [keys]; - } - keys = keys.map(function(key) { - return key.toPacketlist(); - }); +////////////////////////////////////////////////////////////////////////////////////////////////// +// // +// Proxy functions. See the corresponding code in the openpgp module for the documentation. // +// // +////////////////////////////////////////////////////////////////////////////////////////////////// + + +AsyncProxy.prototype.generateKey = function(options) { + return new Promise((resolve, reject) => { + this.worker.postMessage({ + event: 'generate-key', + options: options + }); + + this.tasks.push({ resolve: data => { + const packetlist = packet.List.fromStructuredClone(data.key); + data.key = new key.Key(packetlist); + resolve(data); + }, reject }); + }); +}; + +AsyncProxy.prototype.encrypt = function({ data, publicKeys, privateKeys, passwords, filename, packets }) { + return this.execute(() => { + if(publicKeys) { + publicKeys = publicKeys.length ? publicKeys : [publicKeys]; + publicKeys = publicKeys.map(key => key.toPacketlist()); } - self.worker.postMessage({ - event: 'encrypt-message', - keys: keys, - data: data, - passwords: passwords, - params: params + if(privateKeys) { + privateKeys = privateKeys.length ? privateKeys : [privateKeys]; + privateKeys = privateKeys.map(key => key.toPacketlist()); + } + this.worker.postMessage({ + event:'encrypt', + options: { data, publicKeys, privateKeys, passwords, filename, packets } }); }); }; -/** - * Encrypts session key with keys or passwords - * @param {Uint8Array} sessionKey sessionKey as Uint8Array - * @param {String} algo algorithm of sessionKey - * @param {(Array|module:key~Key)} keys array of keys or single key, used to encrypt the key - * @param {(Array|String)} passwords passwords for the message - */ -AsyncProxy.prototype.encryptSessionKey = function(sessionKey, algo, keys, passwords) { - var self = this; - - return self.execute(function() { +AsyncProxy.prototype.encryptSessionKey = function({ sessionKey, algo, keys, passwords }) { + return this.execute(() => { if(keys) { - if (!Array.prototype.isPrototypeOf(keys)) { - keys = [keys]; - } - keys = keys.map(function(key) { - return key.toPacketlist(); - }); + keys = keys.length ? keys : [keys]; + keys = keys.map(key => key.toPacketlist()); } - self.worker.postMessage({ - event: 'encrypt-session-key', - sessionKey: sessionKey, - algo: algo, - keys: keys, - passwords: passwords - }); + this.worker.postMessage({ event:'encrypt-session-key', sessionKey, algo, keys, passwords }); }); }; -/** - * Signs message text and encrypts it - * @param {(Array|module:key~Key)} publicKeys array of keys or single key, used to encrypt the message - * @param {module:key~Key} privateKey private key with decrypted secret key data for signing - * @param {Uint8Array} text message as Uint8Array - */ AsyncProxy.prototype.signAndEncryptMessage = function(publicKeys, privateKey, text) { var self = this; @@ -216,36 +192,16 @@ AsyncProxy.prototype.signAndEncryptMessage = function(publicKeys, privateKey, te }); }; -/** - * Decrypts message - * @param {module:key~Key|String} privateKey private key with decrypted secret key data or string password - * @param {module:message~Message} msg the message object with the encrypted data - * @param {Object} params parameter object with optional properties binary {Boolean} - * and sessionKeyAlgorithm {String} which must only be set when privateKey is a session key - */ -AsyncProxy.prototype.decryptMessage = function(privateKey, message, params) { - var self = this; - - return self.execute(function() { +AsyncProxy.prototype.decryptMessage = function({ message, privateKey, format }) { + return this.execute(() => { if(!(String.prototype.isPrototypeOf(privateKey) || typeof privateKey === 'string' || Uint8Array.prototype.isPrototypeOf(privateKey))) { privateKey = privateKey.toPacketlist(); } - self.worker.postMessage({ - event: 'decrypt-message', - privateKey: privateKey, - message: message, - params: params - }); + this.worker.postMessage({ event:'decrypt-message', message, privateKey, format }); }); }; -/** - * @param {module:key~Key|String} privateKey private key with decrypted secret key data or string password - * @param {module:message~Message} msg the message object with the encrypted session key packets - * @return {Promise} decrypted session key and algorithm in object form - * or null if no key packets found - */ AsyncProxy.prototype.decryptSessionKey = function(privateKey, message) { var self = this; @@ -262,12 +218,6 @@ AsyncProxy.prototype.decryptSessionKey = function(privateKey, message) { }); }; -/** - * Decrypts message and verifies signatures - * @param {module:key~Key} privateKey private key with decrypted secret key data - * @param {(Array|module:key~Key)} publicKeys array of keys or single key to verify signatures - * @param {module:message~Message} message the message object with signed and encrypted data - */ AsyncProxy.prototype.decryptAndVerifyMessage = function(privateKey, publicKeys, message) { var self = this; @@ -298,11 +248,6 @@ AsyncProxy.prototype.decryptAndVerifyMessage = function(privateKey, publicKeys, return promise; }; -/** - * Signs a cleartext message - * @param {(Array|module:key~Key)} privateKeys array of keys or single key, with decrypted secret key data to sign cleartext - * @param {Uint8Array} text cleartext - */ AsyncProxy.prototype.signClearMessage = function(privateKeys, text) { var self = this; @@ -321,11 +266,6 @@ AsyncProxy.prototype.signClearMessage = function(privateKeys, text) { }); }; -/** - * Verifies signatures of cleartext signed message - * @param {(Array|module:key~Key)} publicKeys array of keys or single key, to verify signatures - * @param {module:cleartext~CleartextMessage} message cleartext message object with signatures - */ AsyncProxy.prototype.verifyClearSignedMessage = function(publicKeys, message) { var self = this; @@ -354,39 +294,6 @@ AsyncProxy.prototype.verifyClearSignedMessage = function(publicKeys, message) { return promise; }; -/** - * 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 - */ -AsyncProxy.prototype.generateKeyPair = function(options) { - var self = this; - - var promise = new Promise(function(resolve, reject) { - self.worker.postMessage({ - event: 'generate-key-pair', - options: options - }); - - self.tasks.push({ resolve:function(data) { - var packetlist = packet.List.fromStructuredClone(data.key); - data.key = new key.Key(packetlist); - resolve(data); - }, reject:reject }); - }); - - return promise; -}; - -/** - * 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 - */ AsyncProxy.prototype.decryptKey = function(privateKey, password) { var self = this; @@ -408,12 +315,6 @@ AsyncProxy.prototype.decryptKey = function(privateKey, password) { return promise; }; -/** - * 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 - */ AsyncProxy.prototype.decryptKeyPacket = function(privateKey, keyIds, password) { var self = this; diff --git a/src/worker/worker.js b/src/worker/worker.js index 742d6432..b11a8b1f 100644 --- a/src/worker/worker.js +++ b/src/worker/worker.js @@ -15,7 +15,9 @@ // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -this.window = {}; // to make UMD bundles work +/* globals self: true */ + +self.window = {}; // to make UMD bundles work // Mozilla bind polyfill because phantomjs is stupid if (!Function.prototype.bind) { @@ -46,7 +48,7 @@ var MAX_SIZE_RANDOM_BUFFER = 60000; window.openpgp.crypto.random.randomBuffer.init(MAX_SIZE_RANDOM_BUFFER); -this.onmessage = function (event) { +self.onmessage = function (event) { var data = null, err = null, msg = event.data, @@ -58,12 +60,14 @@ this.onmessage = function (event) { window.openpgp.config[i] = msg.config[i]; } break; + case 'seed-random': if (!(msg.buf instanceof Uint8Array)) { msg.buf = new Uint8Array(msg.buf); } window.openpgp.crypto.random.randomBuffer.set(msg.buf); break; + case 'encrypt-message': if(msg.keys) { msg.keys = msg.keys.map(packetlistCloneToKey); @@ -74,6 +78,7 @@ this.onmessage = function (event) { response({event: 'method-return', err: e.message}); }); break; + case 'encrypt-session-key': if(msg.keys) { msg.keys = msg.keys.map(packetlistCloneToKey); @@ -84,6 +89,7 @@ this.onmessage = function (event) { response({event: 'method-return', err: e.message}); }); break; + case 'sign-and-encrypt-message': if (!msg.publicKeys.length) { msg.publicKeys = [msg.publicKeys]; @@ -96,6 +102,7 @@ this.onmessage = function (event) { response({event: 'method-return', err: e.message}); }); break; + case 'decrypt-message': if(!(String.prototype.isPrototypeOf(msg.privateKey) || typeof msg.privateKey === 'string' || Uint8Array.prototype.isPrototypeOf(msg.privateKey))) { msg.privateKey = packetlistCloneToKey(msg.privateKey); @@ -107,6 +114,7 @@ this.onmessage = function (event) { response({event: 'method-return', err: e.message}); }); break; + case 'decrypt-session-key': if(!(String.prototype.isPrototypeOf(msg.privateKey) || typeof msg.privateKey === 'string')) { msg.privateKey = packetlistCloneToKey(msg.privateKey); @@ -118,6 +126,7 @@ this.onmessage = function (event) { response({event: 'method-return', err: e.message}); }); break; + case 'decrypt-and-verify-message': msg.privateKey = packetlistCloneToKey(msg.privateKey); if (!msg.publicKeys.length) { @@ -131,6 +140,7 @@ this.onmessage = function (event) { response({event: 'method-return', err: e.message}); }); break; + case 'sign-clear-message': msg.privateKeys = msg.privateKeys.map(packetlistCloneToKey); window.openpgp.signClearMessage(msg.privateKeys, msg.text).then(function(data) { @@ -139,6 +149,7 @@ this.onmessage = function (event) { response({event: 'method-return', err: e.message}); }); break; + case 'verify-clear-signed-message': if (!msg.publicKeys.length) { msg.publicKeys = [msg.publicKeys]; @@ -152,14 +163,16 @@ this.onmessage = function (event) { response({event: 'method-return', err: e.message}); }); break; - case 'generate-key-pair': - window.openpgp.generateKeyPair(msg.options).then(function(data) { + + case 'generate-key': + window.openpgp.generateKey(msg.options).then(function(data) { data.key = data.key.toPacketlist(); response({event: 'method-return', data: data}); }).catch(function(e) { response({event: 'method-return', err: e.message}); }); break; + case 'decrypt-key': try { msg.privateKey = packetlistCloneToKey(msg.privateKey); @@ -174,6 +187,7 @@ this.onmessage = function (event) { } response({event: 'method-return', data: data, err: err}); break; + case 'decrypt-key-packet': try { msg.privateKey = packetlistCloneToKey(msg.privateKey); @@ -189,6 +203,7 @@ this.onmessage = function (event) { } response({event: 'method-return', data: data, err: err}); break; + default: throw new Error('Unknown Worker Event.'); } diff --git a/test/general/index.js b/test/general/index.js index b0b1312c..7ed12b3a 100644 --- a/test/general/index.js +++ b/test/general/index.js @@ -1,5 +1,6 @@ describe('General', function () { require('./util.js'); + require('./openpgp.js'); require('./basic.js'); require('./armor.js'); require('./key.js'); diff --git a/test/general/openpgp.js b/test/general/openpgp.js new file mode 100644 index 00000000..dea2092f --- /dev/null +++ b/test/general/openpgp.js @@ -0,0 +1,267 @@ +'use strict'; + +var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); + +var sinon = require('sinon'), + chai = require('chai'), + expect = chai.expect; + +describe('OpenPGP.js public api tests', function() { + + describe('initWorker, getWorker, destroyWorker', function() { + afterEach(function() { + openpgp.destroyWorker(); // cleanup worker in case of failure + }); + + it('should work', function() { + var workerStub = { + postMessage: function() {} + }; + openpgp.initWorker({ + worker: workerStub + }); + expect(openpgp.getWorker()).to.exist; + openpgp.destroyWorker(); + expect(openpgp.getWorker()).to.not.exist; + }); + }); + + describe('generateKey - unit tests', function() { + var keyGenStub, keyObjStub, getWebCryptoStub; + + beforeEach(function() { + keyObjStub = { + armor: function() { + return 'priv_key'; + }, + toPublic: function() { + return { + armor: function() { + return 'pub_key'; + } + }; + } + }; + keyGenStub = sinon.stub(openpgp.key, 'generate'); + keyGenStub.returns(resolves(keyObjStub)); + getWebCryptoStub = sinon.stub(openpgp.util, 'getWebCrypto'); + }); + + afterEach(function() { + keyGenStub.restore(); + openpgp.destroyWorker(); + getWebCryptoStub.restore(); + }); + + it('should fail for invalid user name', function() { + var opt = { + userIds: [{ name: {}, email: 'text@example.com' }] + }; + var test = openpgp.generateKey.bind(null, opt); + expect(test).to.throw(/Invalid user id format/); + }); + + it('should fail for invalid user email address', function() { + var opt = { + userIds: [{ name: 'Test User', email: 'textexample.com' }] + }; + var test = openpgp.generateKey.bind(null, opt); + expect(test).to.throw(/Invalid user id format/); + }); + + it('should fail for invalid user email address', function() { + var opt = { + userIds: [{ name: 'Test User', email: 'text@examplecom' }] + }; + var test = openpgp.generateKey.bind(null, opt); + expect(test).to.throw(/Invalid user id format/); + }); + + it('should fail for invalid string user id', function() { + var opt = { + userIds: ['Test User text@example.com>'] + }; + var test = openpgp.generateKey.bind(null, opt); + expect(test).to.throw(/Invalid user id format/); + }); + + it('should fail for invalid single string user id', function() { + var opt = { + userIds: 'Test User text@example.com>' + }; + var test = openpgp.generateKey.bind(null, opt); + expect(test).to.throw(/Invalid user id format/); + }); + + it('should work for valid single string user id', function(done) { + var opt = { + userIds: 'Test User ' + }; + openpgp.generateKey(opt).then(function() { done(); }); + }); + + it('should work for valid string user id', function(done) { + var opt = { + userIds: ['Test User '] + }; + openpgp.generateKey(opt).then(function() { done(); }); + }); + + it('should work for valid single user id hash', function(done) { + var opt = { + userIds: { name: 'Test User', email: 'text@example.com' } + }; + openpgp.generateKey(opt).then(function() { done(); }); + }); + + it('should work for valid single user id hash', function(done) { + var opt = { + userIds: [{ name: 'Test User', email: 'text@example.com' }] + }; + openpgp.generateKey(opt).then(function() { done(); }); + }); + + it('should work for an empty name', function(done) { + var opt = { + userIds: { email: 'text@example.com' } + }; + openpgp.generateKey(opt).then(function() { done(); }); + }); + + it('should work for an empty email address', function(done) { + var opt = { + userIds: { name: 'Test User' } + }; + openpgp.generateKey(opt).then(function() { done(); }); + }); + + it('should have default params set', function(done) { + var opt = { + userIds: { name: 'Test User', email: 'text@example.com' }, + passphrase: 'secret', + unlocked: true + }; + openpgp.generateKey(opt).then(function(newKey) { + expect(keyGenStub.withArgs({ + userIds: ['Test User '], + passphrase: 'secret', + numBits: 2048, + unlocked: true + }).calledOnce).to.be.true; + expect(newKey.key).to.exist; + expect(newKey.privateKeyArmored).to.exist; + expect(newKey.publicKeyArmored).to.exist; + done(); + }); + }); + + it('should work for no params', function(done) { + openpgp.generateKey().then(function(newKey) { + expect(keyGenStub.withArgs({ + userIds: [], + passphrase: undefined, + numBits: 2048, + unlocked: false + }).calledOnce).to.be.true; + expect(newKey.key).to.exist; + done(); + }); + }); + + it('should delegate to async proxy', function() { + var workerStub = { + postMessage: function() {} + }; + openpgp.initWorker({ + worker: workerStub + }); + var proxyGenStub = sinon.stub(openpgp.getWorker(), 'generateKey'); + getWebCryptoStub.returns(); + + openpgp.generateKey(); + expect(proxyGenStub.calledOnce).to.be.true; + expect(keyGenStub.calledOnce).to.be.false; + }); + + it('should delegate to async proxy after web crypto failure', function(done) { + var workerStub = { + postMessage: function() {} + }; + openpgp.initWorker({ + worker: workerStub + }); + var proxyGenStub = sinon.stub(openpgp.getWorker(), 'generateKey').returns(resolves('proxy_key')); + getWebCryptoStub.returns({}); + keyGenStub.returns(rejects(new Error('Native webcrypto keygen failed on purpose :)'))); + + openpgp.generateKey().then(function(newKey) { + expect(keyGenStub.calledOnce).to.be.true; + expect(proxyGenStub.calledOnce).to.be.true; + expect(newKey).to.equal('proxy_key'); + done(); + }); + }); + }); + + describe('generateKey - integration tests', function() { + var useNativeVal; + + beforeEach(function() { + useNativeVal = openpgp.config.useNative; + }); + + afterEach(function() { + openpgp.config.useNative = useNativeVal; + openpgp.destroyWorker(); + }); + + it('should work in JS (without worker)', function(done) { + openpgp.config.useNative = false; + openpgp.destroyWorker(); + var opt = { + userIds: [{ name: 'Test User', email: 'text@example.com' }], + numBits: 512 + }; + + openpgp.generateKey(opt).then(function(newKey) { + expect(newKey.key.getUserIds()[0]).to.equal('Test User '); + expect(newKey.privateKeyArmored).to.exist; + expect(newKey.publicKeyArmored).to.exist; + done(); + }); + }); + + it('should work in JS (with worker)', function(done) { + openpgp.config.useNative = false; + openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); + var opt = { + userIds: [{ name: 'Test User', email: 'text@example.com' }], + numBits: 512 + }; + + openpgp.generateKey(opt).then(function(newKey) { + expect(newKey.key.getUserIds()[0]).to.equal('Test User '); + expect(newKey.privateKeyArmored).to.exist; + expect(newKey.publicKeyArmored).to.exist; + done(); + }); + }); + + it('should work in JS (use native)', function(done) { + openpgp.config.useNative = true; + var opt = { + userIds: [{ name: 'Test User', email: 'text@example.com' }], + numBits: 512 + }; + if (openpgp.util.getWebCrypto()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys + + openpgp.generateKey(opt).then(function(newKey) { + expect(newKey.key.getUserIds()[0]).to.equal('Test User '); + expect(newKey.privateKeyArmored).to.exist; + expect(newKey.publicKeyArmored).to.exist; + done(); + }); + }); + }); + +}); diff --git a/test/general/util.js b/test/general/util.js index 75d3354b..48e791fd 100644 --- a/test/general/util.js +++ b/test/general/util.js @@ -7,10 +7,6 @@ var chai = require('chai'), describe('Util unit tests', function() { - beforeEach(function() {}); - - afterEach(function() {}); - describe('isString', function() { it('should return true for type "string"', function() { var data = 'foo';