diff --git a/.travis.yml b/.travis.yml index 3b96a0ea..2114a728 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,10 +19,10 @@ matrix: env: OPENPGPJSTEST='end2end-4' BROWSER='chrome 46' - node_js: "4" env: OPENPGPJSTEST='end2end-1' BROWSER='firefox 42' - - node_js: "4" - env: OPENPGPJSTEST='end2end-6' BROWSER='internet explorer 11' - node_js: "4" env: OPENPGPJSTEST='end2end-9' BROWSER='safari 9' + - node_js: "4" + env: OPENPGPJSTEST='end2end-6' BROWSER='internet explorer 11' - node_js: "4" env: OPENPGPJSTEST='end2end-0' BROWSER='firefox 38' - node_js: "4" @@ -48,6 +48,7 @@ matrix: - env: OPENPGPJSTEST='end2end-2' BROWSER='firefox beta' - env: OPENPGPJSTEST='end2end-3' BROWSER='chrome 38' - env: OPENPGPJSTEST='end2end-5' BROWSER='chrome beta' + - env: OPENPGPJSTEST='end2end-6' BROWSER='internet explorer 11' - env: OPENPGPJSTEST='end2end-7' BROWSER='microsoft edge 20.10240' - env: OPENPGPJSTEST='end2end-8' BROWSER='safari 8' - env: OPENPGPJSTEST='end2end-10' BROWSER='android 4.4' diff --git a/README.md b/README.md index 57059843..235a7f6e 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,11 @@ OpenPGP.js [![Build Status](https://travis-ci.org/openpgpjs/openpgpjs.svg?branch * Version 2.x of the library has been built from the ground up with Uint8Arrays. This allows for much better performance and memory usage than strings. -* If the user's browser supports [native WebCrypto](http://caniuse.com/#feat=cryptography) via the `window.crypto.subtle` api, this will be used. Under node.js the native [crypto module](https://nodejs.org/api/crypto.html#crypto_crypto) is used. This can be deactivated by setting `openpgp.config.useNative = false`. +* If the user's browser supports [native WebCrypto](http://caniuse.com/#feat=cryptography) via the `window.crypto.subtle` api, this will be used. Under node.js the native [crypto module](https://nodejs.org/api/crypto.html#crypto_crypto) is used. This can be deactivated by setting `openpgp.config.use_native = false`. -* For environments that don't provide native crypto, the library falls back to [asm.js](http://caniuse.com/#feat=asmjs) implementations of AES-CFB, SHA-1, and SHA-256. We use [Rusha](https://github.com/srijs/rusha) and [asmCrypto Lite](https://github.com/openpgpjs/asmcrypto-lite) (a minimal subset of asmCrypto.js built specifically for OpenPGP.js). +* The library implements the [IETF proposal](https://tools.ietf.org/html/draft-ford-openpgp-format-00) for authenticated encryption using native AES-GCM. This makes symmetric encryption about 30x faster on supported platforms. Since the specification has not been finalized and other OpenPGP implementations haven't adopted it yet, the feature is currently behind a flag. You can activate it by setting `openpgp.config.aead_protect = true`. + +* For environments that don't provide native crypto, the library falls back to [asm.js](http://caniuse.com/#feat=asmjs) implementations of AES, SHA-1, and SHA-256. We use [Rusha](https://github.com/srijs/rusha) and [asmCrypto Lite](https://github.com/openpgpjs/asmcrypto-lite) (a minimal subset of asmCrypto.js built specifically for OpenPGP.js). ### Getting started @@ -47,6 +49,8 @@ Here are some examples of how to use the v2.x api. For more elaborate examples a var openpgp = require('openpgp'); // use as CommonJS, AMD, ES6 module or via window.openpgp openpgp.initWorker({ path:'openpgp.worker.js' }) // set the relative web worker path + +openpgp.config.aead_protect = true // activate fast AES-GCM mode (experimental) ``` #### Encrypt and decrypt *String* data with a password diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index b665fff2..46a5c16a 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -3,9 +3,9 @@ "version": "2.1.0", "dependencies": { "asmcrypto-lite": { - "version": "1.0.1", + "version": "1.1.0", "from": "asmcrypto-lite@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/asmcrypto-lite/-/asmcrypto-lite-1.0.1.tgz" + "resolved": "https://registry.npmjs.org/asmcrypto-lite/-/asmcrypto-lite-1.1.0.tgz" }, "babel-preset-es2015": { "version": "6.5.0", diff --git a/src/config/config.js b/src/config/config.js index 78c97647..6a070b76 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -37,11 +37,12 @@ export default { prefer_hash_algorithm: enums.hash.sha256, encryption_cipher: enums.symmetric.aes256, compression: enums.compression.zip, + aead_protect: false, // use Authenticated Encryption with Additional Data (AEAD) protection for symmetric encryption integrity_protect: true, // use integrity protection for symmetric encryption ignore_mdc_error: false, // fail on decrypt if message is not integrity protected rsa_blinding: true, - useNative: true, // use native node.js crypto and Web Crypto apis (if available) - zeroCopy: false, // use transferable objects between the Web Worker and main thread + use_native: true, // use native node.js crypto and Web Crypto apis (if available) + zero_copy: false, // use transferable objects between the Web Worker and main thread debug: false, show_version: true, show_comment: true, diff --git a/src/crypto/gcm.js b/src/crypto/gcm.js new file mode 100644 index 00000000..b06b7318 --- /dev/null +++ b/src/crypto/gcm.js @@ -0,0 +1,117 @@ +// OpenPGP.js - An OpenPGP implementation in javascript +// Copyright (C) 2016 Tankred Hase +// +// 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 3.0 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 + +/** + * @fileoverview This module wraps native AES-GCM en/decryption for both + * the WebCrypto api as well as node.js' crypto api. + */ + +'use strict'; + +import util from '../util.js'; +import config from '../config'; +import asmCrypto from 'asmcrypto-lite'; +const webCrypto = util.getWebCrypto(); // no GCM support in IE11, Safari 9 +const nodeCrypto = util.getNodeCrypto(); +const Buffer = util.getNodeBuffer(); + +export const ivLength = 12; // size of the IV in bytes +const TAG_LEN = 16; // size of the tag in bytes +const ALGO = 'AES-GCM'; + +/** + * Encrypt plaintext input. + * @param {String} cipher The symmetric cipher algorithm to use e.g. 'aes128' + * @param {Uint8Array} plaintext The cleartext input to be encrypted + * @param {Uint8Array} key The encryption key + * @param {Uint8Array} iv The initialization vector (12 bytes) + * @return {Promise} The ciphertext output + */ +export function encrypt(cipher, plaintext, key, iv) { + if (cipher.substr(0,3) !== 'aes') { + return Promise.reject(new Error('GCM mode supports only AES cipher')); + } + + if (webCrypto && config.use_native && key.length !== 24) { // WebCrypto (no 192 bit support) see: https://www.chromium.org/blink/webcrypto#TOC-AES-support + return webEncrypt(plaintext, key, iv); + } else if (nodeCrypto && config.use_native) { // Node crypto library + return nodeEncrypt(plaintext, key, iv) ; + } else { // asm.js fallback + return Promise.resolve(asmCrypto.AES_GCM.encrypt(plaintext, key, iv)); + } +} + +/** + * Decrypt ciphertext input. + * @param {String} cipher The symmetric cipher algorithm to use e.g. 'aes128' + * @param {Uint8Array} ciphertext The ciphertext input to be decrypted + * @param {Uint8Array} key The encryption key + * @param {Uint8Array} iv The initialization vector (12 bytes) + * @return {Promise} The plaintext output + */ +export function decrypt(cipher, ciphertext, key, iv) { + if (cipher.substr(0,3) !== 'aes') { + return Promise.reject(new Error('GCM mode supports only AES cipher')); + } + + if (webCrypto && config.use_native && key.length !== 24) { // WebCrypto (no 192 bit support) see: https://www.chromium.org/blink/webcrypto#TOC-AES-support + return webDecrypt(ciphertext, key, iv); + } else if (nodeCrypto && config.use_native) { // Node crypto library + return nodeDecrypt(ciphertext, key, iv); + } else { // asm.js fallback + return Promise.resolve(asmCrypto.AES_GCM.decrypt(ciphertext, key, iv)); + } +} + + +////////////////////////// +// // +// Helper functions // +// // +////////////////////////// + + +function webEncrypt(pt, key, iv) { + return webCrypto.importKey('raw', key, { name: ALGO }, false, ['encrypt']) + .then(keyObj => webCrypto.encrypt({ name: ALGO, iv }, keyObj, pt)) + .then(ct => new Uint8Array(ct)); +} + +function webDecrypt(ct, key, iv) { + return webCrypto.importKey('raw', key, { name: ALGO }, false, ['decrypt']) + .then(keyObj => webCrypto.decrypt({ name: ALGO, iv }, keyObj, ct)) + .then(pt => new Uint8Array(pt)); +} + +function nodeEncrypt(pt, key, iv) { + pt = new Buffer(pt); + key = new Buffer(key); + iv = new Buffer(iv); + const en = new nodeCrypto.createCipheriv('aes-' + (key.length * 8) + '-gcm', key, iv); + const ct = Buffer.concat([en.update(pt), en.final(), en.getAuthTag()]); // append auth tag to ciphertext + return Promise.resolve(new Uint8Array(ct)); +} + +function nodeDecrypt(ct, key, iv) { + ct = new Buffer(ct); + key = new Buffer(key); + iv = new Buffer(iv); + const de = new nodeCrypto.createDecipheriv('aes-' + (key.length * 8) + '-gcm', key, iv); + de.setAuthTag(ct.slice(ct.length - TAG_LEN, ct.length)); // read auth tag at end of ciphertext + const pt = Buffer.concat([de.update(ct.slice(0, ct.length - TAG_LEN)), de.final()]); + return Promise.resolve(new Uint8Array(pt)); +} \ No newline at end of file diff --git a/src/crypto/index.js b/src/crypto/index.js index af3dfeb1..e337b4cb 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -8,6 +8,7 @@ import cipher from './cipher'; import hash from './hash'; import cfb from './cfb'; +import * as gcm from './gcm'; import publicKey from './public_key'; import signature from './signature'; import random from './random'; @@ -21,6 +22,8 @@ const mod = { hash: hash, /** @see module:crypto/cfb */ cfb: cfb, + /** @see module:crypto/gcm */ + gcm: gcm, /** @see module:crypto/public_key */ publicKey: publicKey, /** @see module:crypto/signature */ diff --git a/src/crypto/public_key/rsa.js b/src/crypto/public_key/rsa.js index 0089de6e..89510f49 100644 --- a/src/crypto/public_key/rsa.js +++ b/src/crypto/public_key/rsa.js @@ -136,7 +136,7 @@ export default function RSA() { // Generate a new random private key B bits long, using public expt E function generate(B, E) { - var webCrypto = util.getWebCrypto(); + var webCrypto = util.getWebCryptoAll(); // // Native RSA keygen using Web Crypto diff --git a/src/crypto/random.js b/src/crypto/random.js index 6023382b..4e2331a4 100644 --- a/src/crypto/random.js +++ b/src/crypto/random.js @@ -91,6 +91,7 @@ export default { } else { throw new Error('No secure random number generator available.'); } + return buf; }, /** diff --git a/src/enums.js b/src/enums.js index 2b08d348..837c6649 100644 --- a/src/enums.js +++ b/src/enums.js @@ -94,7 +94,8 @@ export default { publicSubkey: 14, userAttribute: 17, symEncryptedIntegrityProtected: 18, - modificationDetectionCode: 19 + modificationDetectionCode: 19, + symEncryptedAEADProtected: 20 // see IETF draft: https://tools.ietf.org/html/draft-ford-openpgp-format-00#section-2.1 }, /** Data types in the literal packet diff --git a/src/key.js b/src/key.js index 31130995..dd261f8d 100644 --- a/src/key.js +++ b/src/key.js @@ -933,24 +933,21 @@ export function readArmored(armoredText) { */ export function generate(options) { var packetlist, secretKeyPacket, userIdPacket, dataToSign, signaturePacket, secretSubkeyPacket, subkeySignaturePacket; + return Promise.resolve().then(() => { + options.keyType = options.keyType || enums.publicKey.rsa_encrypt_sign; + if (options.keyType !== enums.publicKey.rsa_encrypt_sign) { // RSA Encrypt-Only and RSA Sign-Only are deprecated and SHOULD NOT be generated + throw new Error('Only RSA Encrypt or Sign supported'); + } - options.keyType = options.keyType || enums.publicKey.rsa_encrypt_sign; - // RSA Encrypt-Only and RSA Sign-Only are deprecated and SHOULD NOT be generated - if (options.keyType !== enums.publicKey.rsa_encrypt_sign) { - throw new Error('Only RSA Encrypt or Sign supported'); - } - // Key without passphrase is unlocked by definition - if (!options.passphrase) { - options.unlocked = true; - } - if (String.prototype.isPrototypeOf(options.userIds) || typeof options.userIds === 'string') { - options.userIds = [options.userIds]; - } + if (!options.passphrase) { // Key without passphrase is unlocked by definition + options.unlocked = true; + } + if (String.prototype.isPrototypeOf(options.userIds) || typeof options.userIds === 'string') { + options.userIds = [options.userIds]; + } - // generate - var genSecretKey = generateSecretKey(); - var genSecretSubkey = generateSecretSubkey(); - return Promise.all([genSecretKey, genSecretSubkey]).then(wrapKeyObject); + return Promise.all([generateSecretKey(), generateSecretSubkey()]).then(wrapKeyObject); + }); function generateSecretKey() { secretKeyPacket = new packet.SecretKey(); @@ -990,8 +987,8 @@ export function generate(options) { signaturePacket.keyFlags = [enums.keyFlags.certify_keys | enums.keyFlags.sign_data]; signaturePacket.preferredSymmetricAlgorithms = []; signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.aes256); - signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.aes192); signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.aes128); + signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.aes192); signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.cast5); signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.tripledes); signaturePacket.preferredHashAlgorithms = []; diff --git a/src/message.js b/src/message.js index 10115ad8..0cf35287 100644 --- a/src/message.js +++ b/src/message.js @@ -92,19 +92,29 @@ Message.prototype.getSigningKeyIds = function() { * @return {Message} new message with decrypted content */ Message.prototype.decrypt = function(privateKey, sessionKey, password) { - var keyObj = sessionKey || this.decryptSessionKey(privateKey, password); - if (!keyObj || !util.isUint8Array(keyObj.data) || !util.isString(keyObj.algorithm)) { - throw new Error('Invalid session key for decryption.'); - } - var symEncryptedPacketlist = this.packets.filterByTag(enums.packet.symmetricallyEncrypted, enums.packet.symEncryptedIntegrityProtected); - if (symEncryptedPacketlist.length !== 0) { - var symEncryptedPacket = symEncryptedPacketlist[0]; - symEncryptedPacket.decrypt(keyObj.algorithm, keyObj.data); - var resultMsg = new Message(symEncryptedPacket.packets); - // remove packets after decryption - symEncryptedPacket.packets = new packet.List(); - return resultMsg; - } + return Promise.resolve().then(() => { + const keyObj = sessionKey || this.decryptSessionKey(privateKey, password); + if (!keyObj || !util.isUint8Array(keyObj.data) || !util.isString(keyObj.algorithm)) { + throw new Error('Invalid session key for decryption.'); + } + + const symEncryptedPacketlist = this.packets.filterByTag( + enums.packet.symmetricallyEncrypted, + enums.packet.symEncryptedIntegrityProtected, + enums.packet.symEncryptedAEADProtected + ); + + if (symEncryptedPacketlist.length === 0) { + return; + } + + const symEncryptedPacket = symEncryptedPacketlist[0]; + return symEncryptedPacket.decrypt(keyObj.algorithm, keyObj.data).then(() => { + const resultMsg = new Message(symEncryptedPacket.packets); + symEncryptedPacket.packets = new packet.List(); // remove packets after decryption + return resultMsg; + }); + }); }; /** @@ -205,32 +215,35 @@ Message.prototype.getText = function() { * @return {Message} new message with encrypted content */ Message.prototype.encrypt = function(keys, passwords) { - var symAlgo; - if (keys) { - symAlgo = keyModule.getPreferredSymAlgo(keys); - } else if (passwords) { - symAlgo = config.encryption_cipher; - } else { - throw new Error('No keys or passwords'); - } + let symAlgo, msg, symEncryptedPacket; + return Promise.resolve().then(() => { + if (keys) { + symAlgo = keyModule.getPreferredSymAlgo(keys); + } else if (passwords) { + symAlgo = config.encryption_cipher; + } else { + throw new Error('No keys or passwords'); + } - var sessionKey = crypto.generateSessionKey(enums.read(enums.symmetric, symAlgo)); - var msg = encryptSessionKey(sessionKey, enums.read(enums.symmetric, symAlgo), keys, passwords); - var packetlist = msg.packets; + let sessionKey = crypto.generateSessionKey(enums.read(enums.symmetric, symAlgo)); + msg = encryptSessionKey(sessionKey, enums.read(enums.symmetric, symAlgo), keys, passwords); - var symEncryptedPacket; - if (config.integrity_protect) { - symEncryptedPacket = new packet.SymEncryptedIntegrityProtected(); - } else { - symEncryptedPacket = new packet.SymmetricallyEncrypted(); - } - symEncryptedPacket.packets = this.packets; - symEncryptedPacket.encrypt(enums.read(enums.symmetric, symAlgo), sessionKey); - packetlist.push(symEncryptedPacket); - // remove packets after encryption - symEncryptedPacket.packets = new packet.List(); + if (config.aead_protect) { + symEncryptedPacket = new packet.SymEncryptedAEADProtected(); + } else if (config.integrity_protect) { + symEncryptedPacket = new packet.SymEncryptedIntegrityProtected(); + } else { + symEncryptedPacket = new packet.SymmetricallyEncrypted(); + } + symEncryptedPacket.packets = this.packets; - return msg; + return symEncryptedPacket.encrypt(enums.read(enums.symmetric, symAlgo), sessionKey); + + }).then(() => { + msg.packets.push(symEncryptedPacket); + symEncryptedPacket.packets = new packet.List(); // remove packets after encryption + return msg; + }); }; /** diff --git a/src/openpgp.js b/src/openpgp.js index 0cf3f403..0a5cacf3 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -99,7 +99,7 @@ export function destroyWorker() { export function generateKey({ userIds=[], passphrase, numBits=2048, unlocked=false } = {}) { const options = formatUserIds({ userIds, passphrase, numBits, unlocked }); - if (!util.getWebCrypto() && asyncProxy) { // use web worker if web crypto apis are not supported + if (!util.getWebCryptoAll() && asyncProxy) { // use web worker if web crypto apis are not supported return asyncProxy.delegate('generateKey', options); } @@ -109,18 +109,7 @@ export function generateKey({ userIds=[], passphrase, numBits=2048, unlocked=fal privateKeyArmored: newKey.armor(), publicKeyArmored: newKey.toPublic().armor() - })).catch(err => { - - // js fallback already tried - 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 - if (config.debug) { console.log('Error generating keypair using native WebCrypto... falling back back to js'); } - return asyncProxy.delegate('generateKey', options); - - }).catch(onError.bind(null, 'Error generating keypair')); + })).catch(onError.bind(null, 'Error generating keypair')); } /** @@ -169,17 +158,19 @@ export function decryptKey({ privateKey, passphrase }) { export function encrypt({ data, publicKeys, privateKeys, passwords, filename, armor=true }) { checkData(data); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords); - if (asyncProxy) { // use web worker if available + if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported return asyncProxy.delegate('encrypt', { data, publicKeys, privateKeys, passwords, filename, armor }); } - return execute(() => { + return Promise.resolve().then(() => { let message = createMessage(data, filename); if (privateKeys) { // sign the message only if private keys are specified message = message.sign(privateKeys); } - message = message.encrypt(publicKeys, passwords); + return message.encrypt(publicKeys, passwords); + + }).then(message => { if(armor) { return { @@ -190,7 +181,7 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, filename, ar message: message }; - }, 'Error encrypting message'); + }).catch(onError.bind(null, 'Error encrypting message')); } /** @@ -209,20 +200,19 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, filename, ar export function decrypt({ message, privateKey, publicKeys, sessionKey, password, format='utf8' }) { checkMessage(message); publicKeys = toArray(publicKeys); - if (asyncProxy) { // use web worker if available + if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported return asyncProxy.delegate('decrypt', { message, privateKey, publicKeys, sessionKey, password, format }); } - return execute(() => { + return message.decrypt(privateKey, sessionKey, password).then(message => { - message = message.decrypt(privateKey, sessionKey, password); const result = parseMessage(message, format); if (publicKeys && result.data) { // verify only if publicKeys are specified result.signatures = message.verify(publicKeys); } return result; - }, 'Error decrypting message'); + }).catch(onError.bind(null, 'Error decrypting message')); } @@ -483,3 +473,12 @@ function onError(message, error) { // rethrow new high level error for api users throw new Error(message + ': ' + error.message); } + +/** + * Check for AES-GCM support and configuration by the user. Only browsers that + * implement the current WebCrypto specification support native AES-GCM. + * @return {Boolean} If authenticated encryption should be used + */ +function nativeAEAD() { + return util.getWebCrypto() && config.aead_protect; +} \ No newline at end of file diff --git a/src/packet/all_packets.js b/src/packet/all_packets.js index aa63a27e..077026c8 100644 --- a/src/packet/all_packets.js +++ b/src/packet/all_packets.js @@ -12,6 +12,8 @@ import * as packets from './all_packets.js'; // re-import module to parse packet export { default as Compressed } from './compressed.js'; /** @see module:packet/sym_encrypted_integrity_protected */ export { default as SymEncryptedIntegrityProtected } from './sym_encrypted_integrity_protected.js'; +/** @see module:packet/sym_encrypted_aead_protected */ +export { default as SymEncryptedAEADProtected } from './sym_encrypted_aead_protected.js'; /** @see module:packet/public_key_encrypted_session_key */ export { default as PublicKeyEncryptedSessionKey } from './public_key_encrypted_session_key.js'; /** @see module:packet/sym_encrypted_session_key */ diff --git a/src/packet/sym_encrypted_aead_protected.js b/src/packet/sym_encrypted_aead_protected.js new file mode 100644 index 00000000..41f44aec --- /dev/null +++ b/src/packet/sym_encrypted_aead_protected.js @@ -0,0 +1,88 @@ +// OpenPGP.js - An OpenPGP implementation in javascript +// Copyright (C) 2016 Tankred Hase +// +// 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 3.0 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 + +/** + * Implementation of the Symmetrically Encrypted Authenticated Encryption with Additional Data (AEAD) Protected Data Packet + * {@link https://tools.ietf.org/html/draft-ford-openpgp-format-00#section-2.1}: AEAD Protected Data Packet + */ + +'use strict'; + +import util from '../util.js'; +import crypto from '../crypto'; +import enums from '../enums.js'; + +const VERSION = 1; // A one-octet version number of the data packet. +const IV_LEN = crypto.gcm.ivLength; // currently only AES-GCM is supported + +/** + * @constructor + */ +export default function SymEncryptedAEADProtected() { + this.tag = enums.packet.symEncryptedAEADProtected; + this.version = VERSION; + this.iv = null; + this.encrypted = null; + this.packets = null; +} + +/** + * Parse an encrypted payload of bytes in the order: version, IV, ciphertext (see specification) + */ +SymEncryptedAEADProtected.prototype.read = function (bytes) { + let offset = 0; + if (bytes[offset] !== VERSION) { // The only currently defined value is 1. + throw new Error('Invalid packet version.'); + } + offset++; + this.iv = bytes.subarray(offset, IV_LEN + offset); + offset += IV_LEN; + this.encrypted = bytes.subarray(offset, bytes.length); +}; + +/** + * Write the encrypted payload of bytes in the order: version, IV, ciphertext (see specification) + * @return {Uint8Array} The encrypted payload + */ +SymEncryptedAEADProtected.prototype.write = function () { + return util.concatUint8Array([new Uint8Array([this.version]), this.iv, this.encrypted]); +}; + +/** + * Decrypt the encrypted payload. + * @param {String} sessionKeyAlgorithm The session key's cipher algorithm e.g. 'aes128' + * @param {Uint8Array} key The session key used to encrypt the payload + * @return {Promise} Nothing is returned + */ +SymEncryptedAEADProtected.prototype.decrypt = function (sessionKeyAlgorithm, key) { + return crypto.gcm.decrypt(sessionKeyAlgorithm, this.encrypted, key, this.iv).then(decrypted => { + this.packets.read(decrypted); + }); +}; + +/** + * Encrypt the packet list payload. + * @param {String} sessionKeyAlgorithm The session key's cipher algorithm e.g. 'aes128' + * @param {Uint8Array} key The session key used to encrypt the payload + * @return {Promise} Nothing is returned + */ +SymEncryptedAEADProtected.prototype.encrypt = function (sessionKeyAlgorithm, key) { + this.iv = crypto.random.getRandomValues(new Uint8Array(IV_LEN)); // generate new random IV + return crypto.gcm.encrypt(sessionKeyAlgorithm, this.packets.write(), key, this.iv).then(encrypted => { + this.encrypted = encrypted; + }); +}; \ No newline at end of file diff --git a/src/packet/sym_encrypted_integrity_protected.js b/src/packet/sym_encrypted_integrity_protected.js index f2eaa985..8347e2ae 100644 --- a/src/packet/sym_encrypted_integrity_protected.js +++ b/src/packet/sym_encrypted_integrity_protected.js @@ -95,7 +95,7 @@ SymEncryptedIntegrityProtected.prototype.encrypt = function (sessionKeyAlgorithm if(sessionKeyAlgorithm.substr(0,3) === 'aes') { // AES optimizations. Native code for node, asmCrypto for browser. var blockSize = crypto.cipher[sessionKeyAlgorithm].blockSize; - if(nodeCrypto) { // Node crypto library. Only loaded if config.useNative === true + if(nodeCrypto) { // Node crypto library. Only loaded if config.use_native === true var cipherObj = new nodeCrypto.createCipheriv('aes-' + sessionKeyAlgorithm.substr(3,3) + '-cfb', new Buffer(key), new Buffer(new Uint8Array(blockSize))); this.encrypted = new Uint8Array(cipherObj.update(new Buffer(util.concatUint8Array([prefix, tohash])))); @@ -108,6 +108,8 @@ SymEncryptedIntegrityProtected.prototype.encrypt = function (sessionKeyAlgorithm this.encrypted = crypto.cfb.encrypt(prefixrandom, sessionKeyAlgorithm, tohash, key, false) .subarray(0, prefix.length + tohash.length); } + + return Promise.resolve(); }; /** @@ -125,7 +127,7 @@ SymEncryptedIntegrityProtected.prototype.decrypt = function (sessionKeyAlgorithm if(sessionKeyAlgorithm.substr(0,3) === 'aes') { // AES optimizations. Native code for node, asmCrypto for browser. var blockSize = crypto.cipher[sessionKeyAlgorithm].blockSize; - if(nodeCrypto) { // Node crypto library. Only loaded if config.useNative === true + if(nodeCrypto) { // Node crypto library. Only loaded if config.use_native === true var decipherObj = new nodeCrypto.createDecipheriv('aes-' + sessionKeyAlgorithm.substr(3,3) + '-cfb', new Buffer(key), new Buffer(new Uint8Array(blockSize))); decrypted = new Uint8Array(decipherObj.update(new Buffer(this.encrypted))); @@ -153,4 +155,6 @@ SymEncryptedIntegrityProtected.prototype.decrypt = function (sessionKeyAlgorithm } else { this.packets.read(decrypted.subarray(0, decrypted.length - 22)); } + + return Promise.resolve(); }; diff --git a/src/packet/symmetrically_encrypted.js b/src/packet/symmetrically_encrypted.js index 2b919be3..04a0b8a1 100644 --- a/src/packet/symmetrically_encrypted.js +++ b/src/packet/symmetrically_encrypted.js @@ -73,11 +73,14 @@ SymmetricallyEncrypted.prototype.decrypt = function (sessionKeyAlgorithm, key) { throw new Error('Decryption failed due to missing MDC in combination with modern cipher.'); } this.packets.read(decrypted); + + return Promise.resolve(); }; SymmetricallyEncrypted.prototype.encrypt = function (algo, key) { var data = this.packets.write(); - this.encrypted = crypto.cfb.encrypt( - crypto.getPrefixRandom(algo), algo, data, key, true); + this.encrypted = crypto.cfb.encrypt(crypto.getPrefixRandom(algo), algo, data, key, true); + + return Promise.resolve(); }; diff --git a/src/util.js b/src/util.js index c6f4fa73..32f22445 100644 --- a/src/util.js +++ b/src/util.js @@ -61,7 +61,7 @@ export default { * @return {Array} an array of binary data to be passed */ getTransferables: function(obj) { - if (config.zeroCopy && Object.prototype.isPrototypeOf(obj)) { + if (config.zero_copy && Object.prototype.isPrototypeOf(obj)) { const transferables = []; this.collectBuffers(obj, transferables); return transferables.length ? transferables : undefined; @@ -450,12 +450,28 @@ export default { }, /** - * Get native Web Cryptography api. The default configuration is to use - * the api when available. But it can also be deactivated with config.useNative + * Get native Web Cryptography api, only the current version of the spec. + * The default configuration is to use the api when available. But it can + * be deactivated with config.use_native * @return {Object} The SubtleCrypto api or 'undefined' */ getWebCrypto: function() { - if (!config.useNative) { + if (!config.use_native) { + return; + } + + return typeof window !== 'undefined' && window.crypto && window.crypto.subtle; + }, + + /** + * Get native Web Cryptography api for all browsers, including legacy + * implementations of the spec e.g IE11 and Safari 8/9. The default + * configuration is to use the api when available. But it can be deactivated + * with config.use_native + * @return {Object} The SubtleCrypto api or 'undefined' + */ + getWebCryptoAll: function() { + if (!config.use_native) { return; } @@ -512,11 +528,11 @@ export default { /** * Get native Node.js crypto api. The default configuration is to use - * the api when available. But it can also be deactivated with config.useNative + * the api when available. But it can also be deactivated with config.use_native * @return {Object} The crypto module or 'undefined' */ getNodeCrypto: function() { - if (!this.detectNode() || !config.useNative) { + if (!this.detectNode() || !config.use_native) { return; } diff --git a/test/crypto/crypto.js b/test/crypto/crypto.js index 08afcfeb..c4dda83f 100644 --- a/test/crypto/crypto.js +++ b/test/crypto/crypto.js @@ -1,6 +1,6 @@ 'use strict'; -var openpgp = typeof window != 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); +var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); var chai = require('chai'), expect = chai.expect; @@ -80,7 +80,7 @@ describe('API functional testing', function() { 0x51,0xe0,0x22,0xf0,0xff,0xa7,0x42,0xd4,0xde,0x0b,0x47,0x8f,0x2b, 0xf5,0x4d,0x04,0x32,0x91,0x89,0x4b,0x0e,0x05,0x8d,0x70,0xf9,0xbb, 0xe7,0xd6,0x76,0xea,0x0e,0x1a,0x90,0x30,0xf5,0x98,0x01,0xc5,0x73])]; - + var DSApubMPIstrs = [ new Uint8Array([0x08,0x00,0xa8,0x85,0x5c,0x28,0x05,0x94,0x03,0xbe,0x07,0x6c,0x13,0x3e,0x65, 0xfb,0xb5,0xe1,0x99,0x7c,0xfa,0x84,0xe3,0xac,0x47,0xa5,0xc4,0x46,0xd8,0x5f, @@ -143,7 +143,7 @@ describe('API functional testing', function() { new Uint8Array([0x01,0x00,0x9b,0x58,0xa8,0xf4,0x04,0xb1,0xd5,0x14,0x09,0xe1,0xe1,0xa1,0x8a, 0x0b,0xa3,0xc3,0xa3,0x66,0xaa,0x27,0x99,0x50,0x1c,0x4d,0xba,0x24,0xee,0xdf, 0xdf,0xb8,0x8e,0x8e])]; - + var ElgamalpubMPIstrs = [ new Uint8Array([0x08,0x00,0xea,0xcc,0xbe,0xe2,0xe4,0x5a,0x51,0x18,0x93,0xa1,0x12,0x2f,0x00, 0x99,0x42,0xd8,0x5c,0x1c,0x2f,0xb6,0x3c,0xd9,0x94,0x61,0xb4,0x55,0x8d,0x4e, @@ -200,13 +200,13 @@ describe('API functional testing', function() { RSAsecMPIs[i] = new openpgp.MPI(); RSAsecMPIs[i].read(RSAsecMPIstrs[i]); } - + var DSAsecMPIs = []; for (i = 0; i < 1; i++) { DSAsecMPIs[i] = new openpgp.MPI(); DSAsecMPIs[i].read(DSAsecMPIstrs[i]); } - + var DSApubMPIs = []; for (i = 0; i < 4; i++) { DSApubMPIs[i] = new openpgp.MPI(); @@ -217,7 +217,7 @@ describe('API functional testing', function() { ElgamalsecMPIs[i] = new openpgp.MPI(); ElgamalsecMPIs[i].read(ElgamalsecMPIstrs[i]); } - + var ElgamalpubMPIs = []; for (i = 0; i < 3; i++) { ElgamalpubMPIs[i] = new openpgp.MPI(); @@ -287,6 +287,25 @@ describe('API functional testing', function() { }); } + function testAESGCM(plaintext) { + symmAlgos.forEach(function(algo) { + if(algo.substr(0,3) === 'aes') { + it(algo, function(done) { + var key = openpgp.crypto.generateSessionKey(algo); + var iv = openpgp.crypto.random.getRandomValues(new Uint8Array(openpgp.crypto.gcm.ivLength)); + + openpgp.crypto.gcm.encrypt(algo, util.str2Uint8Array(plaintext), key, iv).then(function(ciphertext) { + return openpgp.crypto.gcm.decrypt(algo, ciphertext, key, iv); + }).then(function(decrypted) { + var decryptedStr = util.Uint8Array2str(decrypted); + expect(decryptedStr).to.equal(plaintext); + done(); + }); + }); + } + }); + } + it("Symmetric with OpenPGP CFB resync", function () { testCFB("hello", true); testCFB("1234567", true); @@ -301,11 +320,37 @@ describe('API functional testing', function() { testCFB("12345678901234567890123456789012345678901234567890", false); }); - it("asmCrypto AES without OpenPGP CFB resync", function () { - testCFB("hello"); - testCFB("1234567"); - testCFB("foobarfoobar1234567890"); - testCFB("12345678901234567890123456789012345678901234567890"); + it.skip("asmCrypto AES without OpenPGP CFB resync", function () { + testAESCFB("hello"); + testAESCFB("1234567"); + testAESCFB("foobarfoobar1234567890"); + testAESCFB("12345678901234567890123456789012345678901234567890"); + }); + + describe('Symmetric AES-GCM (native)', function() { + var use_nativeVal; + beforeEach(function() { + use_nativeVal = openpgp.config.use_native; + openpgp.config.use_native = true; + }); + afterEach(function() { + openpgp.config.use_native = use_nativeVal; + }); + + testAESGCM("12345678901234567890123456789012345678901234567890"); + }); + + describe('Symmetric AES-GCM (asm.js fallback)', function() { + var use_nativeVal; + beforeEach(function() { + use_nativeVal = openpgp.config.use_native; + openpgp.config.use_native = false; + }); + afterEach(function() { + openpgp.config.use_native = use_nativeVal; + }); + + testAESGCM("12345678901234567890123456789012345678901234567890"); }); it('Asymmetric using RSA with eme_pkcs1 padding', function (done) { diff --git a/test/general/key.js b/test/general/key.js index 0283a44e..4344cfb8 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -599,13 +599,13 @@ var pgp_desktop_priv = expect(prefAlgo).to.equal(openpgp.enums.symmetric.aes256); }); - it('getPreferredSymAlgo() - two key - AES192', function() { + it('getPreferredSymAlgo() - two key - AES128', function() { var keys = openpgp.key.readArmored(twoKeys).keys; var key1 = keys[0]; var key2 = keys[1]; - key2.getPrimaryUser().selfCertificate.preferredSymmetricAlgorithms = [6,8,3]; + key2.getPrimaryUser().selfCertificate.preferredSymmetricAlgorithms = [6,7,3]; var prefAlgo = openpgp.key.getPreferredSymAlgo([key1, key2]); - expect(prefAlgo).to.equal(openpgp.enums.symmetric.aes192); + expect(prefAlgo).to.equal(openpgp.enums.symmetric.aes128); }); it('getPreferredSymAlgo() - two key - one without pref', function() { @@ -626,7 +626,7 @@ var pgp_desktop_priv = expect(key.subKeys[0].bindingSignature.keyFlags[0] & keyFlags.encrypt_communication).to.equal(keyFlags.encrypt_communication); expect(key.subKeys[0].bindingSignature.keyFlags[0] & keyFlags.encrypt_storage).to.equal(keyFlags.encrypt_storage); var sym = openpgp.enums.symmetric; - expect(key.users[0].selfCertifications[0].preferredSymmetricAlgorithms).to.eql([sym.aes256, sym.aes192, sym.aes128, sym.cast5, sym.tripledes]); + expect(key.users[0].selfCertifications[0].preferredSymmetricAlgorithms).to.eql([sym.aes256, sym.aes128, sym.aes192, sym.cast5, sym.tripledes]); var hash = openpgp.enums.hash; expect(key.users[0].selfCertifications[0].preferredHashAlgorithms).to.eql([hash.sha256, hash.sha1, hash.sha512]); var compr = openpgp.enums.compression; @@ -634,7 +634,7 @@ var pgp_desktop_priv = expect(key.users[0].selfCertifications[0].features).to.eql(openpgp.config.integrity_protect ? [1] : null); // modification detection }; var opt = {numBits: 512, userIds: 'test ', passphrase: 'hello'}; - if (openpgp.util.getWebCrypto()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys openpgp.generateKey(opt).then(function(key) { testPref(key.key); testPref(openpgp.key.readArmored(key.publicKeyArmored).keys[0]); @@ -658,11 +658,15 @@ var pgp_desktop_priv = it('Generated key is not unlocked by default', function(done) { var opt = {numBits: 512, userIds: 'test ', passphrase: '123'}; - if (openpgp.util.getWebCrypto()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys - openpgp.generateKey(opt).then(function(key) { - var msg = openpgp.message.fromText('hello').encrypt([key.key]); - msg = msg.decrypt.bind(msg, key.key); - expect(msg).to.throw('Private key is not decrypted.'); + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys + var key; + openpgp.generateKey(opt).then(function(newKey) { + key = newKey; + return openpgp.message.fromText('hello').encrypt([key.key]); + }).then(function(msg) { + return msg.decrypt(key.key); + }).catch(function(err) { + expect(err.message).to.equal('Private key is not decrypted.'); done(); }); }); @@ -670,7 +674,7 @@ var pgp_desktop_priv = it('Generate key - single userid', function(done) { var userId = 'test '; var opt = {numBits: 512, userIds: userId, passphrase: '123'}; - if (openpgp.util.getWebCrypto()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys openpgp.generateKey(opt).then(function(key) { key = key.key; expect(key.users.length).to.equal(1); @@ -683,7 +687,7 @@ var pgp_desktop_priv = var userId1 = 'test '; var userId2 = 'test '; var opt = {numBits: 512, userIds: [userId1, userId2], passphrase: '123'}; - if (openpgp.util.getWebCrypto()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys openpgp.generateKey(opt).then(function(key) { key = key.key; expect(key.users.length).to.equal(2); diff --git a/test/general/openpgp.js b/test/general/openpgp.js index f7ecd6ca..82544c8d 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -1,4 +1,4 @@ -/* globals tryWorker: true */ +/* globals tryTests: true */ 'use strict'; @@ -184,7 +184,7 @@ describe('OpenPGP.js public api tests', function() { }); describe('generateKey - unit tests', function() { - var keyGenStub, keyObjStub, getWebCryptoStub; + var keyGenStub, keyObjStub, getWebCryptoAllStub; beforeEach(function() { keyObjStub = { @@ -201,13 +201,13 @@ describe('OpenPGP.js public api tests', function() { }; keyGenStub = sinon.stub(openpgp.key, 'generate'); keyGenStub.returns(resolves(keyObjStub)); - getWebCryptoStub = sinon.stub(openpgp.util, 'getWebCrypto'); + getWebCryptoAllStub = sinon.stub(openpgp.util, 'getWebCryptoAll'); }); afterEach(function() { keyGenStub.restore(); openpgp.destroyWorker(); - getWebCryptoStub.restore(); + getWebCryptoAllStub.restore(); }); it('should fail for invalid user name', function() { @@ -333,47 +333,28 @@ describe('OpenPGP.js public api tests', function() { worker: workerStub }); var proxyGenStub = sinon.stub(openpgp.getWorker(), 'delegate'); - getWebCryptoStub.returns(); + getWebCryptoAllStub.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(), 'delegate').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; + var use_nativeVal; beforeEach(function() { - useNativeVal = openpgp.config.useNative; + use_nativeVal = openpgp.config.use_native; }); afterEach(function() { - openpgp.config.useNative = useNativeVal; + openpgp.config.use_native = use_nativeVal; openpgp.destroyWorker(); }); it('should work in JS (without worker)', function(done) { - openpgp.config.useNative = false; + openpgp.config.use_native = false; openpgp.destroyWorker(); var opt = { userIds: [{ name: 'Test User', email: 'text@example.com' }], @@ -389,7 +370,7 @@ describe('OpenPGP.js public api tests', function() { }); it('should work in JS (with worker)', function(done) { - openpgp.config.useNative = false; + openpgp.config.use_native = false; openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); var opt = { userIds: [{ name: 'Test User', email: 'text@example.com' }], @@ -405,12 +386,12 @@ describe('OpenPGP.js public api tests', function() { }); it('should work in with native crypto', function(done) { - openpgp.config.useNative = true; + openpgp.config.use_native = 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 + if (openpgp.util.getWebCryptoAll()) { 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 '); @@ -422,7 +403,7 @@ describe('OpenPGP.js public api tests', function() { }); describe('encrypt, decrypt, sign, verify - integration tests', function() { - var privateKey, publicKey, zeroCopyVal; + var privateKey, publicKey, zero_copyVal, use_nativeVal, aead_protectVal; beforeEach(function() { publicKey = openpgp.key.readArmored(pub_key); @@ -431,11 +412,15 @@ describe('OpenPGP.js public api tests', function() { privateKey = openpgp.key.readArmored(priv_key); expect(privateKey.keys).to.have.length(1); expect(privateKey.err).to.not.exist; - zeroCopyVal = openpgp.config.zeroCopy; + zero_copyVal = openpgp.config.zero_copy; + use_nativeVal = openpgp.config.use_native; + aead_protectVal = openpgp.config.aead_protect; }); afterEach(function() { - openpgp.config.zeroCopy = zeroCopyVal; + openpgp.config.zero_copy = zero_copyVal; + openpgp.config.use_native = use_nativeVal; + openpgp.config.aead_protect = aead_protectVal; }); it('Decrypting key with wrong passphrase returns false', function () { @@ -446,18 +431,43 @@ describe('OpenPGP.js public api tests', function() { expect(privateKey.keys[0].decrypt(passphrase)).to.be.true; }); - describe('without Worker', tests); + tryTests('CFB mode (asm.js)', tests, { + if: true, + beforeEach: function() { + openpgp.config.use_native = true; + openpgp.config.aead_protect = false; + } + }); - tryWorker('with Worker', tests, function() { - openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); - }, function() { - openpgp.destroyWorker(); // cleanup worker in case of failure + tryTests('CFB mode (asm.js, worker)', tests, { + if: typeof window !== 'undefined' && window.Worker, + before: function() { + openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); + }, + beforeEach: function() { + openpgp.config.use_native = true; + openpgp.config.aead_protect = false; + }, + after: function() { + openpgp.destroyWorker(); + } + }); + + tryTests('GCM mode (native)', tests, { + if: openpgp.util.getWebCrypto() || openpgp.util.getNodeCrypto(), + beforeEach: function() { + openpgp.config.use_native = true; + openpgp.config.aead_protect = true; + } }); function tests() { it('Configuration', function(done){ openpgp.config.show_version = false; openpgp.config.commentstring = 'different'; + if (openpgp.getWorker()) { // init again to trigger config event + openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); + } openpgp.encrypt({ publicKeys:publicKey.keys, data:plaintext }).then(function(encrypted) { expect(encrypted.data).to.exist; expect(encrypted.data).not.to.match(/^Version:/); @@ -875,7 +885,7 @@ describe('OpenPGP.js public api tests', function() { }); it('should encrypt and decrypt with binary data and transferable objects', function(done) { - openpgp.config.zeroCopy = true; // activate transferable objects + openpgp.config.zero_copy = true; // activate transferable objects var encOpt = { data: new Uint8Array([0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01]), passwords: password1, diff --git a/test/general/packet.js b/test/general/packet.js index 0bc59f14..475989ee 100644 --- a/test/general/packet.js +++ b/test/general/packet.js @@ -121,6 +121,29 @@ describe("Packet", function() { done(); }); + it('Sym. encrypted AEAD protected packet', function(done) { + var key = new Uint8Array([1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2]), + algo = 'aes256'; + + var literal = new openpgp.packet.Literal(), + enc = new openpgp.packet.SymEncryptedAEADProtected(), + msg = new openpgp.packet.List(); + + msg.push(enc); + literal.setText('Hello world!'); + enc.packets.push(literal); + + var msg2 = new openpgp.packet.List(); + + enc.encrypt(algo, key).then(function() { + msg2.read(msg.write()); + return msg2[0].decrypt(algo, key); + }).then(function() { + expect(msg2[0].packets[0].data).to.deep.equal(literal.data); + done(); + }); + }); + it('Sym encrypted session key with a compressed packet', function(done) { var msg = '-----BEGIN PGP MESSAGE-----\n' + @@ -150,7 +173,7 @@ describe("Packet", function() { it('Public key encrypted symmetric key packet', function(done) { var rsa = new openpgp.crypto.publicKey.rsa(); - var keySize = openpgp.util.getWebCrypto() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys + var keySize = openpgp.util.getWebCryptoAll() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys rsa.generate(keySize, "10001").then(function(mpiGen) { @@ -414,7 +437,7 @@ describe("Packet", function() { key.push(new openpgp.packet.SecretKey()); var rsa = new openpgp.crypto.publicKey.rsa(); - var keySize = openpgp.util.getWebCrypto() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys + var keySize = openpgp.util.getWebCryptoAll() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys rsa.generate(keySize, "10001").then(function(mipGen) { var mpi = [mipGen.n, mipGen.ee, mipGen.d, mipGen.p, mipGen.q, mipGen.u]; @@ -443,7 +466,7 @@ describe("Packet", function() { var key = new openpgp.packet.SecretKey(); var rsa = new openpgp.crypto.publicKey.rsa(); - var keySize = openpgp.util.getWebCrypto() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys + var keySize = openpgp.util.getWebCryptoAll() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys rsa.generate(keySize, "10001").then(function(mpiGen) { var mpi = [mpiGen.n, mpiGen.ee, mpiGen.d, mpiGen.p, mpiGen.q, mpiGen.u]; diff --git a/test/general/signature.js b/test/general/signature.js index e29c562a..7370380e 100644 --- a/test/general/signature.js +++ b/test/general/signature.js @@ -304,12 +304,13 @@ describe("Signature", function() { var msg = openpgp.message.readArmored(msg_arm1); priv_key_gnupg_ext.subKeys[0].subKey.decrypt("abcd"); - msg = msg.decrypt(priv_key_gnupg_ext); - var verified = msg.verify([pub_key]); - expect(verified).to.exist; - expect(verified).to.have.length(1); - expect(verified[0].valid).to.be.true; - done(); + msg.decrypt(priv_key_gnupg_ext).then(function(msg) { + var verified = msg.verify([pub_key]); + expect(verified).to.exist; + expect(verified).to.have.length(1); + expect(verified[0].valid).to.be.true; + done(); + }); }); it('Verify V4 signature. Hash: SHA1. PK: RSA. Signature Type: 0x00 (binary document)', function(done) { @@ -642,7 +643,7 @@ describe("Signature", function() { it('Sign message with key without password', function(done) { var opt = {numBits: 512, userIds: { name:'test', email:'a@b.com' }, passphrase: null}; - if (openpgp.util.getWebCrypto()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys openpgp.generateKey(opt).then(function(gen) { var key = gen.key; diff --git a/test/general/util.js b/test/general/util.js index 7ea3441c..d805eef1 100644 --- a/test/general/util.js +++ b/test/general/util.js @@ -145,7 +145,7 @@ describe('Util unit tests', function() { }); describe('getTransferables', function() { - var zeroCopyVal, + var zero_copyVal, buf1 = new Uint8Array(1), buf2 = new Uint8Array(1), obj = { @@ -157,16 +157,16 @@ describe('Util unit tests', function() { }; beforeEach(function() { - zeroCopyVal = openpgp.config.zeroCopy; - openpgp.config.zeroCopy = true; + zero_copyVal = openpgp.config.zero_copy; + openpgp.config.zero_copy = true; }); afterEach(function() { - openpgp.config.zeroCopy = zeroCopyVal; + openpgp.config.zero_copy = zero_copyVal; }); - it('should return undefined when zeroCopy is false', function() { - openpgp.config.zeroCopy = false; + it('should return undefined when zero_copy is false', function() { + openpgp.config.zero_copy = false; expect(openpgp.util.getTransferables(obj)).to.be.undefined; }); it('should return undefined for no input', function() { diff --git a/test/unittests.js b/test/unittests.js index 9ed7383a..6af30273 100644 --- a/test/unittests.js +++ b/test/unittests.js @@ -6,17 +6,19 @@ return new Promise(function(res, rej) { rej(val); }); }; -(typeof window !== 'undefined' ? window : global).tryWorker = function(name, tests, beforeFn, afterFn) { - if (typeof window !== 'undefined' && window.Worker) { +(typeof window !== 'undefined' ? window : global).tryTests = function(name, tests, options) { + if (options.if) { describe(name, function() { - before(beforeFn); + if (options.before) { before(options.before); } + if (options.beforeEach) { beforeEach(options.beforeEach); } tests(); - after(afterFn); + if (options.afterEach) { afterEach(options.afterEach); } + if (options.after) { after(options.after); } }); } else { - describe.skip(name + ' (No Web Worker support --> skipping tests)', tests); + describe.skip(name + ' (no support --> skipping tests)', tests); } }; diff --git a/test/worker/async_proxy.js b/test/worker/async_proxy.js index 66170871..c90bd71b 100644 --- a/test/worker/async_proxy.js +++ b/test/worker/async_proxy.js @@ -1,4 +1,4 @@ -/* globals tryWorker: true */ +/* globals tryTests: true */ 'use strict'; @@ -35,11 +35,15 @@ var pub_key = var plaintext = 'short message\nnext line\n한국어/조선말'; var pubKey; -tryWorker('Async Proxy', tests, function() { - openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); - pubKey = openpgp.key.readArmored(pub_key).keys[0]; -}, function() { - openpgp.destroyWorker(); +tryTests('Async Proxy', tests, { + if: typeof window !== 'undefined' && window.Worker, + before: function() { + openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); + pubKey = openpgp.key.readArmored(pub_key).keys[0]; + }, + after: function() { + openpgp.destroyWorker(); + } }); function tests() {