Write tests for new api: openpgp.generateKey

This commit is contained in:
Tankred Hase 2016-02-08 19:32:42 +07:00
parent c38d41036e
commit a44e1e5024
8 changed files with 416 additions and 194 deletions

View File

@ -223,11 +223,10 @@ module.exports = function(grunt) {
} }
}, },
}, },
watch: { watch: {
src: { src: {
files: ['src/**/*.js'], files: ['src/**/*.js'],
tasks: ['browserify:openpgp'] tasks: ['browserify:openpgp', 'browserify:worker']
}, },
test: { test: {
files: ['test/*.js', 'test/crypto/**/*.js', 'test/general/**/*.js', 'test/worker/**/*.js'], files: ['test/*.js', 'test/crypto/**/*.js', 'test/general/**/*.js', 'test/worker/**/*.js'],

View File

@ -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. * @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} * 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 {Integer} options.numBits number of bits for the key creation.
* @param {String|Array<String>} options.userId assumes already in form of "User Name <username@email.com>" * @param {String|Array<String>} options.userIds assumes already in form of "User Name <username@email.com>"
If array is used, the first userId is set as primary user Id 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 {String} options.passphrase The passphrase used to encrypt the resulting private key
* @param {Boolean} [options.unlocked=false] The secret part of the generated key is unlocked * @param {Boolean} [options.unlocked=false] The secret part of the generated key is unlocked
@ -943,8 +943,8 @@ export function generate(options) {
if (!options.passphrase) { if (!options.passphrase) {
options.unlocked = true; options.unlocked = true;
} }
if (String.prototype.isPrototypeOf(options.userId) || typeof options.userId === 'string') { if (String.prototype.isPrototypeOf(options.userIds) || typeof options.userIds === 'string') {
options.userId = [options.userId]; options.userIds = [options.userIds];
} }
// generate // generate
@ -975,7 +975,7 @@ export function generate(options) {
packetlist.push(secretKeyPacket); packetlist.push(secretKeyPacket);
options.userId.forEach(function(userId, index) { options.userIds.forEach(function(userId, index) {
userIdPacket = new packet.Userid(); userIdPacket = new packet.Userid();
userIdPacket.read(util.str2Uint8Array(userId)); userIdPacket.read(util.str2Uint8Array(userId));

View File

@ -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 * 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; return asyncProxy;
} }
/**
* Cleanup the current instance of the web worker.
*/
export function destroyWorker() {
asyncProxy = undefined;
}
//////////////////////////// ////////////////////////////
// // // //
@ -84,8 +91,8 @@ export function getWorker() {
* @static * @static
*/ */
export function generateKey({ userIds=[], passphrase, numBits=2048, unlocked=false } = {}) { 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 }; const options = { userIds, passphrase, numBits, unlocked };
formatUserIds(options);
if (!util.getWebCrypto() && asyncProxy) { // use web worker if web crypto apis are not supported if (!util.getWebCrypto() && asyncProxy) { // use web worker if web crypto apis are not supported
return asyncProxy.generateKey(options); return asyncProxy.generateKey(options);
@ -100,12 +107,12 @@ export function generateKey({ userIds=[], passphrase, numBits=2048, unlocked=fal
})).catch(err => { })).catch(err => {
// js fallback already tried // js fallback already tried
console.error(err); if (config.debug) { console.error(err); }
if (!util.getWebCrypto()) { if (!util.getWebCrypto()) {
throw new Error('Error generating keypair using js fallback!'); throw new Error('Error generating keypair using js fallback!');
} }
// fall back to js keygen in a worker // 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); return asyncProxy.generateKey(options);
}).catch(onError.bind(null, 'Error generating keypair!')); }).catch(onError.bind(null, 'Error generating keypair!'));
@ -132,8 +139,8 @@ export function generateKey({ userIds=[], passphrase, numBits=2048, unlocked=fal
* @static * @static
*/ */
export function encrypt({ data, publicKeys, privateKeys, passwords, filename, packets }) { export function encrypt({ data, publicKeys, privateKeys, passwords, filename, packets }) {
publicKeys = publicKeys ? (publicKeys.length ? publicKeys : [publicKeys]) : undefined; // normalize key objects to arrays publicKeys = toArray(publicKeys);
privateKeys = privateKeys ? (privateKeys.length ? privateKeys : [privateKeys]) : undefined; privateKeys = toArray(privateKeys);
if (asyncProxy) { // use web worker if available if (asyncProxy) { // use web worker if available
return asyncProxy.encrypt({ data, publicKeys, privateKeys, passwords, filename, packets }); return asyncProxy.encrypt({ data, publicKeys, privateKeys, passwords, filename, packets });
@ -170,7 +177,7 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, filename, pa
* @static * @static
*/ */
export function decrypt({ message, privateKey, publickeys, sessionKey, password, format='utf8' }) { 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 if (asyncProxy) { // use web worker if available
return asyncProxy.decrypt({ message, privateKey, publickeys, sessionKey, password, format }); return asyncProxy.decrypt({ message, privateKey, publickeys, sessionKey, password, format });
@ -204,7 +211,10 @@ export function decrypt({ message, privateKey, publickeys, sessionKey, password,
* @static * @static
*/ */
export function sign({ data, privateKeys }) { 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 if (asyncProxy) { // use web worker if available
return asyncProxy.sign({ data, privateKeys }); return asyncProxy.sign({ data, privateKeys });
@ -230,23 +240,21 @@ export function sign({ data, privateKeys }) {
* @static * @static
*/ */
export function verify({ message, publicKeys }) { 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 if (asyncProxy) { // use web worker if available
return asyncProxy.verify({ message, publicKeys }); 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(), data: message.getText(),
signatures: message.verify(publicKeys) 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<Object>|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. * Creates a message obejct either from a Uint8Array or a string.
* @param {String|Uint8Array} data the payload for the message * @param {String|Uint8Array} data the payload for the message
@ -314,9 +359,9 @@ export function decryptSessionKey({ message, privateKey, sessionKey, password })
*/ */
function createMessage(data, filename) { function createMessage(data, filename) {
let msg; let msg;
if (data instanceof Uint8Array) { if (util.isUint8Array(data)) {
msg = messageLib.fromBinary(data, filename); msg = messageLib.fromBinary(data, filename);
} else if (typeof data === 'string') { } else if (util.isString(data)) {
msg = messageLib.fromText(data, filename); msg = messageLib.fromText(data, filename);
} else { } else {
throw new Error('Data must be of type String or Uint8Array!'); throw new Error('Data must be of type String or Uint8Array!');
@ -391,9 +436,7 @@ function execute(cmd, message) {
*/ */
function onError(message, error) { function onError(message, error) {
// log the stack trace // log the stack trace
if (config.debug) { if (config.debug) { console.error(error.stack); }
console.error(error.stack);
}
// rethrow new high level error for api users // rethrow new high level error for api users
throw new Error(message); throw new Error(message);
} }

View File

@ -31,58 +31,49 @@ import packet from '../packet';
import * as key from '../key.js'; import * as key from '../key.js';
import type_keyid from '../type/keyid.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 RANDOM_SEED_REQUEST = 20000; // random bytes seeded after worker request
/** /**
* Initializes a new proxy and loads the web worker * Initializes a new proxy and loads the web worker
* @constructor * @constructor
* @param {String} path The path to the worker or 'openpgp.worker.js' by default * @param {String} path The path to the worker or 'openpgp.worker.js' by default
* @param {Object} [options.config=Object] config The worker configuration * @param {Object} config config The worker configuration
* @param {Object} [options.worker=Object] alternative to path parameter: * @param {Object} worker alternative to path parameter: web worker initialized with 'openpgp.worker.js'
* web worker initialized with 'openpgp.worker.js' * @return {Promise}
*/ */
export default function AsyncProxy(path, options) { export default function AsyncProxy({ path='openpgp.worker.js', worker, config } = {}) {
if (options && options.worker) { this.worker = worker || new Worker(path);
this.worker = options.worker;
} else {
this.worker = new Worker(path || 'openpgp.worker.js');
}
this.worker.onmessage = this.onMessage.bind(this); 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 + ')'); throw new Error('Unhandled error in openpgp worker: ' + e.message + ' (' + e.filename + ':' + e.lineno + ')');
}; };
this.seedRandom(INITIAL_RANDOM_SEED); this.seedRandom(INITIAL_RANDOM_SEED);
// FIFO // FIFO
this.tasks = []; this.tasks = [];
if (options && options.config) { if (config) {
this.worker.postMessage({event: 'configure', config: options.config}); this.worker.postMessage({ event:'configure', config });
} }
} }
/** /**
* Command pattern that wraps synchronous code into a promise * 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 * @param {function} cmd The synchronous function with a return value
* to be wrapped in a promise * to be wrapped in a promise
* @return {Promise} The promise wrapped around cmd * @return {Promise} The promise wrapped around cmd
*/ */
AsyncProxy.prototype.execute = function(cmd) { AsyncProxy.prototype.execute = function(cmd) {
var self = this; return new Promise((resolve, reject) => {
var promise = new Promise(function(resolve, reject) {
cmd(); cmd();
self.tasks.push({ resolve:resolve, reject:reject }); this.tasks.push({ resolve, reject });
}); });
return promise;
}; };
/** /**
* Message handling * Message handling
*/ */
AsyncProxy.prototype.onMessage = function(event) { AsyncProxy.prototype.onMessage = function(event) {
var msg = event.data; const msg = event.data;
switch (msg.event) { switch (msg.event) {
case 'method-return': case 'method-return':
if (msg.err) { if (msg.err) {
@ -106,8 +97,8 @@ AsyncProxy.prototype.onMessage = function(event) {
* @param {Integer} size Number of bytes to send * @param {Integer} size Number of bytes to send
*/ */
AsyncProxy.prototype.seedRandom = function(size) { AsyncProxy.prototype.seedRandom = function(size) {
var buf = this.getRandomBuffer(size); const buf = this.getRandomBuffer(size);
this.worker.postMessage({event: 'seed-random', buf: buf}); this.worker.postMessage({ event:'seed-random', buf });
}; };
/** /**
@ -119,7 +110,7 @@ AsyncProxy.prototype.getRandomBuffer = function(size) {
if (!size) { if (!size) {
return null; return null;
} }
var buf = new Uint8Array(size); const buf = new Uint8Array(size);
crypto.random.getRandomValues(buf); crypto.random.getRandomValues(buf);
return buf; return buf;
}; };
@ -131,71 +122,56 @@ AsyncProxy.prototype.terminate = function() {
this.worker.terminate(); this.worker.terminate();
}; };
/**
* Encrypts message text/data with keys or passwords
* @param {(Array<module:key~Key>|module:key~Key)} keys array of keys or single key, used to encrypt the message
* @param {Uint8Array} data message as Uint8Array
* @param {(Array<String>|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)) { // Proxy functions. See the corresponding code in the openpgp module for the documentation. //
keys = [keys]; // //
} //////////////////////////////////////////////////////////////////////////////////////////////////
keys = keys.map(function(key) {
return key.toPacketlist();
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({ if(privateKeys) {
event: 'encrypt-message', privateKeys = privateKeys.length ? privateKeys : [privateKeys];
keys: keys, privateKeys = privateKeys.map(key => key.toPacketlist());
data: data, }
passwords: passwords, this.worker.postMessage({
params: params event:'encrypt',
options: { data, publicKeys, privateKeys, passwords, filename, packets }
}); });
}); });
}; };
/** AsyncProxy.prototype.encryptSessionKey = function({ sessionKey, algo, keys, passwords }) {
* Encrypts session key with keys or passwords return this.execute(() => {
* @param {Uint8Array} sessionKey sessionKey as Uint8Array
* @param {String} algo algorithm of sessionKey
* @param {(Array<module:key~Key>|module:key~Key)} keys array of keys or single key, used to encrypt the key
* @param {(Array<String>|String)} passwords passwords for the message
*/
AsyncProxy.prototype.encryptSessionKey = function(sessionKey, algo, keys, passwords) {
var self = this;
return self.execute(function() {
if(keys) { if(keys) {
if (!Array.prototype.isPrototypeOf(keys)) { keys = keys.length ? keys : [keys];
keys = [keys]; keys = keys.map(key => key.toPacketlist());
} }
keys = keys.map(function(key) { this.worker.postMessage({ event:'encrypt-session-key', sessionKey, algo, keys, passwords });
return key.toPacketlist();
});
}
self.worker.postMessage({
event: 'encrypt-session-key',
sessionKey: sessionKey,
algo: algo,
keys: keys,
passwords: passwords
});
}); });
}; };
/**
* Signs message text and encrypts it
* @param {(Array<module:key~Key>|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) { AsyncProxy.prototype.signAndEncryptMessage = function(publicKeys, privateKey, text) {
var self = this; var self = this;
@ -216,36 +192,16 @@ AsyncProxy.prototype.signAndEncryptMessage = function(publicKeys, privateKey, te
}); });
}; };
/** AsyncProxy.prototype.decryptMessage = function({ message, privateKey, format }) {
* Decrypts message return this.execute(() => {
* @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() {
if(!(String.prototype.isPrototypeOf(privateKey) || typeof privateKey === 'string' || Uint8Array.prototype.isPrototypeOf(privateKey))) { if(!(String.prototype.isPrototypeOf(privateKey) || typeof privateKey === 'string' || Uint8Array.prototype.isPrototypeOf(privateKey))) {
privateKey = privateKey.toPacketlist(); privateKey = privateKey.toPacketlist();
} }
self.worker.postMessage({ this.worker.postMessage({ event:'decrypt-message', message, privateKey, format });
event: 'decrypt-message',
privateKey: privateKey,
message: message,
params: params
});
}); });
}; };
/**
* @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<Object|null>} decrypted session key and algorithm in object form
* or null if no key packets found
*/
AsyncProxy.prototype.decryptSessionKey = function(privateKey, message) { AsyncProxy.prototype.decryptSessionKey = function(privateKey, message) {
var self = this; 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>|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) { AsyncProxy.prototype.decryptAndVerifyMessage = function(privateKey, publicKeys, message) {
var self = this; var self = this;
@ -298,11 +248,6 @@ AsyncProxy.prototype.decryptAndVerifyMessage = function(privateKey, publicKeys,
return promise; return promise;
}; };
/**
* Signs a cleartext message
* @param {(Array<module:key~Key>|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) { AsyncProxy.prototype.signClearMessage = function(privateKeys, text) {
var self = this; var self = this;
@ -321,11 +266,6 @@ AsyncProxy.prototype.signClearMessage = function(privateKeys, text) {
}); });
}; };
/**
* Verifies signatures of cleartext signed message
* @param {(Array<module:key~Key>|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) { AsyncProxy.prototype.verifyClearSignedMessage = function(publicKeys, message) {
var self = this; var self = this;
@ -354,39 +294,6 @@ AsyncProxy.prototype.verifyClearSignedMessage = function(publicKeys, message) {
return promise; 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 <username@email.com>"
* @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) { AsyncProxy.prototype.decryptKey = function(privateKey, password) {
var self = this; var self = this;
@ -408,12 +315,6 @@ AsyncProxy.prototype.decryptKey = function(privateKey, password) {
return promise; 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<module:type/keyid>} keyIds
* @param {String} password password to unlock the key
*/
AsyncProxy.prototype.decryptKeyPacket = function(privateKey, keyIds, password) { AsyncProxy.prototype.decryptKeyPacket = function(privateKey, keyIds, password) {
var self = this; var self = this;

View File

@ -15,7 +15,9 @@
// License along with this library; if not, write to the Free Software // License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // 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 // Mozilla bind polyfill because phantomjs is stupid
if (!Function.prototype.bind) { if (!Function.prototype.bind) {
@ -46,7 +48,7 @@ var MAX_SIZE_RANDOM_BUFFER = 60000;
window.openpgp.crypto.random.randomBuffer.init(MAX_SIZE_RANDOM_BUFFER); window.openpgp.crypto.random.randomBuffer.init(MAX_SIZE_RANDOM_BUFFER);
this.onmessage = function (event) { self.onmessage = function (event) {
var data = null, var data = null,
err = null, err = null,
msg = event.data, msg = event.data,
@ -58,12 +60,14 @@ this.onmessage = function (event) {
window.openpgp.config[i] = msg.config[i]; window.openpgp.config[i] = msg.config[i];
} }
break; break;
case 'seed-random': case 'seed-random':
if (!(msg.buf instanceof Uint8Array)) { if (!(msg.buf instanceof Uint8Array)) {
msg.buf = new Uint8Array(msg.buf); msg.buf = new Uint8Array(msg.buf);
} }
window.openpgp.crypto.random.randomBuffer.set(msg.buf); window.openpgp.crypto.random.randomBuffer.set(msg.buf);
break; break;
case 'encrypt-message': case 'encrypt-message':
if(msg.keys) { if(msg.keys) {
msg.keys = msg.keys.map(packetlistCloneToKey); msg.keys = msg.keys.map(packetlistCloneToKey);
@ -74,6 +78,7 @@ this.onmessage = function (event) {
response({event: 'method-return', err: e.message}); response({event: 'method-return', err: e.message});
}); });
break; break;
case 'encrypt-session-key': case 'encrypt-session-key':
if(msg.keys) { if(msg.keys) {
msg.keys = msg.keys.map(packetlistCloneToKey); msg.keys = msg.keys.map(packetlistCloneToKey);
@ -84,6 +89,7 @@ this.onmessage = function (event) {
response({event: 'method-return', err: e.message}); response({event: 'method-return', err: e.message});
}); });
break; break;
case 'sign-and-encrypt-message': case 'sign-and-encrypt-message':
if (!msg.publicKeys.length) { if (!msg.publicKeys.length) {
msg.publicKeys = [msg.publicKeys]; msg.publicKeys = [msg.publicKeys];
@ -96,6 +102,7 @@ this.onmessage = function (event) {
response({event: 'method-return', err: e.message}); response({event: 'method-return', err: e.message});
}); });
break; break;
case 'decrypt-message': case 'decrypt-message':
if(!(String.prototype.isPrototypeOf(msg.privateKey) || typeof msg.privateKey === 'string' || Uint8Array.prototype.isPrototypeOf(msg.privateKey))) { if(!(String.prototype.isPrototypeOf(msg.privateKey) || typeof msg.privateKey === 'string' || Uint8Array.prototype.isPrototypeOf(msg.privateKey))) {
msg.privateKey = packetlistCloneToKey(msg.privateKey); msg.privateKey = packetlistCloneToKey(msg.privateKey);
@ -107,6 +114,7 @@ this.onmessage = function (event) {
response({event: 'method-return', err: e.message}); response({event: 'method-return', err: e.message});
}); });
break; break;
case 'decrypt-session-key': case 'decrypt-session-key':
if(!(String.prototype.isPrototypeOf(msg.privateKey) || typeof msg.privateKey === 'string')) { if(!(String.prototype.isPrototypeOf(msg.privateKey) || typeof msg.privateKey === 'string')) {
msg.privateKey = packetlistCloneToKey(msg.privateKey); msg.privateKey = packetlistCloneToKey(msg.privateKey);
@ -118,6 +126,7 @@ this.onmessage = function (event) {
response({event: 'method-return', err: e.message}); response({event: 'method-return', err: e.message});
}); });
break; break;
case 'decrypt-and-verify-message': case 'decrypt-and-verify-message':
msg.privateKey = packetlistCloneToKey(msg.privateKey); msg.privateKey = packetlistCloneToKey(msg.privateKey);
if (!msg.publicKeys.length) { if (!msg.publicKeys.length) {
@ -131,6 +140,7 @@ this.onmessage = function (event) {
response({event: 'method-return', err: e.message}); response({event: 'method-return', err: e.message});
}); });
break; break;
case 'sign-clear-message': case 'sign-clear-message':
msg.privateKeys = msg.privateKeys.map(packetlistCloneToKey); msg.privateKeys = msg.privateKeys.map(packetlistCloneToKey);
window.openpgp.signClearMessage(msg.privateKeys, msg.text).then(function(data) { 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}); response({event: 'method-return', err: e.message});
}); });
break; break;
case 'verify-clear-signed-message': case 'verify-clear-signed-message':
if (!msg.publicKeys.length) { if (!msg.publicKeys.length) {
msg.publicKeys = [msg.publicKeys]; msg.publicKeys = [msg.publicKeys];
@ -152,14 +163,16 @@ this.onmessage = function (event) {
response({event: 'method-return', err: e.message}); response({event: 'method-return', err: e.message});
}); });
break; 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(); data.key = data.key.toPacketlist();
response({event: 'method-return', data: data}); response({event: 'method-return', data: data});
}).catch(function(e) { }).catch(function(e) {
response({event: 'method-return', err: e.message}); response({event: 'method-return', err: e.message});
}); });
break; break;
case 'decrypt-key': case 'decrypt-key':
try { try {
msg.privateKey = packetlistCloneToKey(msg.privateKey); msg.privateKey = packetlistCloneToKey(msg.privateKey);
@ -174,6 +187,7 @@ this.onmessage = function (event) {
} }
response({event: 'method-return', data: data, err: err}); response({event: 'method-return', data: data, err: err});
break; break;
case 'decrypt-key-packet': case 'decrypt-key-packet':
try { try {
msg.privateKey = packetlistCloneToKey(msg.privateKey); msg.privateKey = packetlistCloneToKey(msg.privateKey);
@ -189,6 +203,7 @@ this.onmessage = function (event) {
} }
response({event: 'method-return', data: data, err: err}); response({event: 'method-return', data: data, err: err});
break; break;
default: default:
throw new Error('Unknown Worker Event.'); throw new Error('Unknown Worker Event.');
} }

View File

@ -1,5 +1,6 @@
describe('General', function () { describe('General', function () {
require('./util.js'); require('./util.js');
require('./openpgp.js');
require('./basic.js'); require('./basic.js');
require('./armor.js'); require('./armor.js');
require('./key.js'); require('./key.js');

267
test/general/openpgp.js Normal file
View File

@ -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 <text@example.com>'
};
openpgp.generateKey(opt).then(function() { done(); });
});
it('should work for valid string user id', function(done) {
var opt = {
userIds: ['Test User <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 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 <text@example.com>'],
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 <text@example.com>');
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 <text@example.com>');
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 <text@example.com>');
expect(newKey.privateKeyArmored).to.exist;
expect(newKey.publicKeyArmored).to.exist;
done();
});
});
});
});

View File

@ -7,10 +7,6 @@ var chai = require('chai'),
describe('Util unit tests', function() { describe('Util unit tests', function() {
beforeEach(function() {});
afterEach(function() {});
describe('isString', function() { describe('isString', function() {
it('should return true for type "string"', function() { it('should return true for type "string"', function() {
var data = 'foo'; var data = 'foo';