Merge pull request #430 from openpgpjs/aes_gcm
Implement AES-GCM proposal (IETF draft)
This commit is contained in:
commit
10bf9ec41e
|
@ -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'
|
||||
|
|
|
@ -19,9 +19,11 @@ OpenPGP.js [ 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
|
||||
|
|
4
npm-shrinkwrap.json
generated
4
npm-shrinkwrap.json
generated
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
|
117
src/crypto/gcm.js
Normal file
117
src/crypto/gcm.js
Normal file
|
@ -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<Uint8Array>} 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<Uint8Array>} 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));
|
||||
}
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -91,6 +91,7 @@ export default {
|
|||
} else {
|
||||
throw new Error('No secure random number generator available.');
|
||||
}
|
||||
return buf;
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
31
src/key.js
31
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 = [];
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
||||
|
|
88
src/packet/sym_encrypted_aead_protected.js
Normal file
88
src/packet/sym_encrypted_aead_protected.js
Normal file
|
@ -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<undefined>} 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<undefined>} 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;
|
||||
});
|
||||
};
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
28
src/util.js
28
src/util.js
|
@ -61,7 +61,7 @@ export default {
|
|||
* @return {Array<ArrayBuffer>} 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 <a@b.com>', 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 <a@b.com>', 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 <a@b.com>';
|
||||
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 <a@b.com>';
|
||||
var userId2 = 'test <b@c.com>';
|
||||
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);
|
||||
|
|
|
@ -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 <text@example.com>');
|
||||
|
@ -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,
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in New Issue
Block a user