diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 00000000..83879ed2 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,360 @@ +module.exports = function(grunt) { + + const version = grunt.option('release'); + const fs = require('fs'); + + // Project configuration. + const dev = !!grunt.option('dev'); + const compat = !!grunt.option('compat'); + const lightweight = !!grunt.option('lightweight'); + const plugins = compat ? [ + "transform-async-to-generator", + "syntax-async-functions", + "transform-regenerator", + "transform-runtime" + ] : []; + const presets = [[require.resolve('babel-preset-env'), { + targets: { + browsers: compat ? [ + 'IE >= 11', + 'Safari >= 9', + 'Last 2 Chrome versions', + 'Last 2 Firefox versions', + 'Last 2 Edge versions' + ] : [ + 'Last 2 Chrome versions', + 'Last 2 Firefox versions', + 'Last 2 Safari versions', + 'Last 2 Edge versions' + ] + } + }]]; + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + browserify: { + openpgp: { + files: { + 'dist/openpgp.js': ['./src/index.js'] + }, + options: { + browserifyOptions: { + fullPaths: dev, + debug: dev, + standalone: 'openpgp' + }, + cacheFile: 'browserify-cache' + (compat ? '-compat' : '') + (lightweight ? '-lightweight' : '') + '.json', + // Don't bundle these packages with openpgp.js + external: ['crypto', 'zlib', 'node-localstorage', 'node-fetch', 'asn1.js', 'stream', 'buffer'].concat( + compat ? [] : [ + 'whatwg-fetch', + 'core-js/fn/array/fill', + 'core-js/fn/array/find', + 'core-js/fn/array/includes', + 'core-js/fn/array/from', + 'core-js/fn/promise', + 'core-js/fn/typed/uint8-array', + 'core-js/fn/string/repeat', + 'core-js/fn/symbol', + 'core-js/fn/object/assign' + ], + lightweight ? [ + 'elliptic', + 'elliptic.min.js' + ] : [] + ), + transform: [ + ["babelify", { + global: true, + // Only babelify web-streams-polyfill, web-stream-tools, asmcrypto, email-addresses and seek-bzip in node_modules + only: /^(?:.*\/node_modules\/@mattiasbuelens\/web-streams-polyfill\/|.*\/node_modules\/web-stream-tools\/|.*\/node_modules\/asmcrypto\.js\/|.*\/node_modules\/email-addresses\/|.*\/node_modules\/seek-bzip\/|(?!.*\/node_modules\/)).*$/, + ignore: ['*.min.js'], + plugins, + presets + }] + ], + plugin: ['browserify-derequire'] + } + }, + worker: { + files: { + 'dist/openpgp.worker.js': ['./src/worker/worker.js'] + }, + options: { + cacheFile: 'browserify-cache-worker.json' + } + }, + unittests: { + files: { + 'test/lib/unittests-bundle.js': ['./test/unittests.js'] + }, + options: { + cacheFile: 'browserify-cache-unittests.json', + external: ['buffer', 'openpgp', '../../dist/openpgp', '../../../dist/openpgp'], + transform: [ + ["babelify", { + global: true, + // Only babelify chai-as-promised in node_modules + only: /^(?:.*\/node_modules\/chai-as-promised\/|(?!.*\/node_modules\/)).*$/, + ignore: ['*.min.js'], + plugins, + presets + }] + ] + } + } + }, + nyc: { + cover: { + options: { + include: ['dist/**'], + reporter: ['text-summary'], + reportDir: 'coverage' + }, + cmd: false, + args: ['grunt', 'mochaTest'], + sourceMap: true + }, + report: { + options: { + reporter: 'text' + } + } + }, + replace: { + openpgp: { + src: ['dist/openpgp.js'], + dest: ['dist/openpgp.js'], + replacements: [{ + from: /OpenPGP.js VERSION/g, + to: 'OpenPGP.js v<%= pkg.version %>' + }] + }, + openpgp_min: { + src: ['dist/openpgp.min.js'], + dest: ['dist/openpgp.min.js'], + replacements: [{ + from: "openpgp.worker.js", + to: "openpgp.worker.min.js" + }] + }, + worker_min: { + src: ['dist/openpgp.worker.min.js'], + dest: ['dist/openpgp.worker.min.js'], + replacements: [{ + from: "openpgp.js", + to: "openpgp.min.js" + }] + }, + lightweight_build: { + src: ['dist/openpgp.js'], + overwrite: true, + replacements: [ + { + from: "external_indutny_elliptic: false", + to: "external_indutny_elliptic: true" + } + ] + }, + indutny_global: { + src: ['dist/elliptic.min.js'], + overwrite: true, + replacements: [ + { + from: 'b.elliptic=a()', + to: 'b.openpgp.elliptic=a()' + } + ] + } + }, + terser: { + openpgp: { + files: { + 'dist/openpgp.min.js' : ['dist/openpgp.js'], + 'dist/openpgp.worker.min.js' : ['dist/openpgp.worker.js'] + }, + options: { + output: { + comments: `/^!/` + }, + sourceMap: dev ? { + content: 'inline', + url: 'inline' + } : {}, + safari10: true + } + } + }, + header: { + openpgp: { + options: { + text: '/*! OpenPGP.js v<%= pkg.version %> - ' + + '<%= grunt.template.today("yyyy-mm-dd") %> - ' + + 'this is LGPL licensed code, see LICENSE/our website <%= pkg.homepage %> for more information. */' + }, + files: { + 'dist/openpgp.js': 'dist/openpgp.js', + 'dist/openpgp.worker.js': 'dist/openpgp.worker.js' + } + } + }, + jsbeautifier: { + files: ['src/**/*.js'], + options: { + indent_size: 2, + preserve_newlines: true, + keep_array_indentation: false, + keep_function_indentation: false, + wrap_line_length: 120 + } + }, + eslint: { + target: ['src/**/*.js', './Gruntfile.js', './eslintrc.js', 'test/crypto/**/*.js'], + options: { + configFile: '.eslintrc.js', + fix: !!grunt.option('fix') + } + }, + jsdoc: { + dist: { + src: ['README.md', 'src'], + options: { + configure: '.jsdocrc.js', + destination: 'doc', + recurse: true + } + } + }, + mochaTest: { + unittests: { + options: { + reporter: 'spec', + timeout: 120000, + grep: lightweight ? 'lightweight' : undefined + }, + src: ['test/unittests.js'] + } + }, + copy: { + browsertest: { + expand: true, + flatten: true, + cwd: 'node_modules/', + src: ['mocha/mocha.css', 'mocha/mocha.js'], + dest: 'test/lib/' + }, + openpgp_compat: { + expand: true, + cwd: 'dist/', + src: ['*.js'], + dest: 'dist/compat/' + }, + openpgp_lightweight: { + expand: true, + cwd: 'dist/', + src: ['*.js'], + dest: 'dist/lightweight/' + }, + indutny_elliptic: { + expand: true, + flatten: true, + src: ['./node_modules/elliptic/dist/elliptic.min.js'], + dest: 'dist/' + } + }, + clean: { + dist: ['dist/'], + js: ['dist/*.js'] + }, + connect: { + dev: { + options: { + port: 3001, + base: '.', + keepalive: true + } + }, + test: { + options: { + port: 3000, + base: '.' + } + } + }, + watch: { + src: { + files: ['src/**/*.js'], + tasks: lightweight ? ['browserify:openpgp', 'browserify:worker', 'replace:lightweight_build'] : ['browserify:openpgp', 'browserify:worker'] + }, + test: { + files: ['test/*.js', 'test/crypto/**/*.js', 'test/general/**/*.js', 'test/worker/**/*.js'], + tasks: ['browserify:unittests'] + } + } + }); + + // Load the plugin(s) + grunt.loadNpmTasks('grunt-browserify'); + grunt.loadNpmTasks('grunt-terser'); + grunt.loadNpmTasks('grunt-header'); + grunt.loadNpmTasks('grunt-text-replace'); + grunt.loadNpmTasks('grunt-jsbeautifier'); + grunt.loadNpmTasks('grunt-jsdoc'); + grunt.loadNpmTasks('gruntify-eslint'); + grunt.loadNpmTasks('grunt-mocha-test'); + grunt.loadNpmTasks('grunt-contrib-copy'); + grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-contrib-connect'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-simple-nyc'); + + grunt.registerTask('set_version', function() { + if (!version) { + throw new Error('You must specify the version: "--release=1.0.0"'); + } + + patchFile({ + fileName: 'package.json', + version: version + }); + + patchFile({ + fileName: 'package-lock.json', + version: version + }); + + patchFile({ + fileName: 'bower.json', + version: version + }); + }); + + function patchFile(options) { + const path = './' + options.fileName; + //eslint-disable-next-line + const file = require(path); + + if (options.version) { + file.version = options.version; + } + //eslint-disable-next-line + fs.writeFileSync(path, JSON.stringify(file, null, 2) + '\n'); + } + + // Build tasks + grunt.registerTask('version', ['replace:openpgp']); + grunt.registerTask('replace_min', ['replace:openpgp_min', 'replace:worker_min']); + grunt.registerTask('build', function() { + if (lightweight) { + grunt.task.run(['copy:indutny_elliptic', 'browserify:openpgp', 'browserify:worker', 'replace:lightweight_build', 'replace:indutny_global', 'version', 'header', 'terser', 'replace_min']); + return; + } + grunt.task.run(['browserify:openpgp', 'browserify:worker', 'version', 'header', 'terser', 'replace_min']); + } + ); + grunt.registerTask('documentation', ['jsdoc']); + grunt.registerTask('default', ['build']); + // Test/Dev tasks + grunt.registerTask('test', ['eslint', 'mochaTest']); + grunt.registerTask('coverage', ['nyc']); + grunt.registerTask('browsertest', ['build', 'browserify:unittests', 'copy:browsertest', 'connect:test', 'watch']); +}; diff --git a/README.md b/README.md index 953b2aca..7dfa559e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,601 @@ -# tEnvoy +OpenPGP.js [![Build Status](https://travis-ci.org/openpgpjs/openpgpjs.svg?branch=master)](https://travis-ci.org/openpgpjs/openpgpjs) [![BrowserStack Status](https://automate.browserstack.com/badge.svg?badge_key=eEkxVVM1TytwOGJNWEdnTjk4Y0VNUUNyR3pXcEtJUGRXOVFBRjVNT1JpUT0tLTZYUlZaMWdtQWs4Z0ROS3grRXc2bFE9PQ==--4a9cac0d6ea009d81aff66de0dbb239edd1aef3c)](https://automate.browserstack.com/public-build/eEkxVVM1TytwOGJNWEdnTjk4Y0VNUUNyR3pXcEtJUGRXOVFBRjVNT1JpUT0tLTZYUlZaMWdtQWs4Z0ROS3grRXc2bFE9PQ==--4a9cac0d6ea009d81aff66de0dbb239edd1aef3c) [![Join the chat on Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/openpgpjs/openpgpjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +========== -More details coming soon... +[OpenPGP.js](https://openpgpjs.org/) is a JavaScript implementation of the OpenPGP protocol. This is defined in [RFC 4880](https://tools.ietf.org/html/rfc4880). + + +**Table of Contents** + +- [OpenPGP.js](#openpgpjs) + - [Platform Support](#platform-support) + - [Performance](#performance) + - [Getting started](#getting-started) + - [Npm](#npm) + - [Bower](#bower) + - [Examples](#examples) + - [Set up](#set-up) + - [Encrypt and decrypt *Uint8Array* data with a password](#encrypt-and-decrypt-uint8array-data-with-a-password) + - [Encrypt and decrypt *String* data with PGP keys](#encrypt-and-decrypt-string-data-with-pgp-keys) + - [Encrypt with compression](#encrypt-with-compression) + - [Streaming encrypt *Uint8Array* data with a password](#streaming-encrypt-uint8array-data-with-a-password) + - [Streaming encrypt and decrypt *String* data with PGP keys](#streaming-encrypt-and-decrypt-string-data-with-pgp-keys) + - [Generate new key pair](#generate-new-key-pair) + - [Revoke a key](#revoke-a-key) + - [Lookup public key on HKP server](#lookup-public-key-on-hkp-server) + - [Upload public key to HKP server](#upload-public-key-to-hkp-server) + - [Sign and verify cleartext messages](#sign-and-verify-cleartext-messages) + - [Create and verify *detached* signatures](#create-and-verify-detached-signatures) + - [Streaming sign and verify *Uint8Array* data](#streaming-sign-and-verify-uint8array-data) + - [Documentation](#documentation) + - [Security Audit](#security-audit) + - [Security recommendations](#security-recommendations) + - [Development](#development) + - [How do I get involved?](#how-do-i-get-involved) + - [License](#license) + - [Resources](#resources) + + + +### Platform Support + +* The `dist/openpgp.min.js` bundle works well with recent versions of Chrome, Firefox, Safari and Edge. It also works in Node.js 8+. + +* The `dist/compat/openpgp.min.js` bundle also works with Internet Explorer 11 and old versions of Safari. Please note that this bundle overwrites the global `Promise` with a polyfill version even in some cases where it already exists, which may cause issues. It also adds some built-in prototype functions if they don't exist, such as `Array.prototype.includes`. + +* If you wish, you could even load one or the other depending on which browser the user is using. However, if you're using the Web Worker, keep in mind that you also need to pass `{ path: 'compat/openpgp.worker.min.js' }` to `initWorker` whenever you load `compat/openpgp.min.js`. + +* Currently, Chrome, Safari and Edge have partial implementations of the +[Streams specification](https://streams.spec.whatwg.org/), and Firefox +has a partial implementation behind feature flags. Chrome is the only +browser that implements `TransformStream`s, which we need, so we include +a [polyfill](https://github.com/MattiasBuelens/web-streams-polyfill) for +all other browsers. Please note that in those browsers, the global +`ReadableStream` property gets overwritten with the polyfill version if +it exists. In some edge cases, you might need to use the native +`ReadableStream` (for example when using it to create a `Response` +object), in which case you should store a reference to it before loading +OpenPGP.js. There is also the +[web-streams-adapter](https://github.com/MattiasBuelens/web-streams-adapter) +library to convert back and forth between them. + +### Performance + +* Version 3.0.0 of the library introduces support for public-key cryptography using [elliptic curves](https://wiki.gnupg.org/ECC). We use native implementations on browsers and Node.js when available or [Elliptic](https://github.com/indutny/elliptic) otherwise. Elliptic curve cryptography provides stronger security per bits of key, which allows for much faster operations. Currently the following curves are supported (* = when available): + + + | Curve | Encryption | Signature | Elliptic | NodeCrypto | WebCrypto | + |:--------------- |:----------:|:---------:|:--------:|:----------:|:---------:| + | p256 | ECDH | ECDSA | Yes | Yes* | Yes* | + | p384 | ECDH | ECDSA | Yes | Yes* | Yes* | + | p521 | ECDH | ECDSA | Yes | Yes* | Yes* | + | secp256k1 | ECDH | ECDSA | Yes | Yes* | No | + | brainpoolP256r1 | ECDH | ECDSA | Yes | Yes* | No | + | brainpoolP384r1 | ECDH | ECDSA | Yes | Yes* | No | + | brainpoolP512r1 | ECDH | ECDSA | Yes | Yes* | No | + | curve25519 | ECDH | N/A | Yes | No | No | + | ed25519 | N/A | EdDSA | Yes | No | No | + +* 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](https://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`. + +* The library implements the [IETF proposal](https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-07) for authenticated encryption using native AES-EAX, OCB, or GCM. This makes symmetric encryption up to 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. **Note: activating this setting can break compatibility with other OpenPGP implementations, and also with future versions of OpenPGP.js. Don't use it with messages you want to store on disk or in a database.** You can enable it by setting `openpgp.config.aead_protect = true`. + + You can change the AEAD mode by setting one of the following options: + + ``` + openpgp.config.aead_mode = openpgp.enums.aead.eax // Default, native + openpgp.config.aead_mode = openpgp.enums.aead.ocb // Non-native + openpgp.config.aead_mode = openpgp.enums.aead.experimental_gcm // **Non-standard**, fastest + ``` + +* For environments that don't provide native crypto, the library falls back to [asm.js](https://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 + +#### Npm + + npm install --save openpgp + +#### Bower + + bower install --save openpgp + +Or just fetch a minified build under [dist](https://github.com/openpgpjs/openpgpjs/tree/master/dist). + + +### Examples + +Here are some examples of how to use the v2.x+ API. For more elaborate examples and working code, please check out the [public API unit tests](https://github.com/openpgpjs/openpgpjs/blob/master/test/general/openpgp.js). If you're upgrading from v1.x it might help to check out the [documentation](https://github.com/openpgpjs/openpgpjs#documentation). + +#### Set up + +##### Node.js + +```js +const openpgp = require('openpgp'); +``` + +##### Browser + +Copy `dist/openpgp.min.js` or `dist/compat/openpgp.min.js` (depending on the browser support you need, see [Platform Support](#platform-support)) to your project folder, and load it in a script tag: + +```html + +``` + +If you want to use the built-in Web Worker, to offload cryptographic operations off the main thread: + +```js +await openpgp.initWorker({ path: 'openpgp.worker.js' }); // set the relative web worker path +``` + +On logout, be sure to destroy the worker again, to clear private keys from memory: + +```js +await openpgp.destroyWorker(); +``` + +Alternatively, you can also implement a Web Worker in your application and load OpenPGP.js from there. This can be more performant if you store or fetch keys and messages directly inside the Worker, so that they don't have to be `postMessage`d there. + +If you want to use the lightweight build (which is smaller, and lazily loads non-default curves on demand), copy `dist/lightweight/openpgp.min.js` and `dist/lightweight/elliptic.min.js`, load the former in a script tag, and point `openpgp.config.indutny_elliptic_path` to the latter: + +```html + + +``` + +To test whether the lazy loading works, try: + +```js +await openpgp.generateKey({ curve: 'brainpoolP512r1', userIds: [{ name: 'Test', email: 'test@test.com' }] }); +``` + +For more examples of how to generate a key, see [Generate new key pair](#generate-new-key-pair). It is recommended to use `curve25519` instead of `brainpoolP512r1` by default. + +#### Encrypt and decrypt *Uint8Array* data with a password + +Encryption will use the algorithm specified in config.encryption_cipher (defaults to aes256), and decryption will use the algorithm used for encryption. + +```js +(async () => { + const { message } = await openpgp.encrypt({ + message: openpgp.message.fromBinary(new Uint8Array([0x01, 0x01, 0x01])), // input as Message object + passwords: ['secret stuff'], // multiple passwords possible + armor: false // don't ASCII armor (for Uint8Array output) + }); + const encrypted = message.packets.write(); // get raw encrypted packets as Uint8Array + + const { data: decrypted } = await openpgp.decrypt({ + message: await openpgp.message.read(encrypted), // parse encrypted bytes + passwords: ['secret stuff'], // decrypt with password + format: 'binary' // output as Uint8Array + }); + console.log(decrypted); // Uint8Array([0x01, 0x01, 0x01]) +})(); +``` + +#### Encrypt and decrypt *String* data with PGP keys + +Encryption will use the algorithm preferred by the public key (defaults to aes256 for keys generated in OpenPGP.js), and decryption will use the algorithm used for encryption. + +```js +const openpgp = require('openpgp'); // use as CommonJS, AMD, ES6 module or via window.openpgp + +(async () => { + await openpgp.initWorker({ path: 'openpgp.worker.js' }); // set the relative web worker path + + // put keys in backtick (``) to avoid errors caused by spaces or tabs + const publicKeyArmored = `-----BEGIN PGP PUBLIC KEY BLOCK----- +... +-----END PGP PUBLIC KEY BLOCK-----`; + const privateKeyArmored = `-----BEGIN PGP PRIVATE KEY BLOCK----- +... +-----END PGP PRIVATE KEY BLOCK-----`; // encrypted private key + const passphrase = `yourPassphrase`; // what the private key is encrypted with + + const { keys: [privateKey] } = await openpgp.key.readArmored(privateKeyArmored); + await privateKey.decrypt(passphrase); + + const { data: encrypted } = await openpgp.encrypt({ + message: openpgp.message.fromText('Hello, World!'), // input as Message object + publicKeys: (await openpgp.key.readArmored(publicKeyArmored)).keys, // for encryption + privateKeys: [privateKey] // for signing (optional) + }); + console.log(encrypted); // '-----BEGIN PGP MESSAGE ... END PGP MESSAGE-----' + const { data: decrypted } = await openpgp.decrypt({ + message: await openpgp.message.readArmored(encrypted), // parse armored message + publicKeys: (await openpgp.key.readArmored(publicKeyArmored)).keys, // for verification (optional) + privateKeys: [privateKey] // for decryption + }); + console.log(decrypted); // 'Hello, World!' +})(); +``` + +Encrypt with multiple public keys: + +```js +(async () => { + const publicKeysArmored = [ + `-----BEGIN PGP PUBLIC KEY BLOCK----- +... +-----END PGP PUBLIC KEY BLOCK-----`, + `-----BEGIN PGP PUBLIC KEY BLOCK----- +... +-----END PGP PUBLIC KEY BLOCK-----` + ]; + const privateKeyArmored = `-----BEGIN PGP PRIVATE KEY BLOCK----- +... +-----END PGP PRIVATE KEY BLOCK-----`; // encrypted private key + const passphrase = `yourPassphrase`; // what the private key is encrypted with + const message = 'Hello, World!'; + + const { keys: [privateKey] } = await openpgp.key.readArmored(privateKeyArmored); + await privateKey.decrypt(passphrase) + + const publicKeys = await Promise.all(publicKeysArmored.map(async (key) => { + return (await openpgp.key.readArmored(key)).keys[0]; + })); + + const { data: encrypted } = await openpgp.encrypt({ + message: openpgp.message.fromText(message), // input as Message object + publicKeys, // for encryption + privateKeys: [privateKey] // for signing (optional) + }); + console.log(encrypted); // '-----BEGIN PGP MESSAGE ... END PGP MESSAGE-----' +})(); +``` + +#### Encrypt with compression + +By default, `encrypt` will not use any compression. It's possible to override that behavior in two ways: + +Either set the `compression` parameter in the options object when calling `encrypt`. + +```js +(async () => { + const encrypted = await openpgp.encrypt({ + message: openpgp.message.fromBinary(new Uint8Array([0x01, 0x02, 0x03])), // or .fromText('string') + passwords: ['secret stuff'], // multiple passwords possible + compression: openpgp.enums.compression.zip // compress the data with zip + }); +})(); +``` + +Or, override the config to enable compression: + +```js +openpgp.config.compression = openpgp.enums.compression.zlib; +``` + +Where the value can be any of: + * `openpgp.enums.compression.zip` + * `openpgp.enums.compression.zlib` + + +#### Streaming encrypt *Uint8Array* data with a password + +```js +(async () => { + const readableStream = new ReadableStream({ + start(controller) { + controller.enqueue(new Uint8Array([0x01, 0x02, 0x03])); + controller.close(); + } + }); + + const { message } = await openpgp.encrypt({ + message: openpgp.message.fromBinary(readableStream), // input as Message object + passwords: ['secret stuff'], // multiple passwords possible + armor: false // don't ASCII armor (for Uint8Array output) + }); + const encrypted = message.packets.write(); // get raw encrypted packets as ReadableStream + + // Either pipe the above stream somewhere, pass it to another function, + // or read it manually as follows: + const reader = openpgp.stream.getReader(encrypted); + while (true) { + const { done, value } = await reader.read(); + if (done) break; + console.log('new chunk:', value); // Uint8Array + } + + // Or, in Node.js, you can pipe the above stream as follows: + const nodeStream = openpgp.stream.webToNode(encrypted); + nodeStream.pipe(nodeWritableStream); +})(); +``` + +For more information on creating ReadableStreams, see [the MDN Documentation on `new +ReadableStream()`](https://developer.mozilla.org/docs/Web/API/ReadableStream/ReadableStream). +For more information on reading streams using `openpgp.stream`, see the documentation of +[the web-stream-tools dependency](https://openpgpjs.org/web-stream-tools/), particularly +its [Reader class](https://openpgpjs.org/web-stream-tools/Reader.html). + + +#### Streaming encrypt and decrypt *String* data with PGP keys + +```js +(async () => { + const publicKeyArmored = `-----BEGIN PGP PUBLIC KEY BLOCK----- +... +-----END PGP PUBLIC KEY BLOCK-----`; // Public key + const [privateKeyArmored] = `-----BEGIN PGP PRIVATE KEY BLOCK----- +... +-----END PGP PRIVATE KEY BLOCK-----`; // Encrypted private key + const passphrase = `yourPassphrase`; // Password that private key is encrypted with + + const privateKey = (await openpgp.key.readArmored([privateKeyArmored])).keys[0]; + await privateKey.decrypt(passphrase); + + const readableStream = new ReadableStream({ + start(controller) { + controller.enqueue('Hello, world!'); + controller.close(); + } + }); + + const encrypted = await openpgp.encrypt({ + message: openpgp.message.fromText(readableStream), // input as Message object + publicKeys: (await openpgp.key.readArmored(publicKeyArmored)).keys, // for encryption + privateKeys: [privateKey] // for signing (optional) + }); + const ciphertext = encrypted.data; // ReadableStream containing '-----BEGIN PGP MESSAGE ... END PGP MESSAGE-----' + + const decrypted = await openpgp.decrypt({ + message: await openpgp.message.readArmored(ciphertext), // parse armored message + publicKeys: (await openpgp.key.readArmored(publicKeyArmored)).keys, // for verification (optional) + privateKeys: [privateKey] // for decryption + }); + const plaintext = await openpgp.stream.readToEnd(decrypted.data); // 'Hello, World!' +})(); +``` + + +#### Generate new key pair + +ECC keys: + +Possible values for `curve` are: `curve25519`, `ed25519`, `p256`, `p384`, `p521`, `secp256k1`, +`brainpoolP256r1`, `brainpoolP384r1`, or `brainpoolP512r1`. +Note that both the `curve25519` and `ed25519` options generate a primary key for signing using Ed25519 +and a subkey for encryption using Curve25519. + +```js +(async () => { + const { privateKeyArmored, publicKeyArmored, revocationCertificate } = await openpgp.generateKey({ + userIds: [{ name: 'Jon Smith', email: 'jon@example.com' }], // you can pass multiple user IDs + curve: 'ed25519', // ECC curve name + passphrase: 'super long and hard to guess secret' // protects the private key + }); + + console.log(privateKeyArmored); // '-----BEGIN PGP PRIVATE KEY BLOCK ... ' + console.log(publicKeyArmored); // '-----BEGIN PGP PUBLIC KEY BLOCK ... ' + console.log(revocationCertificate); // '-----BEGIN PGP PUBLIC KEY BLOCK ... ' +})(); +``` + +RSA keys: + +```js +(async () => { + const key = await openpgp.generateKey({ + userIds: [{ name: 'Jon Smith', email: 'jon@example.com' }], // you can pass multiple user IDs + rsaBits: 4096, // RSA key size + passphrase: 'super long and hard to guess secret' // protects the private key + }); +})(); +``` + +#### Revoke a key + +Using a revocation certificate: +```js +(async () => { + const { publicKeyArmored: revokedKeyArmored } = await openpgp.revokeKey({ + key: (await openpgp.key.readArmored(publicKeyArmored)).keys[0], + revocationCertificate + }); + console.log(revokedKeyArmored); // '-----BEGIN PGP PUBLIC KEY BLOCK ... ' +})(); +``` + +Using the private key: +```js +(async () => { + const { publicKeyArmored, publicKey } = await openpgp.revokeKey({ + key: (await openpgp.key.readArmored(privateKeyArmored)).keys[0] + }); +})(); +``` + +#### Lookup public key on HKP server + +```js +(async () => { + var hkp = new openpgp.HKP(); // Defaults to https://keyserver.ubuntu.com, or pass another keyserver URL as a string + + let publicKeyArmored = await hkp.lookup({ + query: 'alice@example.com' + }); + var { keys: [publicKey] } = await openpgp.key.readArmored(publicKeyArmored); +})(); +``` + +#### Upload public key to HKP server + +```js +(async () => { + var hkp = new openpgp.HKP('https://pgp.mit.edu'); + + var publicKeyArmored = `-----BEGIN PGP PUBLIC KEY BLOCK----- +... +-----END PGP PUBLIC KEY BLOCK-----`; + + await hkp.upload(publicKeyArmored); +})(); +``` + +#### Sign and verify cleartext messages + +```js +(async () => { + const publicKeyArmored = `-----BEGIN PGP PUBLIC KEY BLOCK----- +... +-----END PGP PUBLIC KEY BLOCK-----`; + const privateKeyArmored = `-----BEGIN PGP PRIVATE KEY BLOCK----- +... +-----END PGP PRIVATE KEY BLOCK-----`; // encrypted private key + const passphrase = `yourPassphrase`; // what the private key is encrypted with + + const { keys: [privateKey] } = await openpgp.key.readArmored(privateKeyArmored); + await privateKey.decrypt(passphrase); + + const { data: cleartext } = await openpgp.sign({ + message: openpgp.cleartext.fromText('Hello, World!'), // CleartextMessage or Message object + privateKeys: [privateKey] // for signing + }); + console.log(cleartext); // '-----BEGIN PGP SIGNED MESSAGE ... END PGP SIGNATURE-----' + + const verified = await openpgp.verify({ + message: await openpgp.cleartext.readArmored(cleartext), // parse armored message + publicKeys: (await openpgp.key.readArmored(publicKeyArmored)).keys // for verification + }); + const { valid } = verified.signatures[0]; + if (valid) { + console.log('signed by key id ' + verified.signatures[0].keyid.toHex()); + } else { + throw new Error('signature could not be verified'); + } +})(); +``` + +#### Create and verify *detached* signatures + +```js +(async () => { + const publicKeyArmored = `-----BEGIN PGP PUBLIC KEY BLOCK----- +... +-----END PGP PUBLIC KEY BLOCK-----`; + const privateKeyArmored = `-----BEGIN PGP PRIVATE KEY BLOCK----- +... +-----END PGP PRIVATE KEY BLOCK-----`; // encrypted private key + const passphrase = `yourPassphrase`; // what the private key is encrypted with + + const { keys: [privateKey] } = await openpgp.key.readArmored(privateKeyArmored); + await privateKey.decrypt(passphrase); + + const { signature: detachedSignature } = await openpgp.sign({ + message: openpgp.cleartext.fromText('Hello, World!'), // CleartextMessage or Message object + privateKeys: [privateKey], // for signing + detached: true + }); + console.log(detachedSignature); + + const verified = await openpgp.verify({ + message: openpgp.cleartext.fromText('Hello, World!'), // CleartextMessage or Message object + signature: await openpgp.signature.readArmored(detachedSignature), // parse detached signature + publicKeys: (await openpgp.key.readArmored(publicKeyArmored)).keys // for verification + }); + const { valid } = verified.signatures[0]; + if (valid) { + console.log('signed by key id ' + verified.signatures[0].keyid.toHex()); + } else { + throw new Error('signature could not be verified'); + } +})(); +``` + +#### Streaming sign and verify *Uint8Array* data + +```js +(async () => { + var readableStream = new ReadableStream({ + start(controller) { + controller.enqueue(new Uint8Array([0x01, 0x02, 0x03])); + controller.close(); + } + }); + + const publicKeyArmored = `-----BEGIN PGP PUBLIC KEY BLOCK----- +... +-----END PGP PUBLIC KEY BLOCK-----`; + const privateKeyArmored = `-----BEGIN PGP PRIVATE KEY BLOCK----- +... +-----END PGP PRIVATE KEY BLOCK-----`; // encrypted private key + const passphrase = `yourPassphrase`; // what the private key is encrypted with + + const { keys: [privateKey] } = await openpgp.key.readArmored(privateKeyArmored); + await privateKey.decrypt(passphrase); + + const { data: signatureArmored } = await openpgp.sign({ + message: openpgp.message.fromBinary(readableStream), // or .fromText(readableStream: ReadableStream) + privateKeys: [privateKey] // for signing + }); + console.log(signatureArmored); // ReadableStream containing '-----BEGIN PGP MESSAGE ... END PGP MESSAGE-----' + + const verified = await openpgp.verify({ + message: await openpgp.message.readArmored(signatureArmored), // parse armored signature + publicKeys: (await openpgp.key.readArmored(publicKeyArmored)).keys // for verification + }); + + await openpgp.stream.readToEnd(verified.data); + // Note: you *have* to read `verified.data` in some way or other, + // even if you don't need it, as that is what triggers the + // verification of the data. + + const { valid } = verified.signatures[0]; + if (valid) { + console.log('signed by key id ' + verified.signatures[0].keyid.toHex()); + } else { + throw new Error('signature could not be verified'); + } +})(); +``` + +### Documentation + +A jsdoc build of our code comments is available at [doc/index.html](https://openpgpjs.org/openpgpjs/doc/index.html). Public calls should generally be made through the OpenPGP object [doc/openpgp.html](https://openpgpjs.org/openpgpjs/doc/module-openpgp.html). + +For the documentation of `openpgp.stream`, see the documentation of [the web-stream-tools dependency](https://openpgpjs.org/web-stream-tools/). + +### Security Audit + +To date the OpenPGP.js code base has undergone two complete security audits from [Cure53](https://cure53.de). The first audit's report has been published [here](https://github.com/openpgpjs/openpgpjs/wiki/Cure53-security-audit). + +### Security recommendations + +It should be noted that js crypto apps deployed via regular web hosting (a.k.a. [**host-based security**](https://www.schneier.com/blog/archives/2012/08/cryptocat.html)) provide users with less security than installable apps with auditable static versions. Installable apps can be deployed as a [Firefox](https://developer.mozilla.org/en-US/Marketplace/Options/Packaged_apps) or [Chrome](https://developer.chrome.com/apps/about_apps.html) packaged app. These apps are basically signed zip files and their runtimes typically enforce a strict [Content Security Policy (CSP)](https://www.html5rocks.com/en/tutorials/security/content-security-policy/) to protect users against [XSS](https://en.wikipedia.org/wiki/Cross-site_scripting). This [blogpost](https://tankredhase.com/2014/04/13/heartbleed-and-javascript-crypto/) explains the trust model of the web quite well. + +It is also recommended to set a strong passphrase that protects the user's private key on disk. + +### Development + +To create your own build of the library, just run the following command after cloning the git repo. This will download all dependencies, run the tests and create a minified bundle under `dist/openpgp.min.js` to use in your project: + + npm install && npm test + +For debugging browser errors, you can open `test/unittests.html` in a browser or, after running the following command, open [`http://localhost:3000/test/unittests.html`](http://localhost:3000/test/unittests.html): + + grunt browsertest + +### How do I get involved? + +You want to help, great! It's probably best to send us a message on [Gitter](https://gitter.im/openpgpjs/openpgpjs) before you start your undertaking, to make sure nobody else is working on it, and so we can discuss the best course of action. Other than that, just go ahead and fork our repo, make your changes and send us a pull request! :) + +### License + +[GNU Lesser General Public License](https://www.gnu.org/licenses/lgpl-3.0.en.html) (3.0 or any later version). Please take a look at the [LICENSE](LICENSE) file for more information. + +### Resources + +Below is a collection of resources, many of these were projects that were in someway a precursor to the current OpenPGP.js project. If you'd like to add your link here, please do so in a pull request or email to the list. + +* [https://www.hanewin.net/encrypt/](https://www.hanewin.net/encrypt/) +* [https://github.com/seancolyer/gmail-crypt](https://github.com/seancolyer/gmail-crypt) +* [https://github.com/mete0r/jspg](https://github.com/mete0r/jspg) +* [https://github.com/GPGTools/Mobile/wiki/Introduction](https://github.com/GPGTools/Mobile/wiki/Introduction) +* [https://github.com/gmontalvoriv/mailock](https://github.com/gmontalvoriv/mailock) diff --git a/bower.json b/bower.json new file mode 100644 index 00000000..af9cf5a3 --- /dev/null +++ b/bower.json @@ -0,0 +1,37 @@ +{ + "name": "openpgp", + "version": "4.10.10", + "license": "LGPL-3.0+", + "homepage": "https://openpgpjs.org/", + "authors": [ + "OpenPGP Development Team (https://github.com/openpgpjs/openpgpjs/graphs/contributors)" + ], + "description": "OpenPGP.js is a Javascript implementation of the OpenPGP protocol. This is defined in RFC 4880.", + "main": [ + "dist/openpgp.js", + "dist/openpgp.worker.js" + ], + "moduleType": [ + "amd", + "es6", + "globals", + "node" + ], + "keywords": [ + "crypto", + "gpg", + "pgp", + "openpgp", + "encryption" + ], + "ignore": [ + "**/.*", + "dist/*.tgz", + "dist/*_debug.js", + "node_modules", + "bower_components", + "test", + "tests", + "doc" + ] +} diff --git a/package-lock.json b/package-lock.json index 98cdbc1a..c974d79a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,26 +14,34 @@ } }, "@babel/generator": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.6.tgz", - "integrity": "sha512-+htwWKJbH2bL72HRluF8zumBxzuX0ZZUFl3JLNyoUjM/Ho8wnVpPXM6aUz8cfKDqQ/h7zHqKt4xzJteUosckqQ==", + "version": "7.8.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.8.tgz", + "integrity": "sha512-HKyUVu69cZoclptr8t8U5b6sx6zoWjh8jiUhnuj3MpZuKT2dJ8zPTuiy31luq32swhI0SpwItCIlU8XW7BZeJg==", "dev": true, "requires": { - "@babel/types": "^7.9.6", + "@babel/types": "^7.8.7", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + } } }, "@babel/helper-function-name": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz", - "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.8.3", "@babel/template": "^7.8.3", - "@babel/types": "^7.9.5" + "@babel/types": "^7.8.3" } }, "@babel/helper-get-function-arity": { @@ -54,12 +62,6 @@ "@babel/types": "^7.8.3" } }, - "@babel/helper-validator-identifier": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", - "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==", - "dev": true - }, "@babel/highlight": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", @@ -109,9 +111,9 @@ } }, "@babel/parser": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz", - "integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==", + "version": "7.8.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.8.tgz", + "integrity": "sha512-mO5GWzBPsPf6865iIbzNE0AvkKF3NE+2S3eRUpE+FE07BOAkXh6G+GW/Pj01hhXjve1WScbaIO4UlY1JKeqCcA==", "dev": true }, "@babel/template": { @@ -126,17 +128,17 @@ } }, "@babel/traverse": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.6.tgz", - "integrity": "sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.6.tgz", + "integrity": "sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.9.6", - "@babel/helper-function-name": "^7.9.5", + "@babel/generator": "^7.8.6", + "@babel/helper-function-name": "^7.8.3", "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.9.6", - "@babel/types": "^7.9.6", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" @@ -151,6 +153,12 @@ "ms": "^2.1.1" } }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -160,124 +168,29 @@ } }, "@babel/types": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz", - "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==", + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.7.tgz", + "integrity": "sha512-k2TreEHxFA4CjGkL+GYjRyx35W0Mr7DP5+9q6WMkyKXB+904bYmG40syjMFV0oLlhhFCwWl0vA0DyzTDkwAiJw==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.9.5", + "esutils": "^2.0.2", "lodash": "^4.17.13", "to-fast-properties": "^2.0.0" - } - }, - "@mattiasbuelens/web-streams-adapter": { - "version": "0.1.0-alpha.5", - "resolved": "https://registry.npmjs.org/@mattiasbuelens/web-streams-adapter/-/web-streams-adapter-0.1.0-alpha.5.tgz", - "integrity": "sha512-OIfunNt/fTjIgDtUqXhBYOKtgaxm30ZWkMWegI9iS3xUHy2/A3AXki6/k+z40+BywNMi+spON/jSE0FF9WmUKA==", - "dev": true - }, - "@rollup/plugin-commonjs": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.1.0.tgz", - "integrity": "sha512-Ycr12N3ZPN96Fw2STurD21jMqzKwL9QuFhms3SD7KKRK7oaXUsBU9Zt0jL/rOPHiPYisI21/rXGO3jr9BnLHUA==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.0.8", - "commondir": "^1.0.1", - "estree-walker": "^1.0.1", - "glob": "^7.1.2", - "is-reference": "^1.1.2", - "magic-string": "^0.25.2", - "resolve": "^1.11.0" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - } - } - }, - "@rollup/plugin-node-resolve": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz", - "integrity": "sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.0.8", - "@types/resolve": "0.0.8", - "builtin-modules": "^3.1.0", - "is-module": "^1.0.0", - "resolve": "^1.14.2" }, "dependencies": { - "builtin-modules": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", - "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true - }, - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } } } }, - "@rollup/plugin-replace": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.3.2.tgz", - "integrity": "sha512-KEEL7V2tMNOsbAoNMKg91l1sNXBDoiP31GFlqXVOuV5691VQKzKBh91+OKKOG4uQWYqcFskcjFyh1d5YnZd0Zw==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.0.8", - "magic-string": "^0.25.5" - } - }, - "@rollup/pluginutils": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.0.9.tgz", - "integrity": "sha512-TLZavlfPAZYI7v33wQh4mTP6zojne14yok3DNSLcjoG/Hirxfkonn6icP5rrNWRn8nZsirJBFFpijVOJzkUHDg==", - "dev": true, - "requires": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "micromatch": "^4.0.2" - } + "@mattiasbuelens/web-streams-polyfill": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@mattiasbuelens/web-streams-polyfill/-/web-streams-polyfill-0.3.1.tgz", + "integrity": "sha512-IW+tCurEH2NL2tA3KKFAMXa90WWmTJMZksnQWMABe3bMijV7hEiOLthy4tKGTnUTV8qVf8WTCApiGmKb75Bxkg==", + "dev": true }, "@sinonjs/commons": { "version": "1.7.1", @@ -322,31 +235,53 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, - "@types/chai": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.14.tgz", - "integrity": "sha512-G+ITQPXkwTrslfG5L/BksmbLUA0M1iybEsmCWPqzSxsRRhJZimBKJkoMi8fr/CPygPTj4zO5pJH7I2/cm9M7SQ==", - "dev": true + "JSONStream": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.3.tgz", + "integrity": "sha512-3Sp6WZZ/lXl+nTDoGpGWHEpTnnC6X5fnkolYZR6nwIfzbxxvA8utPWe1gCt7i0m9uVGsSz2IS8K8mJ7HmlduMg==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } }, - "@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, - "@types/node": { - "version": "13.13.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.2.tgz", - "integrity": "sha512-LB2R1Oyhpg8gu4SON/mfforE525+Hi/M1ineICEDftqNVTyFg1aRIeGuTvXAoWHc4nbrFncWtJgMmoyRvuGh7A==", + "accepts": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", + "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", + "dev": true, + "requires": { + "mime-types": "~2.1.16", + "negotiator": "0.6.1" + } + }, + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", "dev": true }, - "@types/resolve": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", - "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", + "acorn-dynamic-import": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz", + "integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==", "dev": true, "requires": { - "@types/node": "*" + "acorn": "^5.0.0" + }, + "dependencies": { + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true + } } }, "acorn-jsx": { @@ -366,6 +301,25 @@ } } }, + "acorn-node": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.5.2.tgz", + "integrity": "sha512-krFKvw/d1F17AN3XZbybIUzEY4YEPNiGo05AfP3dBlfVKrMHETKpgjpuZkSF8qDNt9UkQcqj7am8yJLseklCMg==", + "dev": true, + "requires": { + "acorn": "^5.7.1", + "acorn-dynamic-import": "^3.0.0", + "xtend": "^4.0.1" + }, + "dependencies": { + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true + } + } + }, "ajv": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.0.tgz", @@ -426,12 +380,48 @@ "sprintf-js": "~1.0.2" } }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-filter": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", + "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, "array-from": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", "dev": true }, + "array-map": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", + "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=", + "dev": true + }, + "array-reduce": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", + "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=", + "dev": true + }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", @@ -454,8 +444,8 @@ "dev": true }, "asmcrypto.js": { - "version": "github:openpgpjs/asmcrypto#5b994303a9d3e27e0915f72a10b6c2c51535a4dc", - "from": "github:openpgpjs/asmcrypto#5b994303a9d3e27e0915f72a10b6c2c51535a4dc", + "version": "github:openpgpjs/asmcrypto#475cffa5ccb2cf2556427056679414acf3610d1b", + "from": "github:openpgpjs/asmcrypto#475cffa5ccb2cf2556427056679414acf3610d1b", "dev": true }, "asn1.js": { @@ -468,12 +458,62 @@ "minimalistic-assert": "^1.0.0" } }, + "assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", + "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "dev": true, + "requires": { + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", @@ -485,1184 +525,4010 @@ "js-tokens": "^3.0.2" } }, - "babel-eslint": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", - "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", + "babel-core": { + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.0", - "@babel/traverse": "^7.7.0", - "@babel/types": "^7.7.0", - "eslint-visitor-keys": "^1.0.0", - "resolve": "^1.12.0" + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" } }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base64-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", - "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==", - "dev": true - }, - "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" - }, - "brace-expansion": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", - "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", "dev": true, "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" } }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "babel-helper-builder-binary-assignment-operator-visitor": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", + "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", "dev": true, "requires": { - "fill-range": "^7.0.1" + "babel-helper-explode-assignable-expression": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true - }, - "browser-stdout": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", - "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", - "dev": true - }, - "buffer": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.0.8.tgz", - "integrity": "sha512-xXvjQhVNz50v2nPeoOsNqWCLGfiv4ji/gXZM28jnVwdLJxH4mFyqgqCKfaK9zf1KUbG6zTkjLOy7ou+jSMarGA==", + "babel-helper-call-delegate": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", + "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", "dev": true, "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, - "caching-transform": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.2.tgz", - "integrity": "sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==", + "babel-helper-define-map": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", + "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", "dev": true, "requires": { - "hasha": "^3.0.0", - "make-dir": "^2.0.0", - "package-hash": "^3.0.0", - "write-file-atomic": "^2.4.2" - }, - "dependencies": { - "write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - } + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" } }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "babel-helper-explode-assignable-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", + "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", "dev": true, "requires": { - "callsites": "^0.2.0" + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "catharsis": { - "version": "0.8.11", - "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.11.tgz", - "integrity": "sha512-a+xUyMV7hD1BrDQA/3iPV7oc+6W26BgVJO05PGEoatMyIuPScQKsde6i3YorWX1qs+AZjnJ18NqdKoCtKiNh1g==", + "babel-helper-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", + "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", "dev": true, "requires": { - "lodash": "^4.17.14" + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, - "chai": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", - "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", + "babel-helper-get-function-arity": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", + "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", "dev": true, "requires": { - "assertion-error": "^1.0.1", - "check-error": "^1.0.1", - "deep-eql": "^3.0.0", - "get-func-name": "^2.0.0", - "pathval": "^1.0.0", - "type-detect": "^4.0.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, - "chai-as-promised": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", - "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "babel-helper-hoist-variables": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", + "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", "dev": true, "requires": { - "check-error": "^1.0.2" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "babel-helper-optimise-call-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", + "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, - "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", - "dev": true - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true - }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "babel-helper-regex": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", + "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", "dev": true, "requires": { - "restore-cursor": "^2.0.0" + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" } }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "babel-helper-remap-async-to-generator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", + "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", "dev": true, "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true + "babel-helper-replace-supers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", + "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", + "dev": true, + "requires": { + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } }, - "color-convert": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "babel-helpers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", + "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", "dev": true, "requires": { - "color-name": "^1.1.1" + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } }, - "commander": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", - "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", + "babel-plugin-check-es2015-constants": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", + "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", "dev": true, "requires": { - "graceful-readlink": ">= 1.0.0" + "babel-runtime": "^6.22.0" } }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "babel-plugin-syntax-async-functions": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", + "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", "dev": true }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "babel-plugin-syntax-exponentiation-operator": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", + "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", "dev": true }, - "concat-stream": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", - "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", + "babel-plugin-syntax-trailing-function-commas": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", + "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", + "dev": true + }, + "babel-plugin-transform-async-to-generator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", + "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", "dev": true, "requires": { - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" + "babel-helper-remap-async-to-generator": "^6.24.1", + "babel-plugin-syntax-async-functions": "^6.8.0", + "babel-runtime": "^6.22.0" } }, - "contains-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", - "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", - "dev": true - }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "babel-plugin-transform-es2015-arrow-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", + "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", "dev": true, "requires": { - "safe-buffer": "~5.1.1" + "babel-runtime": "^6.22.0" } }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "corser": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", - "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=", - "dev": true - }, - "cp-file": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-6.2.0.tgz", - "integrity": "sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA==", + "babel-plugin-transform-es2015-block-scoped-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", + "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "make-dir": "^2.0.0", - "nested-error-stacks": "^2.0.0", - "pify": "^4.0.1", - "safe-buffer": "^5.0.1" - }, - "dependencies": { - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - } + "babel-runtime": "^6.22.0" } }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "babel-plugin-transform-es2015-block-scoping": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", + "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", "dev": true, "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "lru-cache": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", - "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - } + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" } }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "babel-plugin-transform-es2015-classes": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", + "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", "dev": true, "requires": { - "ms": "2.0.0" + "babel-helper-define-map": "^6.24.1", + "babel-helper-function-name": "^6.24.1", + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-helper-replace-supers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" } }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "babel-plugin-transform-es2015-computed-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", + "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "babel-plugin-transform-es2015-destructuring": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", + "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", "dev": true, "requires": { - "type-detect": "^4.0.0" + "babel-runtime": "^6.22.0" } }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true + "babel-plugin-transform-es2015-duplicate-keys": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", + "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } }, - "default-require-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", - "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "babel-plugin-transform-es2015-for-of": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", + "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", "dev": true, "requires": { - "strip-bom": "^3.0.0" - }, - "dependencies": { - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - } + "babel-runtime": "^6.22.0" } }, - "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "babel-plugin-transform-es2015-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", + "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", "dev": true, "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, - "diff": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", - "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", - "dev": true + "babel-plugin-transform-es2015-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", + "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "babel-plugin-transform-es2015-modules-amd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", + "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", "dev": true, "requires": { - "esutils": "^2.0.2" + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, - "ecstatic": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.3.2.tgz", - "integrity": "sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog==", + "babel-plugin-transform-es2015-modules-commonjs": { + "version": "6.26.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", + "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==", "dev": true, "requires": { - "he": "^1.1.1", - "mime": "^1.6.0", - "minimist": "^1.1.0", - "url-join": "^2.0.5" - }, - "dependencies": { - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - } + "babel-plugin-transform-strict-mode": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-types": "^6.26.0" } }, - "elliptic": { - "version": "github:openpgpjs/elliptic#ab7d8268c60b6abeb175841c578c224ac5b2d279", - "from": "github:openpgpjs/elliptic#ab7d8268c60b6abeb175841c578c224ac5b2d279", + "babel-plugin-transform-es2015-modules-systemjs": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", + "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", "dev": true, "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, - "email-addresses": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz", - "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==", - "dev": true + "babel-plugin-transform-es2015-modules-umd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", + "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", + "dev": true, + "requires": { + "babel-plugin-transform-es2015-modules-amd": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true + "babel-plugin-transform-es2015-object-super": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", + "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", + "dev": true, + "requires": { + "babel-helper-replace-supers": "^6.24.1", + "babel-runtime": "^6.22.0" + } }, - "entities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", - "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", - "dev": true + "babel-plugin-transform-es2015-parameters": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", + "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", + "dev": true, + "requires": { + "babel-helper-call-delegate": "^6.24.1", + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } }, - "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "babel-plugin-transform-es2015-shorthand-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", + "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", "dev": true, "requires": { - "is-arrayish": "^0.2.1" + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" } }, - "es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true + "babel-plugin-transform-es2015-spread": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", + "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "babel-plugin-transform-es2015-sticky-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", + "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", + "dev": true, + "requires": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-template-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", + "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-typeof-symbol": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", + "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-unicode-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", + "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", + "dev": true, + "requires": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "regexpu-core": "^2.0.0" + } + }, + "babel-plugin-transform-es3-member-expression-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es3-member-expression-literals/-/babel-plugin-transform-es3-member-expression-literals-6.22.0.tgz", + "integrity": "sha1-cz00RPPsxBvvjtGmpOCWV7iWnrs=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es3-property-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es3-property-literals/-/babel-plugin-transform-es3-property-literals-6.22.0.tgz", + "integrity": "sha1-sgeNWELiKr9A9z6M3pzTcRq9V1g=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-exponentiation-operator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", + "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", + "dev": true, + "requires": { + "babel-helper-builder-binary-assignment-operator-visitor": "^6.24.1", + "babel-plugin-syntax-exponentiation-operator": "^6.8.0", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-regenerator": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", + "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", + "dev": true, + "requires": { + "regenerator-transform": "^0.10.0" + } + }, + "babel-plugin-transform-remove-strict-mode": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-strict-mode/-/babel-plugin-transform-remove-strict-mode-0.0.2.tgz", + "integrity": "sha1-kTaFqrlUOfOg7YjliPvV6ZeJBXk=", "dev": true }, - "eslint": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", - "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", + "babel-plugin-transform-runtime": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz", + "integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=", "dev": true, "requires": { - "ajv": "^5.3.0", - "babel-code-frame": "^6.22.0", - "chalk": "^2.1.0", - "concat-stream": "^1.6.0", - "cross-spawn": "^5.1.0", - "debug": "^3.1.0", - "doctrine": "^2.1.0", - "eslint-scope": "^3.7.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^3.5.4", - "esquery": "^1.0.0", - "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", - "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.0.1", - "ignore": "^3.3.3", - "imurmurhash": "^0.1.4", - "inquirer": "^3.0.6", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.9.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-strict-mode": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", + "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-polyfill": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", + "dev": true + } + } + }, + "babel-preset-env": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.7.0.tgz", + "integrity": "sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg==", + "dev": true, + "requires": { + "babel-plugin-check-es2015-constants": "^6.22.0", + "babel-plugin-syntax-trailing-function-commas": "^6.22.0", + "babel-plugin-transform-async-to-generator": "^6.22.0", + "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoping": "^6.23.0", + "babel-plugin-transform-es2015-classes": "^6.23.0", + "babel-plugin-transform-es2015-computed-properties": "^6.22.0", + "babel-plugin-transform-es2015-destructuring": "^6.23.0", + "babel-plugin-transform-es2015-duplicate-keys": "^6.22.0", + "babel-plugin-transform-es2015-for-of": "^6.23.0", + "babel-plugin-transform-es2015-function-name": "^6.22.0", + "babel-plugin-transform-es2015-literals": "^6.22.0", + "babel-plugin-transform-es2015-modules-amd": "^6.22.0", + "babel-plugin-transform-es2015-modules-commonjs": "^6.23.0", + "babel-plugin-transform-es2015-modules-systemjs": "^6.23.0", + "babel-plugin-transform-es2015-modules-umd": "^6.23.0", + "babel-plugin-transform-es2015-object-super": "^6.22.0", + "babel-plugin-transform-es2015-parameters": "^6.23.0", + "babel-plugin-transform-es2015-shorthand-properties": "^6.22.0", + "babel-plugin-transform-es2015-spread": "^6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "^6.22.0", + "babel-plugin-transform-es2015-template-literals": "^6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "^6.23.0", + "babel-plugin-transform-es2015-unicode-regex": "^6.22.0", + "babel-plugin-transform-exponentiation-operator": "^6.22.0", + "babel-plugin-transform-regenerator": "^6.22.0", + "browserslist": "^3.2.6", + "invariant": "^2.2.2", + "semver": "^5.3.0" + } + }, + "babel-preset-es2015": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.6.0.tgz", + "integrity": "sha1-iLM+WP7JTG695Y3GXs5dFODsJWg=", + "dev": true, + "requires": { + "babel-plugin-check-es2015-constants": "^6.3.13", + "babel-plugin-transform-es2015-arrow-functions": "^6.3.13", + "babel-plugin-transform-es2015-block-scoped-functions": "^6.3.13", + "babel-plugin-transform-es2015-block-scoping": "^6.6.0", + "babel-plugin-transform-es2015-classes": "^6.6.0", + "babel-plugin-transform-es2015-computed-properties": "^6.3.13", + "babel-plugin-transform-es2015-destructuring": "^6.6.0", + "babel-plugin-transform-es2015-duplicate-keys": "^6.6.0", + "babel-plugin-transform-es2015-for-of": "^6.6.0", + "babel-plugin-transform-es2015-function-name": "^6.3.13", + "babel-plugin-transform-es2015-literals": "^6.3.13", + "babel-plugin-transform-es2015-modules-commonjs": "^6.6.0", + "babel-plugin-transform-es2015-object-super": "^6.3.13", + "babel-plugin-transform-es2015-parameters": "^6.6.0", + "babel-plugin-transform-es2015-shorthand-properties": "^6.3.13", + "babel-plugin-transform-es2015-spread": "^6.3.13", + "babel-plugin-transform-es2015-sticky-regex": "^6.3.13", + "babel-plugin-transform-es2015-template-literals": "^6.6.0", + "babel-plugin-transform-es2015-typeof-symbol": "^6.6.0", + "babel-plugin-transform-es2015-unicode-regex": "^6.3.13", + "babel-plugin-transform-regenerator": "^6.6.0" + } + }, + "babel-preset-es2015-mod": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/babel-preset-es2015-mod/-/babel-preset-es2015-mod-6.6.0.tgz", + "integrity": "sha1-4QW2LrfBABCQq4YiUpiQTPkMHo4=", + "dev": true, + "requires": { + "babel-plugin-transform-es2015-modules-commonjs": "6.7.7", + "babel-plugin-transform-regenerator": "6.6.5", + "babel-preset-es2015": "6.6.0", + "modify-babel-preset": "2.0.2" + }, + "dependencies": { + "babel-plugin-transform-es2015-modules-commonjs": { + "version": "6.7.7", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.7.7.tgz", + "integrity": "sha1-+lyiAWYXxNcSEj2M/BV4f8qoPzM=", + "dev": true, + "requires": { + "babel-plugin-transform-strict-mode": "^6.6.5", + "babel-runtime": "^5.0.0", + "babel-template": "^6.7.0", + "babel-types": "^6.7.7" + } + }, + "babel-plugin-transform-regenerator": { + "version": "6.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.6.5.tgz", + "integrity": "sha1-B5qYK9VuIjXjHuOxetVK66iY1Oc=", + "dev": true, + "requires": { + "babel-core": "^6.6.5", + "babel-plugin-syntax-async-functions": "^6.3.13", + "babel-plugin-transform-es2015-block-scoping": "^6.6.5", + "babel-plugin-transform-es2015-for-of": "^6.6.0", + "babel-runtime": "^5.0.0", + "babel-traverse": "^6.6.5", + "babel-types": "^6.6.5", + "babylon": "^6.6.5", + "private": "~0.1.5" + } + }, + "babel-runtime": { + "version": "5.8.38", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.38.tgz", + "integrity": "sha1-HAsC62MxL18If/IEUIJ7QlydTBk=", + "dev": true, + "requires": { + "core-js": "^1.0.0" + } + }, + "core-js": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=", + "dev": true + } + } + }, + "babel-preset-es3": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-es3/-/babel-preset-es3-1.0.1.tgz", + "integrity": "sha1-4I3ZUKFnDauLUKvOqpuT09mszR4=", + "dev": true, + "requires": { + "babel-plugin-transform-es3-member-expression-literals": "^6.8.0", + "babel-plugin-transform-es3-property-literals": "^6.8.0" + } + }, + "babel-register": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", + "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "dev": true, + "requires": { + "babel-core": "^6.26.0", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "home-or-tmp": "^2.0.0", "lodash": "^4.17.4", - "minimatch": "^3.0.2", "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "pluralize": "^7.0.0", - "progress": "^2.0.0", - "regexpp": "^1.0.1", - "require-uncached": "^1.0.3", - "semver": "^5.3.0", - "strip-ansi": "^4.0.0", - "strip-json-comments": "~2.0.1", - "table": "4.0.2", - "text-table": "~0.2.0" + "source-map-support": "^0.4.15" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babelify": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/babelify/-/babelify-8.0.0.tgz", + "integrity": "sha512-xVr63fKEvMWUrrIbqlHYsMcc5Zdw4FSVesAHgkgajyCE1W8gbm9rbMakqavhxKvikGYMhEcqxTwB/gQmQ6lBtw==", + "dev": true + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" }, "dependencies": { - "acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true + } + } + }, + "base64-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", + "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==", + "dev": true + }, + "basic-auth": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.0.tgz", + "integrity": "sha1-AV2z81PgLlY3d1X5YnQuiYHnu7o=", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "binary-extensions": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", + "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", + "dev": true + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", + "dev": true + }, + "bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + }, + "body": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", + "integrity": "sha1-5LoM5BCkaTYyM2dgnstOZVMSUGk=", + "dev": true, + "requires": { + "continuable-cache": "^0.3.1", + "error": "^7.0.0", + "raw-body": "~1.1.0", + "safe-json-parse": "~1.0.1" + } + }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browser-pack": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz", + "integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==", + "dev": true, + "requires": { + "JSONStream": "^1.0.3", + "combine-source-map": "~0.8.0", + "defined": "^1.0.0", + "safe-buffer": "^5.1.1", + "through2": "^2.0.0", + "umd": "^3.0.0" + }, + "dependencies": { + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + } + } + } + }, + "browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "dev": true, + "requires": { + "resolve": "1.1.7" + } + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "browserify": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/browserify/-/browserify-16.2.2.tgz", + "integrity": "sha512-fMES05wq1Oukts6ksGUU2TMVHHp06LyQt0SIwbXIHm7waSrQmNBZePsU0iM/4f94zbvb/wHma+D1YrdzWYnF/A==", + "dev": true, + "requires": { + "JSONStream": "^1.0.3", + "assert": "^1.4.0", + "browser-pack": "^6.0.1", + "browser-resolve": "^1.11.0", + "browserify-zlib": "~0.2.0", + "buffer": "^5.0.2", + "cached-path-relative": "^1.0.0", + "concat-stream": "^1.6.0", + "console-browserify": "^1.1.0", + "constants-browserify": "~1.0.0", + "crypto-browserify": "^3.0.0", + "defined": "^1.0.0", + "deps-sort": "^2.0.0", + "domain-browser": "^1.2.0", + "duplexer2": "~0.1.2", + "events": "^2.0.0", + "glob": "^7.1.0", + "has": "^1.0.0", + "htmlescape": "^1.1.0", + "https-browserify": "^1.0.0", + "inherits": "~2.0.1", + "insert-module-globals": "^7.0.0", + "labeled-stream-splicer": "^2.0.0", + "mkdirp": "^0.5.0", + "module-deps": "^6.0.0", + "os-browserify": "~0.3.0", + "parents": "^1.0.1", + "path-browserify": "~0.0.0", + "process": "~0.11.0", + "punycode": "^1.3.2", + "querystring-es3": "~0.2.0", + "read-only-stream": "^2.0.0", + "readable-stream": "^2.0.2", + "resolve": "^1.1.4", + "shasum": "^1.0.0", + "shell-quote": "^1.6.1", + "stream-browserify": "^2.0.0", + "stream-http": "^2.0.0", + "string_decoder": "^1.1.1", + "subarg": "^1.0.0", + "syntax-error": "^1.1.1", + "through2": "^2.0.0", + "timers-browserify": "^1.0.1", + "tty-browserify": "0.0.1", + "url": "~0.11.0", + "util": "~0.10.1", + "vm-browserify": "^1.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + } + } + } + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cache-api": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/browserify-cache-api/-/browserify-cache-api-3.0.1.tgz", + "integrity": "sha1-liR+hT8Gj9bg1FzHPwuyzZd47wI=", + "dev": true, + "requires": { + "async": "^1.5.2", + "through2": "^2.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + } + } + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-derequire": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/browserify-derequire/-/browserify-derequire-0.9.4.tgz", + "integrity": "sha1-ZNYeVs/f8LjxdP2MV/i0Az4oeJU=", + "dev": true, + "requires": { + "derequire": "^2.0.0", + "through2": "^1.1.1" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "browserify-incremental": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/browserify-incremental/-/browserify-incremental-3.1.1.tgz", + "integrity": "sha1-BxPLdYckemMqnwjPG9FpuHi2Koo=", + "dev": true, + "requires": { + "JSONStream": "^0.10.0", + "browserify-cache-api": "^3.0.0", + "through2": "^2.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "JSONStream": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.10.0.tgz", + "integrity": "sha1-dDSdDYlSK3HzDwoD/5vSDKbxKsA=", + "dev": true, + "requires": { + "jsonparse": "0.0.5", + "through": ">=2.2.7 <3" + } + }, + "jsonparse": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz", + "integrity": "sha1-MwVCrT8KZUZlt3jz6y2an6UHrGQ=", + "dev": true + }, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + } + } + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true, + "requires": { + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" + }, + "dependencies": { + "elliptic": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", + "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", + "dev": true, + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + } + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-3.2.8.tgz", + "integrity": "sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000844", + "electron-to-chromium": "^1.3.47" + } + }, + "buffer": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.0.8.tgz", + "integrity": "sha512-xXvjQhVNz50v2nPeoOsNqWCLGfiv4ji/gXZM28jnVwdLJxH4mFyqgqCKfaK9zf1KUbG6zTkjLOy7ou+jSMarGA==", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "buffer-from": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz", + "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", + "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=", + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "cached-path-relative": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.2.tgz", + "integrity": "sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg==", + "dev": true + }, + "caching-transform": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.2.tgz", + "integrity": "sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==", + "dev": true, + "requires": { + "hasha": "^3.0.0", + "make-dir": "^2.0.0", + "package-hash": "^3.0.0", + "write-file-atomic": "^2.4.2" + }, + "dependencies": { + "write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + } + } + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "^0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + } + } + }, + "caniuse-lite": { + "version": "1.0.30000865", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000865.tgz", + "integrity": "sha512-vs79o1mOSKRGv/1pSkp4EXgl4ZviWeYReXw60XfacPU64uQWZwJT6vZNmxRF9O+6zu71sJwMxLK5JXxbzuVrLw==", + "dev": true + }, + "catharsis": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.9.tgz", + "integrity": "sha1-mMyJDKZS3S7w5ws3klMQ/56Q/Is=", + "dev": true, + "requires": { + "underscore-contrib": "~0.3.0" + } + }, + "chai": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", + "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", + "dev": true, + "requires": { + "assertion-error": "^1.0.1", + "check-error": "^1.0.1", + "deep-eql": "^3.0.0", + "get-func-name": "^2.0.0", + "pathval": "^1.0.0", + "type-detect": "^4.0.0" + } + }, + "chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "dev": true, + "requires": { + "check-error": "^1.0.2" + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "coffeescript": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.10.0.tgz", + "integrity": "sha1-56qDAZF+9iGzXYo580jc3R234z4=", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "dev": true, + "requires": { + "color-name": "^1.1.1" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "dev": true + }, + "combine-source-map": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", + "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=", + "dev": true, + "requires": { + "convert-source-map": "~1.1.0", + "inline-source-map": "~0.6.0", + "lodash.memoize": "~3.0.3", + "source-map": "~0.5.3" + }, + "dependencies": { + "convert-source-map": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", + "dev": true + } + } + }, + "commander": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.12.2.tgz", + "integrity": "sha512-BFnaq5ZOGcDN7FlrtBT4xxkgIToalIIxwjxLWVJ8bGTpe1LroqMiqQXdA7ygc7CRvaYS+9zfPGFnJqFSayx+AA==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", + "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "config-chain": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.11.tgz", + "integrity": "sha1-q6CXR9++TD5w52am5BWG4YWfxvI=", + "dev": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "connect": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.5.tgz", + "integrity": "sha1-+43ee6B2OHfQ7J352sC0tA5yx9o=", + "dev": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.0.6", + "parseurl": "~1.3.2", + "utils-merge": "1.0.1" + } + }, + "connect-livereload": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/connect-livereload/-/connect-livereload-0.5.4.tgz", + "integrity": "sha1-gBV9E3HJ83zBQDmrGJWXDRGdw7w=", + "dev": true + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true, + "requires": { + "date-now": "^0.1.4" + } + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, + "continuable-cache": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", + "integrity": "sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=", + "dev": true + }, + "convert-source-map": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", + "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", + "dev": true + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-js": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", + "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cp-file": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-6.2.0.tgz", + "integrity": "sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "make-dir": "^2.0.0", + "nested-error-stacks": "^2.0.0", + "pify": "^4.0.1", + "safe-buffer": "^5.0.1" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + } + } + }, + "create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + }, + "dependencies": { + "elliptic": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", + "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", + "dev": true, + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + } + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "lru-cache": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", + "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + } + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "dev": true, + "requires": { + "es5-ext": "^0.10.9" + } + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "dateformat": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", + "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1", + "meow": "^3.3.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "default-require-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "dev": true, + "requires": { + "strip-bom": "^3.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + } + }, + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", + "dev": true + }, + "deps-sort": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.0.tgz", + "integrity": "sha1-CRckkC6EZYJg65EHSMzNGvbiH7U=", + "dev": true, + "requires": { + "JSONStream": "^1.0.3", + "shasum": "^1.0.0", + "subarg": "^1.0.0", + "through2": "^2.0.0" + }, + "dependencies": { + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + } + } + } + }, + "derequire": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/derequire/-/derequire-2.0.6.tgz", + "integrity": "sha1-MaQUu3yhdiOfp4sRZjbvd9UX52g=", + "dev": true, + "requires": { + "acorn": "^4.0.3", + "concat-stream": "^1.4.6", + "escope": "^3.6.0", + "through2": "^2.0.0", + "yargs": "^6.5.0" + }, + "dependencies": { + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + } + } + } + }, + "des.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", + "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true + }, + "detective": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.1.0.tgz", + "integrity": "sha512-TFHMqfOvxlgrfVzTEkNBSh9SvSNX/HfF4OFI2QFGCyPm02EsyILqnUeb5P6q7JZ3SFNTBL5t2sePRgrN4epUWQ==", + "dev": true, + "requires": { + "acorn-node": "^1.3.0", + "defined": "^1.0.0", + "minimist": "^1.1.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + } + } + }, + "diff": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", + "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", + "dev": true + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true + }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + } + }, + "editorconfig": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.13.3.tgz", + "integrity": "sha512-WkjsUNVCu+ITKDj73QDvi0trvpdDWdkDyHybDGSXPfekLCqwmpD7CP7iPbvBgosNuLcI96XTDwNa75JyFl7tEQ==", + "dev": true, + "requires": { + "bluebird": "^3.0.5", + "commander": "^2.9.0", + "lru-cache": "^3.2.0", + "semver": "^5.1.0", + "sigmund": "^1.0.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.52", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.52.tgz", + "integrity": "sha1-0tnxJwuko7lnuDHEDvcftNmrXOA=", + "dev": true + }, + "elliptic": { + "version": "github:openpgpjs/elliptic#ab7d8268c60b6abeb175841c578c224ac5b2d279", + "from": "github:openpgpjs/elliptic#ab7d8268c60b6abeb175841c578c224ac5b2d279", + "dev": true, + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "email-addresses": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz", + "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "encodeurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", + "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=", + "dev": true + }, + "error": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/error/-/error-7.0.2.tgz", + "integrity": "sha1-pfdf/02ZJhJt2sDqXcOOaJFTywI=", + "dev": true, + "requires": { + "string-template": "~0.2.1", + "xtend": "~4.0.0" + } + }, + "error-ex": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", + "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es5-ext": { + "version": "0.10.37", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.37.tgz", + "integrity": "sha1-DudB0Ui4AGm6J9AgOTdWryV978M=", + "dev": true, + "requires": { + "es6-iterator": "~2.0.1", + "es6-symbol": "~3.1.1" + } + }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-set": "~0.1.5", + "es6-symbol": "~3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-symbol": "3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "es6-weak-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", + "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.14", + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, + "requires": { + "es6-map": "^0.1.3", + "es6-weak-map": "^2.0.1", + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", + "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", + "dev": true, + "requires": { + "ajv": "^5.3.0", + "babel-code-frame": "^6.22.0", + "chalk": "^2.1.0", + "concat-stream": "^1.6.0", + "cross-spawn": "^5.1.0", + "debug": "^3.1.0", + "doctrine": "^2.1.0", + "eslint-scope": "^3.7.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^3.5.4", + "esquery": "^1.0.0", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.0.1", + "ignore": "^3.3.3", + "imurmurhash": "^0.1.4", + "inquirer": "^3.0.6", + "is-resolvable": "^1.0.0", + "js-yaml": "^3.9.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.4", + "minimatch": "^3.0.2", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "regexpp": "^1.0.1", + "require-uncached": "^1.0.3", + "semver": "^5.3.0", + "strip-ansi": "^4.0.0", + "strip-json-comments": "~2.0.1", + "table": "4.0.2", + "text-table": "~0.2.0" + }, + "dependencies": { + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "dev": true, + "requires": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", + "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "eslint-config-airbnb": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-16.1.0.tgz", + "integrity": "sha512-zLyOhVWhzB/jwbz7IPSbkUuj7X2ox4PHXTcZkEmDqTvd0baJmJyuxlFPDlZOE/Y5bC+HQRaEkT3FoHo9wIdRiw==", + "dev": true, + "requires": { + "eslint-config-airbnb-base": "^12.1.0" + } + }, + "eslint-config-airbnb-base": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-12.1.0.tgz", + "integrity": "sha512-/vjm0Px5ZCpmJqnjIzcFb9TKZrKWz0gnuG/7Gfkt0Db1ELJR51xkZth+t14rYdqWgX836XbuxtArbIHlVhbLBA==", + "dev": true, + "requires": { + "eslint-restricted-globals": "^0.1.1" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", + "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "resolve": "^1.5.0" + }, + "dependencies": { + "resolve": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", + "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", + "dev": true, + "requires": { + "path-parse": "^1.0.5" + } + } + } + }, + "eslint-module-utils": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz", + "integrity": "sha512-jDI/X5l/6D1rRD/3T43q8Qgbls2nq5km5KSqiwlyUbGo5+04fXhMKdCPhjwbqAa6HXWaMxj8Q4hQDIh7IadJQw==", + "dev": true, + "requires": { + "debug": "^2.6.8", + "pkg-dir": "^1.0.0" + } + }, + "eslint-plugin-chai-friendly": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-0.5.0.tgz", + "integrity": "sha512-Pxe6z8C9fP0pn2X2nGFU/b3GBOCM/5FVus1hsMwJsXP3R7RiXFl7g0ksJbsc0GxiLyidTW4mEFk77qsNn7Tk7g==", + "dev": true + }, + "eslint-plugin-import": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.8.0.tgz", + "integrity": "sha512-Rf7dfKJxZ16QuTgVv1OYNxkZcsu/hULFnC+e+w0Gzi6jMC3guQoWQgxYxc54IDRinlb6/0v5z/PxxIKmVctN+g==", + "dev": true, + "requires": { + "builtin-modules": "^1.1.1", + "contains-path": "^0.1.0", + "debug": "^2.6.8", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.1", + "eslint-module-utils": "^2.1.1", + "has": "^1.0.1", + "lodash.cond": "^4.3.0", + "minimatch": "^3.0.3", + "read-pkg-up": "^2.0.0" + }, + "dependencies": { + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, + "eslint-restricted-globals": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz", + "integrity": "sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=", + "dev": true + }, + "eslint-scope": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", + "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", + "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", + "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", + "dev": true, + "requires": { + "estraverse": "^4.1.0", + "object-assign": "^4.0.1" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "eventemitter2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", + "dev": true + }, + "events": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/events/-/events-2.1.0.tgz", + "integrity": "sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg==", + "dev": true + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "external-editor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.1.0.tgz", + "integrity": "sha512-E44iT5QVOUJBKij4IIV3uvxuNlbKS38Tw1HiupxEIHPv9qtC2PrDYohbXV5U+1jnfIXttny8gUhj+oZvflFlzA==", + "dev": true, + "requires": { + "chardet": "^0.4.0", + "iconv-lite": "^0.4.17", + "tmp": "^0.0.33" + } + }, + "fast-deep-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", + "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, + "file-sync-cmp": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz", + "integrity": "sha1-peeo/7+kk7Q7kju9TKiaU7Y7YSs=", + "dev": true + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, + "finalhandler": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.6.tgz", + "integrity": "sha1-AHrqM9Gk0+QgF/YkhIrVjSEvgU8=", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.3.1", + "unpipe": "~1.0.0" + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + } + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "findup-sync": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz", + "integrity": "sha1-N5MKpdgWt3fANEXhlmzGeQpMCxY=", + "dev": true, + "requires": { + "glob": "~5.0.0" + }, + "dependencies": { + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "flat-cache": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", + "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "dev": true, + "requires": { + "circular-json": "^0.3.1", + "del": "^2.0.2", + "graceful-fs": "^4.1.2", + "write": "^0.2.1" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "foreground-child": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", + "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", + "dev": true, + "requires": { + "cross-spawn": "^4", + "signal-exit": "^3.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + } + } + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.12.tgz", + "integrity": "sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q==", + "dev": true, + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1", + "node-pre-gyp": "*" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "3.2.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.6.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "bundled": true, + "dev": true, + "optional": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true + }, + "minipass": { + "version": "2.9.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.3.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.9.0" + } + }, + "mkdirp": { + "version": "0.5.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.3.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.14.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4.4.2" + } + }, + "nopt": { + "version": "4.0.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "readable-stream": { + "version": "2.3.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.7.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true + "semver": { + "version": "5.7.1", + "bundled": true, + "dev": true, + "optional": true }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "set-blocking": { + "version": "2.0.0", + "bundled": true, "dev": true, - "requires": { - "color-convert": "^1.9.0" - } + "optional": true }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "signal-exit": { + "version": "3.0.2", + "bundled": true, "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } + "optional": true }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "string-width": { + "version": "1.0.2", + "bundled": true, "dev": true, + "optional": true, "requires": { - "ms": "2.0.0" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, - "espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "string_decoder": { + "version": "1.1.1", + "bundled": true, "dev": true, + "optional": true, "requires": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" + "safe-buffer": "~5.1.0" } }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "strip-ansi": { + "version": "3.0.1", + "bundled": true, "dev": true, + "optional": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "ansi-regex": "^2.0.0" } }, - "globals": { - "version": "11.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", - "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "tar": { + "version": "4.4.13", + "bundled": true, "dev": true, + "optional": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" } }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "util-deprecate": { + "version": "1.0.2", + "bundled": true, "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } + "optional": true }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "wide-align": { + "version": "1.1.3", + "bundled": true, "dev": true, + "optional": true, "requires": { - "has-flag": "^3.0.0" + "string-width": "^1.0.2 || 2" } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "yallist": { + "version": "3.1.1", + "bundled": true, + "dev": true, + "optional": true } } }, - "eslint-config-airbnb": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-16.1.0.tgz", - "integrity": "sha512-zLyOhVWhzB/jwbz7IPSbkUuj7X2ox4PHXTcZkEmDqTvd0baJmJyuxlFPDlZOE/Y5bC+HQRaEkT3FoHo9wIdRiw==", + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", "dev": true, "requires": { - "eslint-config-airbnb-base": "^12.1.0" + "globule": "^1.0.0" } }, - "eslint-config-airbnb-base": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-12.1.0.tgz", - "integrity": "sha512-/vjm0Px5ZCpmJqnjIzcFb9TKZrKWz0gnuG/7Gfkt0Db1ELJR51xkZth+t14rYdqWgX836XbuxtArbIHlVhbLBA==", + "get-assigned-identifiers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", + "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==", + "dev": true + }, + "get-caller-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", + "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getobject": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", + "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=", + "dev": true + }, + "glob": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", + "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", "dev": true, "requires": { - "eslint-restricted-globals": "^0.1.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, - "eslint-import-resolver-node": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", - "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", "dev": true, "requires": { - "debug": "^2.6.9", - "resolve": "^1.5.0" + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "globule": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", + "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", + "dev": true, + "requires": { + "glob": "~7.1.1", + "lodash": "~4.17.10", + "minimatch": "~3.0.2" }, "dependencies": { - "resolve": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", - "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { - "path-parse": "^1.0.5" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } } } }, - "eslint-module-utils": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz", - "integrity": "sha512-jDI/X5l/6D1rRD/3T43q8Qgbls2nq5km5KSqiwlyUbGo5+04fXhMKdCPhjwbqAa6HXWaMxj8Q4hQDIh7IadJQw==", - "dev": true, - "requires": { - "debug": "^2.6.8", - "pkg-dir": "^1.0.0" - } + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, - "eslint-plugin-chai-friendly": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-0.5.0.tgz", - "integrity": "sha512-Pxe6z8C9fP0pn2X2nGFU/b3GBOCM/5FVus1hsMwJsXP3R7RiXFl7g0ksJbsc0GxiLyidTW4mEFk77qsNn7Tk7g==", + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", "dev": true }, - "eslint-plugin-import": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.8.0.tgz", - "integrity": "sha512-Rf7dfKJxZ16QuTgVv1OYNxkZcsu/hULFnC+e+w0Gzi6jMC3guQoWQgxYxc54IDRinlb6/0v5z/PxxIKmVctN+g==", - "dev": true, - "requires": { - "builtin-modules": "^1.1.1", - "contains-path": "^0.1.0", - "debug": "^2.6.8", - "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.1", - "eslint-module-utils": "^2.1.1", - "has": "^1.0.1", - "lodash.cond": "^4.3.0", - "minimatch": "^3.0.3", - "read-pkg-up": "^2.0.0" + "growl": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", + "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", + "dev": true + }, + "grunt": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.1.0.tgz", + "integrity": "sha512-+NGod0grmviZ7Nzdi9am7vuRS/h76PcWDsV635mEXF0PEQMUV6Kb+OjTdsVxbi0PZmfQOjCMKb3w8CVZcqsn1g==", + "dev": true, + "requires": { + "coffeescript": "~1.10.0", + "dateformat": "~1.0.12", + "eventemitter2": "~0.4.13", + "exit": "~0.1.1", + "findup-sync": "~0.3.0", + "glob": "~7.0.0", + "grunt-cli": "~1.2.0", + "grunt-known-options": "~1.1.0", + "grunt-legacy-log": "~2.0.0", + "grunt-legacy-util": "~1.1.1", + "iconv-lite": "~0.4.13", + "js-yaml": "~3.13.1", + "minimatch": "~3.0.2", + "mkdirp": "~1.0.3", + "nopt": "~3.0.6", + "path-is-absolute": "~1.0.0", + "rimraf": "~2.6.2" }, "dependencies": { - "doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "grunt-cli": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz", + "integrity": "sha1-VisRnrsGndtGSs4oRVAb6Xs1tqg=", "dev": true, "requires": { - "locate-path": "^2.0.0" + "findup-sync": "~0.3.0", + "grunt-known-options": "~1.1.0", + "nopt": "~3.0.6", + "resolve": "~1.1.0" } }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - } + "mkdirp": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.3.tgz", + "integrity": "sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==", + "dev": true }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "requires": { - "pify": "^2.0.0" + "glob": "^7.1.3" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } } - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + } + } + }, + "grunt-browserify": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/grunt-browserify/-/grunt-browserify-5.3.0.tgz", + "integrity": "sha1-R/2M+LrFj+LeaDr9xX9/OoDKeS0=", + "dev": true, + "requires": { + "async": "^2.5.0", + "browserify": "^16.0.0", + "browserify-incremental": "^3.1.1", + "glob": "^7.1.2", + "lodash": "^4.17.4", + "resolve": "^1.1.6", + "watchify": "^3.6.1" + }, + "dependencies": { + "async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", "dev": true, "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" + "lodash": "^4.17.10" } }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true } } }, - "eslint-restricted-globals": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz", - "integrity": "sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=", - "dev": true - }, - "eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "grunt-contrib-clean": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-1.1.0.tgz", + "integrity": "sha1-Vkq/LQN4qYOhW54/MO51tzjEBjg=", "dev": true, "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" + "async": "^1.5.2", + "rimraf": "^2.5.1" + }, + "dependencies": { + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "^7.0.5" + } + } } }, - "eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", - "dev": true - }, - "esm": { - "version": "3.2.25", - "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", - "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", - "dev": true - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", - "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", + "grunt-contrib-connect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/grunt-contrib-connect/-/grunt-contrib-connect-1.0.2.tgz", + "integrity": "sha1-XPkzuRpnOGBEJzwLJERgPNmIebo=", "dev": true, "requires": { - "estraverse": "^4.0.0" + "async": "^1.5.2", + "connect": "^3.4.0", + "connect-livereload": "^0.5.0", + "http2": "^3.3.4", + "morgan": "^1.6.1", + "opn": "^4.0.0", + "portscanner": "^1.0.0", + "serve-index": "^1.7.1", + "serve-static": "^1.10.0" } }, - "esrecurse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", - "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", + "grunt-contrib-copy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-1.0.0.tgz", + "integrity": "sha1-cGDGWB6QS4qw0A8HbgqPbj58NXM=", "dev": true, "requires": { - "estraverse": "^4.1.0", - "object-assign": "^4.0.1" + "chalk": "^1.1.1", + "file-sync-cmp": "^0.1.0" } }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - }, - "estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true - }, - "eventemitter3": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz", - "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==", - "dev": true - }, - "external-editor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.1.0.tgz", - "integrity": "sha512-E44iT5QVOUJBKij4IIV3uvxuNlbKS38Tw1HiupxEIHPv9qtC2PrDYohbXV5U+1jnfIXttny8gUhj+oZvflFlzA==", + "grunt-contrib-watch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz", + "integrity": "sha512-yGweN+0DW5yM+oo58fRu/XIRrPcn3r4tQx+nL7eMRwjpvk+rQY6R8o94BPK0i2UhTg9FN21hS+m8vR8v9vXfeg==", "dev": true, "requires": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", - "tmp": "^0.0.33" + "async": "^2.6.0", + "gaze": "^1.1.0", + "lodash": "^4.17.10", + "tiny-lr": "^1.1.1" + }, + "dependencies": { + "async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "dev": true, + "requires": { + "lodash": "^4.17.10" + } + } } }, - "fast-deep-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", - "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "grunt-header": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grunt-header/-/grunt-header-1.1.0.tgz", + "integrity": "sha1-E5OD3hANmJOc4WynnGc2luN1vss=", "dev": true, "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" + "detect-newline": "^2.1.0" } }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "grunt-jsbeautifier": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/grunt-jsbeautifier/-/grunt-jsbeautifier-0.2.13.tgz", + "integrity": "sha1-89QXOPy1+ZhO8pbVvuvEBIkQVkI=", "dev": true, "requires": { - "to-regex-range": "^5.0.1" + "async": "^2.0.0-rc.3", + "grunt": ">=0.4.1", + "js-beautify": ">=1.4.2", + "lodash": ">=2.4.1", + "rc": ">=0.5.5", + "semver": ">=4.3.1", + "underscore.string": ">=2.3.3" }, "dependencies": { - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", + "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", "dev": true, "requires": { - "is-number": "^7.0.0" + "lodash": "^4.14.0" } } } }, - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "grunt-jsdoc": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/grunt-jsdoc/-/grunt-jsdoc-2.2.1.tgz", + "integrity": "sha512-33QZYBYjv2Ph3H2ygqXHn/o0ttfptw1f9QciOTgvzhzUeiPrnvzMNUApTPtw22T6zgReE5FZ1RR58U2wnK/l+w==", "dev": true, "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" + "cross-spawn": "^3.0.1", + "jsdoc": "~3.5.5", + "marked": "^0.3.9" }, "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "cross-spawn": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", + "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", "dev": true, "requires": { - "p-limit": "^2.0.0" + "lru-cache": "^4.0.1", + "which": "^1.2.9" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "lru-cache": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", + "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", "dev": true, "requires": { - "find-up": "^3.0.0" + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" } } } }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } + "grunt-known-options": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.1.tgz", + "integrity": "sha512-cHwsLqoighpu7TuYj5RonnEuxGVFnztcUqTqp5rXFGYL4OuPFofwC4Ycg7n9fYwvK6F5WbYgeVOwph9Crs2fsQ==", + "dev": true }, - "flat-cache": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", - "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "grunt-legacy-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-2.0.0.tgz", + "integrity": "sha512-1m3+5QvDYfR1ltr8hjiaiNjddxGdQWcH0rw1iKKiQnF0+xtgTazirSTGu68RchPyh1OBng1bBUjLmX8q9NpoCw==", "dev": true, "requires": { - "circular-json": "^0.3.1", - "del": "^2.0.2", - "graceful-fs": "^4.1.2", - "write": "^0.2.1" + "colors": "~1.1.2", + "grunt-legacy-log-utils": "~2.0.0", + "hooker": "~0.2.3", + "lodash": "~4.17.5" } }, - "follow-redirects": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.11.0.tgz", - "integrity": "sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==", + "grunt-legacy-log-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.0.1.tgz", + "integrity": "sha512-o7uHyO/J+i2tXG8r2bZNlVk20vlIFJ9IEYyHMCQGfWYru8Jv3wTqKZzvV30YW9rWEjq0eP3cflQ1qWojIe9VFA==", "dev": true, "requires": { - "debug": "^3.0.0" + "chalk": "~2.4.1", + "lodash": "~4.17.10" }, "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, - "foreground-child": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", - "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", + "grunt-legacy-util": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-1.1.1.tgz", + "integrity": "sha512-9zyA29w/fBe6BIfjGENndwoe1Uy31BIXxTH3s8mga0Z5Bz2Sp4UCjkeyv2tI449ymkx3x26B+46FV4fXEddl5A==", "dev": true, "requires": { - "cross-spawn": "^4", - "signal-exit": "^3.0.0" + "async": "~1.5.2", + "exit": "~0.1.1", + "getobject": "~0.1.0", + "hooker": "~0.2.3", + "lodash": "~4.17.10", + "underscore.string": "~3.3.4", + "which": "~1.3.0" }, "dependencies": { - "cross-spawn": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", - "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "which": "^1.2.9" - } - }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "isexe": "^2.0.0" } } } }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "dev": true - }, - "glob": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", - "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "grunt-mocha-test": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/grunt-mocha-test/-/grunt-mocha-test-0.13.3.tgz", + "integrity": "sha512-zQGEsi3d+ViPPi7/4jcj78afKKAKiAA5n61pknQYi25Ugik+aNOuRmiOkmb8mN2CeG8YxT+YdT1H1Q7B/eNkoQ==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.2", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "hooker": "^0.2.3", + "mkdirp": "^0.5.0" } }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "grunt-simple-nyc": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/grunt-simple-nyc/-/grunt-simple-nyc-3.0.1.tgz", + "integrity": "sha512-/YLY+jNI6gBuVO3xu07zwvDN+orTAFS50W00yb/2ncvc2PFO4pR+oU7TyiHhe8a6O3KuQDHsyCE0iE+rqJagQg==", "dev": true, "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "lodash": "^4.17.15", + "nyc": "^14.1.0", + "simple-cli": "^5.0.3" } }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + "grunt-terser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/grunt-terser/-/grunt-terser-0.1.0.tgz", + "integrity": "sha1-l5pXHR7ZdkdzgZ2w9X3Ua8ipxdU=", + "dev": true, + "requires": { + "terser": "^3.8.0" + } }, - "graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "grunt-text-replace": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/grunt-text-replace/-/grunt-text-replace-0.4.0.tgz", + "integrity": "sha1-252c5Z4v5J2id+nbwZXD4Rz7FsI=", "dev": true }, - "growl": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", - "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", - "dev": true + "gruntify-eslint": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gruntify-eslint/-/gruntify-eslint-4.0.0.tgz", + "integrity": "sha512-wEa2WjMGVDzQbq1QmOiDX51/CfaAIS5xx1oSKIjfWVLl/fYbV7PtfWsUhuaQrPIy1se4Crpg3kZFZndw02l16g==", + "dev": true, + "requires": { + "eslint": "^4.0.0" + } }, "has": { "version": "1.0.1", @@ -1688,6 +4554,76 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "hash.js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", @@ -1724,6 +4660,22 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "home-or-tmp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.1" + } + }, + "hooker": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=", + "dev": true + }, "hosted-git-info": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", @@ -1731,60 +4683,47 @@ "dev": true }, "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.0.tgz", + "integrity": "sha512-a4u9BeERWGu/S8JiWEAQcdrg9v4QArtP9keViQjGMdff20fBdd8waotXaNmODqBe6uZ3Nafi7K/ho4gCQHV3Ig==", "dev": true }, - "http-proxy": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz", - "integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==", - "dev": true, - "requires": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - } + "htmlescape": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", + "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=", + "dev": true }, - "http-server": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/http-server/-/http-server-0.12.3.tgz", - "integrity": "sha512-be0dKG6pni92bRjq0kvExtj/NrrAd28/8fCXkaI/4piTwQMSDSLMhWyW0NI1V+DBI3aa1HMlQu46/HjVLfmugA==", + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", "dev": true, "requires": { - "basic-auth": "^1.0.3", - "colors": "^1.4.0", - "corser": "^2.0.1", - "ecstatic": "^3.3.2", - "http-proxy": "^1.18.0", - "minimist": "^1.2.5", - "opener": "^1.5.1", - "portfinder": "^1.0.25", - "secure-compare": "3.0.1", - "union": "~0.5.0" - }, - "dependencies": { - "basic-auth": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz", - "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=", - "dev": true - }, - "colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - } + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": ">= 1.3.1 < 2" } }, + "http-parser-js": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.13.tgz", + "integrity": "sha1-O9bW/ebjFyyTNMOzO2wZPYD+ETc=", + "dev": true + }, + "http2": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/http2/-/http2-3.3.7.tgz", + "integrity": "sha512-puSi8M8WNlFJm9Pk4c/Mbz9Gwparuj3gO9/RRO5zv6piQ0FY+9Qywp0PdWshYgsMJSalixFY7eC6oPu0zRxLAQ==", + "dev": true + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, "iconv-lite": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", @@ -1808,6 +4747,15 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1823,6 +4771,21 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "inline-source-map": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", + "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=", + "dev": true, + "requires": { + "source-map": "~0.5.3" + } + }, "inquirer": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", @@ -1922,38 +4885,153 @@ } } }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "insert-module-globals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.2.0.tgz", + "integrity": "sha512-VE6NlW+WGn2/AeOMd496AHFYmE7eLKkUY6Ty31k4og5vmA3Fjuwe9v6ifH6Xx/Hz27QvdoMoviw1/pqWRB09Sw==", + "dev": true, + "requires": { + "JSONStream": "^1.0.3", + "acorn-node": "^1.5.2", + "combine-source-map": "^0.8.0", + "concat-stream": "^1.6.1", + "is-buffer": "^1.1.0", + "path-is-absolute": "^1.0.1", + "process": "~0.11.0", + "through2": "^2.0.0", + "undeclared-identifiers": "^1.1.2", + "xtend": "^4.0.0" + }, + "dependencies": { + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + } + } + } + }, + "invariant": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", + "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "requires": { + "builtin-modules": "^1.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", "dev": true }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", "dev": true, "requires": { - "builtin-modules": "^1.0.0" + "number-is-nan": "^1.0.0" } }, "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "is-module": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } }, "is-path-cwd": { "version": "1.0.0", @@ -1979,21 +5057,29 @@ "path-is-inside": "^1.0.1" } }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", "dev": true }, - "is-reference": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz", - "integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==", - "dev": true, - "requires": { - "@types/estree": "0.0.39" - } - }, "is-resolvable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", @@ -2006,6 +5092,18 @@ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -2146,25 +5244,16 @@ "html-escaper": "^2.0.0" } }, - "jest-worker": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", - "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", + "js-beautify": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.7.4.tgz", + "integrity": "sha512-6YX1g+lIl0/JDxjFFbgj7fz6i0bWFa2Hdc7PfGqFhynaEiYe1NJ3R1nda0VGaRiGU82OllR+EGDoWFpGr3k5Kg==", "dev": true, "requires": { - "merge-stream": "^2.0.0", - "supports-color": "^6.1.0" - }, - "dependencies": { - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "config-chain": "~1.1.5", + "editorconfig": "^0.13.2", + "mkdirp": "~0.5.0", + "nopt": "~3.0.1" } }, "js-tokens": { @@ -2184,77 +5273,46 @@ } }, "js2xmlparser": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.1.tgz", - "integrity": "sha512-KrPTolcw6RocpYjdC7pL7v62e55q7qOMHvLX1UCLc5AAS8qeJ6nukarEJAF2KL2PZxlbGueEbINqZR2bDe/gUw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-3.0.0.tgz", + "integrity": "sha1-P7YOqgicVED5MZ9RdgzNB+JJlzM=", "dev": true, "requires": { - "xmlcreate": "^2.0.3" + "xmlcreate": "^1.0.1" } }, "jsdoc": { - "version": "github:openpgpjs/jsdoc#0f1816eb4553856647b4ca9561b9307b11ec4f9e", - "from": "github:openpgpjs/jsdoc#0f1816eb4553856647b4ca9561b9307b11ec4f9e", - "dev": true, - "requires": { - "@babel/parser": "^7.9.4", - "bluebird": "^3.7.2", - "catharsis": "^0.8.11", - "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.1", - "klaw": "^3.0.0", - "markdown-it": "^10.0.0", - "markdown-it-anchor": "^5.2.7", - "marked": "^0.8.2", - "mkdirp": "^1.0.4", - "requizzle": "^0.2.3", - "strip-json-comments": "^3.1.0", + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.5.5.tgz", + "integrity": "sha512-6PxB65TAU4WO0Wzyr/4/YhlGovXl0EVYfpKbpSroSj0qBxT4/xod/l40Opkm38dRHRdQgdeY836M0uVnJQG7kg==", + "dev": true, + "requires": { + "babylon": "7.0.0-beta.19", + "bluebird": "~3.5.0", + "catharsis": "~0.8.9", + "escape-string-regexp": "~1.0.5", + "js2xmlparser": "~3.0.0", + "klaw": "~2.0.0", + "marked": "~0.3.6", + "mkdirp": "~0.5.1", + "requizzle": "~0.2.1", + "strip-json-comments": "~2.0.1", "taffydb": "2.6.2", - "underscore": "~1.10.2" + "underscore": "~1.8.3" }, "dependencies": { - "@babel/parser": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz", - "integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==", - "dev": true - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - }, - "marked": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.8.2.tgz", - "integrity": "sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw==", - "dev": true - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", - "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==", + "babylon": { + "version": "7.0.0-beta.19", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.19.tgz", + "integrity": "sha512-Vg0C9s/REX6/WIXN37UKpv5ZhRi6A4pjHlpkE34+8/a6c2W1Q692n3hmc+SZG5lKRnaExLUbxtJ1SVT+KaCQ/A==", "dev": true } } }, "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", "dev": true }, "json-parse-better-errors": { @@ -2269,21 +5327,91 @@ "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", "dev": true }, + "json-stable-stringify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz", + "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=", + "dev": true, + "requires": { + "jsonify": "~0.0.0" + } + }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "key-list": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/key-list/-/key-list-0.1.4.tgz", + "integrity": "sha512-DMGLZAmEoKRUHPlc772EW0i92P/WY12/oWYc2pQZb5MVGOSjYmF0BEQXbOLjbou1+/PqZ+CivwfyjaUwmyl4CQ==", + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, "klaw": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", - "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-2.0.0.tgz", + "integrity": "sha1-WcEo4Nxc5BAgEVEZTuucv4WGUPY=", "dev": true, "requires": { "graceful-fs": "^4.1.9" } }, + "labeled-stream-splicer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.1.tgz", + "integrity": "sha512-MC94mHZRvJ3LfykJlTUipBqenZz1pacOZEMhhQ8dMGcDHs0SBE5GbsavUXV7YtP3icBW17W0Zy1I0lfASmo9Pg==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "isarray": "^2.0.4", + "stream-splicer": "^2.0.0" + }, + "dependencies": { + "isarray": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.4.tgz", + "integrity": "sha512-GMxXOiUirWg1xTKRipM0Ek07rX+ubx4nNVElTJdNLYmNO/2YrDkgJGw9CljXn+r4EWiDQg/8lsRdHyg2PJuUaA==", + "dev": true + } + } + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -2294,13 +5422,23 @@ "type-check": "~0.3.2" } }, - "linkify-it": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", - "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "livereload-js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.3.0.tgz", + "integrity": "sha512-j1R0/FeGa64Y+NmqfZhyoVRzcFlOZ8sNlKzHjh4VvLULFACZhn68XrX5DFg2FhMvSMJmROuFxRSa560ECWKBMg==", + "dev": true + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { - "uc.micro": "^1.0.1" + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" } }, "locate-path": { @@ -2345,19 +5483,44 @@ "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", "dev": true }, + "lodash.memoize": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", + "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=", + "dev": true + }, "lolex": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.3.2.tgz", "integrity": "sha512-A5pN2tkFj7H0dGIAM6MFvHKMJcPnjZsOMvR7ujCjfgW5TbV6H9vb1PgxLtHvjqNZTHsUolz+6/WEO0N1xNx2ng==", "dev": true }, - "magic-string": { - "version": "0.25.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", - "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", + "loose-envify": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", + "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", + "dev": true, + "requires": { + "js-tokens": "^3.0.0" + } + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lru-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-3.2.0.tgz", + "integrity": "sha1-cXibO39Tmb7IVl3aOKow0qCX7+4=", "dev": true, "requires": { - "sourcemap-codec": "^1.4.4" + "pseudomap": "^1.0.1" } }, "make-dir": { @@ -2384,30 +5547,68 @@ } } }, - "markdown-it": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", - "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", "dev": true, "requires": { - "argparse": "^1.0.7", - "entities": "~2.0.0", - "linkify-it": "^2.0.0", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" + "object-visit": "^1.0.0" } }, - "markdown-it-anchor": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.2.7.tgz", - "integrity": "sha512-REFmIaSS6szaD1bye80DMbp7ePwsPNvLTR5HunsUcZ0SG0rWJQ+Pz24R4UlTKtjKBPhxo0v0tOBDYjZQQknW8Q==", + "marked": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.12.tgz", + "integrity": "sha512-k4NaW+vS7ytQn6MgJn3fYpQt20/mOgYM5Ft9BYMfQJDz2QT6yEeS9XJ8k2Nw8JTeWK/znPPW2n3UJGzyYEiMoA==", "dev": true }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", - "dev": true + "md5.js": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", + "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + } + } }, "merge-source-map": { "version": "1.1.0", @@ -2426,20 +5627,35 @@ } } }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + } + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", "dev": true }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "mime-db": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=", + "dev": true + }, + "mime-types": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", "dev": true, "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" + "mime-db": "~1.30.0" } }, "mimic-fn": { @@ -2474,6 +5690,27 @@ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", @@ -2547,6 +5784,80 @@ } } }, + "modify-babel-preset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/modify-babel-preset/-/modify-babel-preset-2.0.2.tgz", + "integrity": "sha1-v6UJZp/kn0IiwM4XG6RO0OgVUec=", + "dev": true, + "requires": { + "require-relative": "^0.8.7" + } + }, + "module-deps": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.1.0.tgz", + "integrity": "sha512-NPs5N511VD1rrVJihSso/LiBShRbJALYBKzDW91uZYy7BpjnO4bGnZL3HjZ9yKcFdZUWwaYjDz9zxbuP7vKMuQ==", + "dev": true, + "requires": { + "JSONStream": "^1.0.3", + "browser-resolve": "^1.7.0", + "cached-path-relative": "^1.0.0", + "concat-stream": "~1.6.0", + "defined": "^1.0.0", + "detective": "^5.0.2", + "duplexer2": "^0.1.2", + "inherits": "^2.0.1", + "parents": "^1.0.0", + "readable-stream": "^2.0.2", + "resolve": "^1.4.0", + "stream-combiner2": "^1.1.1", + "subarg": "^1.0.0", + "through2": "^2.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "resolve": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "dev": true, + "requires": { + "path-parse": "^1.0.5" + } + }, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + } + } + } + }, + "morgan": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", + "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==", + "dev": true, + "requires": { + "basic-auth": "~2.0.0", + "debug": "2.6.9", + "depd": "~1.1.2", + "on-finished": "~2.3.0", + "on-headers": "~1.0.1" + }, + "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + } + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -2559,12 +5870,64 @@ "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", "dev": true }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "dev": true + }, "nested-error-stacks": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz", @@ -2624,6 +5987,15 @@ "write-file-atomic": "^1.1.4" } }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, "normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", @@ -2636,6 +6008,21 @@ "validate-npm-package-license": "^3.0.1" } }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, "nyc": { "version": "14.1.1", "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz", @@ -2669,6 +6056,47 @@ "yargs-parser": "^13.0.0" }, "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, "find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -2678,6 +6106,12 @@ "locate-path": "^3.0.0" } }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -2692,6 +6126,12 @@ "path-is-absolute": "^1.0.0" } }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, "locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", @@ -2703,9 +6143,9 @@ } }, "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -2732,6 +6172,12 @@ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -2746,6 +6192,77 @@ "requires": { "glob": "^7.1.3" } + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } } } }, @@ -2755,6 +6272,77 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", + "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=", + "dev": true + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2773,11 +6361,24 @@ "mimic-fn": "^1.0.0" } }, - "opener": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", - "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==", - "dev": true + "opn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/opn/-/opn-4.0.2.tgz", + "integrity": "sha1-erwi5kTf9jsKltWrfyeQwPAavJU=", + "dev": true, + "requires": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + } + }, + "opted": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/opted/-/opted-1.0.2.tgz", + "integrity": "sha1-CU562dDA/CuzhLTYpQfieOrVV8k=", + "dev": true, + "requires": { + "lodash": "^4.17.4" + } }, "optionator": { "version": "0.8.2", @@ -2793,18 +6394,42 @@ "wordwrap": "~1.0.0" } }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "requires": { + "lcid": "^1.0.0" + } + }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, + "outpipe": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/outpipe/-/outpipe-1.1.1.tgz", + "integrity": "sha1-UM+GFjZeh+Ax4ppeyTOaPaRyX6I=", + "dev": true, + "requires": { + "shell-quote": "^1.4.2" + } + }, "p-limit": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", @@ -2842,18 +6467,54 @@ }, "dependencies": { "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", "dev": true } } }, "pako": { - "version": "github:openpgpjs/pako#f38f7368a5fa511e54b95add2f04444c3a9d803f", - "from": "github:openpgpjs/pako#f38f7368a5fa511e54b95add2f04444c3a9d803f", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", + "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", "dev": true }, + "parents": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", + "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", + "dev": true, + "requires": { + "path-platform": "~0.11.15" + } + }, + "parse-asn1": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", + "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", + "dev": true, + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3" + }, + "dependencies": { + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + } + } + }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", @@ -2863,6 +6524,30 @@ "error-ex": "^1.2.0" } }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, "path-exists": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", @@ -2884,12 +6569,24 @@ "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", "dev": true }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, "path-parse": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", "dev": true }, + "path-platform": { + "version": "0.11.15", + "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", + "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=", + "dev": true + }, "path-to-regexp": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", @@ -2907,17 +6604,35 @@ } } }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, "pathval": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", "dev": true }, - "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true + "pbkdf2": { + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.16.tgz", + "integrity": "sha512-y4CXP3thSxqf7c0qmOF+9UeOTrifiVTIM+u7NWlq+PRsHbr7r7dpCmvzrZxa96JJUNi0Y5w9VqG5ZNeCVMoDcA==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } }, "pify": { "version": "2.3.0", @@ -2955,72 +6670,198 @@ "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", "dev": true }, - "portfinder": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz", - "integrity": "sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ==", + "portscanner": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-1.2.0.tgz", + "integrity": "sha1-sUu9olfRTDEPqcwJaCrwLUCWGAI=", "dev": true, "requires": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.1" - }, - "dependencies": { - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "async": "1.5.2" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "progress": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "dev": true + }, + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "public-encrypt": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.2.tgz", + "integrity": "sha512-4kJ5Esocg8X3h8YgJsKAuoesBgB7mqH3eowiDzMUPKiRDDE7E/BqqZD1hnTByIaAFiwAw246YEltSq7tdrOH0Q==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "randombytes": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", + "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", + "dev": true + }, + "raw-body": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", + "integrity": "sha1-HQJ8K/oRasxmI7yo8AAWVyqH1CU=", + "dev": true, + "requires": { + "bytes": "1", + "string_decoder": "0.10" + }, + "dependencies": { + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", "dev": true } } }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", - "dev": true + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + } + } }, - "progress": { + "read-only-stream": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", - "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", - "dev": true + "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", + "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + } }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } }, "readable-stream": { "version": "2.3.3", @@ -3037,12 +6878,89 @@ "util-deprecate": "~1.0.1" } }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "regenerate": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", + "integrity": "sha512-jVpo1GadrDAK59t/0jRx5VxYWQEDkkEKi6+HjE3joFVLfDOh9Xrdh0dF1eSq+BI/SwvTQ44gSscJ8N5zYL61sg==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + }, + "regenerator-transform": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", + "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", + "dev": true, + "requires": { + "babel-runtime": "^6.18.0", + "babel-types": "^6.19.0", + "private": "^0.1.6" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, "regexpp": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", "dev": true }, + "regexpu-core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", + "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", + "dev": true, + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, "release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", @@ -3052,6 +6970,33 @@ "es6-error": "^4.0.1" } }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -3059,9 +7004,15 @@ "dev": true }, "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "require-relative": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", + "integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=", "dev": true }, "require-uncached": { @@ -3074,44 +7025,41 @@ "resolve-from": "^1.0.0" } }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true - }, "requizzle": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", - "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.1.tgz", + "integrity": "sha1-aUPDUwxNmn5G8c3dUcFY/GcM294=", "dev": true, "requires": { - "path-parse": "^1.0.6" + "underscore": "~1.6.0" }, "dependencies": { - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "underscore": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", "dev": true } } }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, "resolve-from": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", "dev": true }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, "restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", @@ -3122,58 +7070,26 @@ "signal-exit": "^3.0.2" } }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, "rimraf": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", "dev": true }, - "rollup": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.7.2.tgz", - "integrity": "sha512-SdtTZVMMVSPe7SNv4exUyPXARe5v/p3TeeG3LRA5WabLPJt4Usi3wVrvVlyAUTG40JJmqS6zbIHt2vWTss2prw==", - "dev": true, - "requires": { - "fsevents": "~2.1.2" - }, - "dependencies": { - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, - "optional": true - } - } - }, - "rollup-plugin-terser": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-5.3.0.tgz", - "integrity": "sha512-XGMJihTIO3eIBsVGq7jiNYOdDMb3pVxuzY0uhOE/FM4x/u9nQgr3+McsjzqBn3QfHIpNSZmFnpoKAwHBEcsT7g==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.5.5", - "jest-worker": "^24.9.0", - "rollup-pluginutils": "^2.8.2", - "serialize-javascript": "^2.1.2", - "terser": "^4.6.2" - } - }, - "rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", "dev": true, "requires": { - "estree-walker": "^0.6.1" - }, - "dependencies": { - "estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", - "dev": true - } + "hash-base": "^3.0.0", + "inherits": "^2.0.1" } }, "run-async": { @@ -3206,24 +7122,44 @@ "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", "dev": true }, + "safe-json-parse": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", + "integrity": "sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, "samsam": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", "dev": true }, - "secure-compare": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", - "integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=", - "dev": true - }, "seek-bzip": { - "version": "github:openpgpjs/seek-bzip#4b89457f20c0e1921b4689106a31c99782c29829", - "from": "github:openpgpjs/seek-bzip#4b89457f20c0e1921b4689106a31c99782c29829", + "version": "github:openpgpjs/seek-bzip#6187fc025851d35c4e104a25ea15a10b9b8d6f7d", + "from": "github:openpgpjs/seek-bzip#6187fc025851d35c4e104a25ea15a10b9b8d6f7d", "dev": true, "requires": { "commander": "~2.8.1" + }, + "dependencies": { + "commander": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", + "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", + "dev": true, + "requires": { + "graceful-readlink": ">= 1.0.0" + } + } } }, "semver": { @@ -3232,11 +7168,53 @@ "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", "dev": true }, - "serialize-javascript": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", - "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", - "dev": true + "send": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", + "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.1", + "destroy": "~1.0.4", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.3.1" + } + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + } + }, + "serve-static": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", + "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==", + "dev": true, + "requires": { + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.1" + } }, "set-blocking": { "version": "2.0.0", @@ -3244,6 +7222,55 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shasum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", + "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=", + "dev": true, + "requires": { + "json-stable-stringify": "~0.0.0", + "sha.js": "~2.4.4" + } + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -3253,16 +7280,126 @@ "shebang-regex": "^1.0.0" } }, - "shebang-regex": { + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "shell-quote": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", + "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=", + "dev": true, + "requires": { + "array-filter": "~0.0.0", + "array-map": "~0.0.0", + "array-reduce": "~0.0.0", + "jsonify": "~0.0.0" + } + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "simple-cli": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/simple-cli/-/simple-cli-5.0.5.tgz", + "integrity": "sha512-Er2FhsIayL/sktxg6fOCdNQJBTXhlf/fswNFsdmks88xsHzQ/IXGwxYgSSKeXBq4yqn83/iD4Sg8yjagwysUgw==", + "dev": true, + "requires": { + "async": "^3.1.0", + "chalk": "^2.4.2", + "cross-spawn": "^7.0.0", + "key-list": "^0.1.4", + "lodash": "^4.17.15", + "opted": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "async": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "cross-spawn": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", + "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "simple-concat": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", + "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=", "dev": true }, "sinon": { @@ -3297,6 +7434,12 @@ } } }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, "slice-ansi": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", @@ -3319,34 +7462,146 @@ "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=" }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + } + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", "dev": true, "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "source-map": "^0.5.6" } }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "dev": true }, "spawn-wrap": { @@ -3418,40 +7673,140 @@ "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", "dev": true }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "define-property": "^0.2.5", + "object-copy": "^0.1.0" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", + "dev": true + }, + "stream-browserify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", + "dev": true, + "requires": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" } } } }, + "stream-splicer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.0.tgz", + "integrity": "sha1-G2O+Q4oTPktnHMGTUZdgAXWRDYM=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.2" + } + }, + "string-template": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", @@ -3470,18 +7825,62 @@ "ansi-regex": "^2.0.0" } }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1" + } + }, "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, + "subarg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", + "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", + "dev": true, + "requires": { + "minimist": "^1.1.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + } + } + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true }, + "syntax-error": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", + "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", + "dev": true, + "requires": { + "acorn-node": "^1.2.0" + } + }, "table": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", @@ -3571,20 +7970,20 @@ "dev": true }, "terser": { - "version": "4.6.12", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.12.tgz", - "integrity": "sha512-fnIwuaKjFPANG6MAixC/k1TDtnl1YlPLUlLVIxxGZUn1gfUx2+l3/zGNB72wya+lgsb50QBi2tUV75RiODwnww==", + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-3.14.1.tgz", + "integrity": "sha512-NSo3E99QDbYSMeJaEk9YW2lTg3qS9V0aKGlb+PlOrei1X02r1wSBHCNX/O+yeTRFSWPKPIGj6MqvvdqV4rnVGw==", "dev": true, "requires": { - "commander": "^2.20.0", + "commander": "~2.17.1", "source-map": "~0.6.1", - "source-map-support": "~0.5.12" + "source-map-support": "~0.5.6" }, "dependencies": { "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", "dev": true }, "source-map": { @@ -3592,6 +7991,16 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true + }, + "source-map-support": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } } } }, @@ -3653,9 +8062,9 @@ } }, "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -3728,6 +8137,12 @@ "read-pkg": "^3.0.0" } }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -3754,6 +8169,76 @@ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, + "through2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz", + "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=", + "dev": true, + "requires": { + "readable-stream": ">=1.1.13-1 <1.2.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "timers-browserify": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", + "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", + "dev": true, + "requires": { + "process": "~0.11.0" + } + }, + "tiny-lr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz", + "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==", + "dev": true, + "requires": { + "body": "^5.1.0", + "debug": "^3.1.0", + "faye-websocket": "~0.10.0", + "livereload-js": "^2.3.0", + "object-assign": "^4.1.0", + "qs": "^6.4.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -3763,10 +8248,76 @@ "os-tmpdir": "~1.0.2" } }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + } + } + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "tty-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", + "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", "dev": true }, "tweetnacl": { @@ -3795,45 +8346,178 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, - "typescript": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.2.tgz", - "integrity": "sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ==", + "umd": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", + "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==", "dev": true }, - "uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dev": true + "undeclared-identifiers": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.2.tgz", + "integrity": "sha512-13EaeocO4edF/3JKime9rD7oB6QI8llAGhgn5fKOPyfkJbRb6NFv9pYV6dFEmpa4uRjKeBqLZP8GpuzqHlKDMQ==", + "dev": true, + "requires": { + "acorn-node": "^1.3.0", + "get-assigned-identifiers": "^1.2.0", + "simple-concat": "^1.0.0", + "xtend": "^4.0.1" + } }, "underscore": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz", - "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", "dev": true }, - "union": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", - "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "underscore-contrib": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/underscore-contrib/-/underscore-contrib-0.3.0.tgz", + "integrity": "sha1-ZltmwkeD+PorGMn4y7Dix9SMJsc=", "dev": true, "requires": { - "qs": "^6.4.0" + "underscore": "1.6.0" + }, + "dependencies": { + "underscore": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", + "dev": true + } } }, - "url-join": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz", - "integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=", + "underscore.string": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.5.tgz", + "integrity": "sha512-g+dpmgn+XBneLmXXo+sGlW5xQEt4ErkS3mgeN2GFbremYeMBSJKr9Wf2KJplQVaiPY/f7FN6atosWYNm9ovrYg==", + "dev": true, + "requires": { + "sprintf-js": "^1.0.3", + "util-deprecate": "^1.0.2" + } + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + } + } + }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", "dev": true }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", @@ -3850,19 +8534,449 @@ "spdx-expression-parse": "~1.0.0" } }, + "vm-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz", + "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==", + "dev": true + }, + "watchify": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/watchify/-/watchify-3.11.1.tgz", + "integrity": "sha512-WwnUClyFNRMB2NIiHgJU9RQPQNqVeFk7OmZaWf5dC5EnNa0Mgr7imBydbaJ7tGTuPM2hz1Cb4uiBvK9NVxMfog==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "browserify": "^16.1.0", + "chokidar": "^2.1.1", + "defined": "^1.0.0", + "outpipe": "^1.1.0", + "through2": "^2.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } + } + } + } + }, "web-stream-tools": { - "version": "github:openpgpjs/web-stream-tools#5e6cb1a976d50c421091907615cf7cce77ac4f2a", - "from": "github:openpgpjs/web-stream-tools#5e6cb1a976d50c421091907615cf7cce77ac4f2a", + "version": "github:openpgpjs/web-stream-tools#dc4b05e8a272b45819233f3df735423432beacfc", + "from": "github:openpgpjs/web-stream-tools#dc4b05e8a272b45819233f3df735423432beacfc", + "dev": true + }, + "websocket-driver": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz", + "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=", "dev": true, "requires": { - "@mattiasbuelens/web-streams-adapter": "0.1.0-alpha.5", - "web-streams-polyfill": "~2.1.1" + "http-parser-js": ">=0.4.0", + "websocket-extensions": ">=0.1.1" } }, - "web-streams-polyfill": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-2.1.1.tgz", - "integrity": "sha512-dlNpL2aab3g8CKfGz6rl8FNmGaRWLLn2g/DtSc9IjB30mEdE6XxzPfPSig5BwGSzI+oLxHyETrQGKjrVVhbLCg==", + "websocket-extensions": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", "dev": true }, "whatwg-fetch": { @@ -3881,9 +8995,9 @@ } }, "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", "dev": true }, "wordwrap": { @@ -3893,40 +9007,13 @@ "dev": true }, "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" } }, "wrappy": { @@ -3955,15 +9042,21 @@ } }, "xmlcreate": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.3.tgz", - "integrity": "sha512-HgS+X6zAztGa9zIK3Y3LXuJes33Lz9x+YyTxgrkIdabu2vqcGOWwdfCpf1hWLRrd553wd4QCDf6BBO6FfdsRiQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-1.0.2.tgz", + "integrity": "sha1-+mv3YqYKQT+z3Y9LA8WyaSONMI8=", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", "dev": true }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", "dev": true }, "yallist": { @@ -3973,82 +9066,33 @@ "dev": true }, "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", + "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", "dev": true, "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", + "require-main-filename": "^1.0.1", "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - } + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^4.2.0" } }, "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", + "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", "dev": true, "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "camelcase": "^3.0.0" } } } diff --git a/package.json b/package.json index 212e94c7..087682dc 100644 --- a/package.json +++ b/package.json @@ -13,77 +13,74 @@ "gpg", "openpgp" ], - "main": "dist/node/openpgp.min.js", - "module": "dist/node/openpgp.min.mjs", - "browser": { - "./dist/node/openpgp.min.js": "./dist/openpgp.min.js", - "./dist/node/openpgp.min.mjs": "./dist/openpgp.min.mjs" - }, - "types": "openpgp.d.ts", + "main": "dist/openpgp.js", "directories": { "lib": "src" }, "files": [ + "src/", "dist/", - "lightweight/", - "openpgp.d.ts" + "test/unittests.js", + "test/general", + "test/crypto" ], - "esm": { - "cjs": { - "dedefault": true - } - }, "scripts": { - "build": "rollup --config", - "build-test": "npm run build --build-only=test", - "prepare": "npm run build", - "test": "mocha --require esm --timeout 120000 test/unittests.js", - "test-type-definitions": "tsc test/typescript/definitions.ts && node test/typescript/definitions.js", - "start": "http-server", - "prebrowsertest": "npm run build-test", - "browsertest": "npm start -- -o test/unittests.html", - "coverage": "nyc npm test", - "lint": "eslint 'src/**/*.js' 'test/crypto/**/*.js'", - "docs": "jsdoc --configure .jsdocrc.js --destination docs --recurse README.md src", - "preversion": "rm -rf dist docs node_modules && npm install && npm test", - "version": "npm run docs && git add -A docs", - "postversion": "git push && git push --tags && npm publish" + "build": "grunt build --compat copy:openpgp_compat && grunt build --lightweight copy:openpgp_lightweight clean:js && grunt build", + "pretest": "grunt", + "test": "grunt test", + "lint": "eslint src" }, "devDependencies": { - "@mattiasbuelens/web-streams-adapter": "0.1.0-alpha.5", - "@rollup/plugin-commonjs": "^11.1.0", - "@rollup/plugin-node-resolve": "^7.1.3", - "@rollup/plugin-replace": "^2.3.2", - "@types/chai": "^4.2.14", - "asmcrypto.js": "github:openpgpjs/asmcrypto#5b994303a9d3e27e0915f72a10b6c2c51535a4dc", - "babel-eslint": "^10.1.0", - "bn.js": "^4.11.8", - "buffer": "^5.0.8", + "babel-core": "^6.26.3", + "babel-plugin-syntax-async-functions": "^6.13.0", + "babel-plugin-transform-async-to-generator": "^6.24.1", + "babel-plugin-transform-regenerator": "^6.26.0", + "babel-plugin-transform-remove-strict-mode": "0.0.2", + "babel-plugin-transform-runtime": "^6.23.0", + "babel-polyfill": "^6.26.0", + "babel-preset-env": "^1.7.0", + "babel-preset-es2015-mod": "^6.6.0", + "babel-preset-es3": "^1.0.1", + "babelify": "^8.0.0", + "browserify-derequire": "^0.9.4", "chai": "^4.1.2", "chai-as-promised": "^7.1.1", - "elliptic": "github:openpgpjs/elliptic#ab7d8268c60b6abeb175841c578c224ac5b2d279", - "email-addresses": "3.1.0", + "core-js": "^2.5.3", "eslint": "^4.17.0", "eslint-config-airbnb": "^16.1.0", "eslint-config-airbnb-base": "^12.1.0", - "eslint-plugin-chai-friendly": "^0.5.0", "eslint-plugin-import": "^2.8.0", - "esm": "^3.2.25", - "hash.js": "^1.1.3", - "http-server": "^0.12.3", - "jsdoc": "github:openpgpjs/jsdoc#0f1816eb4553856647b4ca9561b9307b11ec4f9e", + "eslint-plugin-chai-friendly": "^0.5.0", + "grunt": "^1.1.0", + "grunt-browserify": "^5.3.0", + "grunt-contrib-clean": "~1.1.0", + "grunt-contrib-connect": "~1.0.2", + "grunt-contrib-copy": "~1.0.0", + "grunt-contrib-watch": "^1.1.0", + "grunt-header": "^1.1.0", + "grunt-jsbeautifier": "^0.2.13", + "grunt-jsdoc": "^2.2.1", + "grunt-mocha-test": "^0.13.3", + "grunt-simple-nyc": "^3.0.1", + "grunt-terser": "^0.1.0", + "grunt-text-replace": "~0.4.0", + "gruntify-eslint": "^4.0.0", "mocha": "^5.0.0", "nyc": "^14.1.1", - "pako": "github:openpgpjs/pako#f38f7368a5fa511e54b95add2f04444c3a9d803f", - "rollup": "^2.7.2", - "rollup-plugin-terser": "^5.3.0", - "seek-bzip": "github:openpgpjs/seek-bzip#4b89457f20c0e1921b4689106a31c99782c29829", "sinon": "^4.3.0", "text-encoding-utf-8": "^1.0.2", + "whatwg-fetch": "^2.0.3", + "@mattiasbuelens/web-streams-polyfill": "^0.3.1", + "asmcrypto.js": "github:openpgpjs/asmcrypto#475cffa5ccb2cf2556427056679414acf3610d1b", + "bn.js": "^4.11.8", + "buffer": "^5.0.8", + "elliptic": "github:openpgpjs/elliptic#ab7d8268c60b6abeb175841c578c224ac5b2d279", + "hash.js": "^1.1.3", + "pako": "^1.0.6", + "seek-bzip": "github:openpgpjs/seek-bzip#6187fc025851d35c4e104a25ea15a10b9b8d6f7d", "tweetnacl": "github:openpgpjs/tweetnacl-js#3dae25bd3eaa77173f3405676b595721dde92eec", - "typescript": "^4.1.2", - "web-stream-tools": "github:openpgpjs/web-stream-tools#5e6cb1a976d50c421091907615cf7cce77ac4f2a", - "whatwg-fetch": "^2.0.3" + "web-stream-tools": "github:openpgpjs/web-stream-tools#dc4b05e8a272b45819233f3df735423432beacfc", + "email-addresses": "3.1.0" }, "dependencies": { "asn1.js": "^5.0.0", diff --git a/release.sh b/release.sh new file mode 100755 index 00000000..f282d14f --- /dev/null +++ b/release.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +# abort if tests fail +set -e + +# go to root +cd `dirname $0` + +if [ "$#" -ne 1 ] ; then + echo 'Usage: ./release.sh 0.0.0' + exit 0 +fi + +# install dependencies +rm -rf node_modules +npm install + +# set version +grunt set_version --release=$1 + +# build and test +rm -rf dist +rm -f browserify-cache* +npm run build +grunt test + +# Add build files to git +git add --force dist/ bower.json package-lock.json package.json +git commit -m "Release new version" +git tag v$1 +git push +git push --tag + +# publish to npm +npm publish #--tag old-version diff --git a/src/biginteger/bn.interface.js b/src/biginteger/bn.interface.js deleted file mode 100644 index 84226097..00000000 --- a/src/biginteger/bn.interface.js +++ /dev/null @@ -1,327 +0,0 @@ -import BN from 'bn.js'; - -/** - * BigInteger implementation of basic operations - * Wrapper of bn.js library (wwww.github.com/indutny/bn.js) - */ -export default class BigInteger { - /** - * Get a BigInteger (input must be big endian for strings and arrays) - * @param {Number|String|Uint8Array} n value to convert - * @throws {Error} on undefined input - */ - constructor(n) { - if (n === undefined) { - throw new Error('Invalid BigInteger input'); - } - - this.value = new BN(n); - } - - clone() { - const clone = new BigInteger(null); - this.value.copy(clone.value); - return clone; - } - - /** - * BigInteger increment in place - */ - iinc() { - this.value.iadd(new BN(1)); - return this; - } - - /** - * BigInteger increment - * @returns {BigInteger} this + 1 - */ - inc() { - return this.clone().iinc(); - } - - /** - * BigInteger decrement in place - */ - idec() { - this.value.isub(new BN(1)); - return this; - } - - /** - * BigInteger decrement - * @returns {BigInteger} this - 1 - */ - dec() { - return this.clone().idec(); - } - - - /** - * BigInteger addition in place - * @param {BigInteger} x value to add - */ - iadd(x) { - this.value.iadd(x.value); - return this; - } - - /** - * BigInteger addition - * @param {BigInteger} x value to add - * @returns {BigInteger} this + x - */ - add(x) { - return this.clone().iadd(x); - } - - /** - * BigInteger subtraction in place - * @param {BigInteger} x value to subtract - */ - isub(x) { - this.value.isub(x.value); - return this; - } - - /** - * BigInteger subtraction - * @param {BigInteger} x value to subtract - * @returns {BigInteger} this - x - */ - sub(x) { - return this.clone().isub(x); - } - - /** - * BigInteger multiplication in place - * @param {BigInteger} x value to multiply - */ - imul(x) { - this.value.imul(x.value); - return this; - } - - /** - * BigInteger multiplication - * @param {BigInteger} x value to multiply - * @returns {BigInteger} this * x - */ - mul(x) { - return this.clone().imul(x); - } - - /** - * Compute value modulo m, in place - * @param {BigInteger} m modulo - */ - imod(m) { - this.value = this.value.umod(m.value); - return this; - } - - /** - * Compute value modulo m - * @param {BigInteger} m modulo - * @returns {BigInteger} this mod m - */ - mod(m) { - return this.clone().imod(m); - } - - /** - * Compute modular exponentiation - * Much faster than this.exp(e).mod(n) - * @param {BigInteger} e exponent - * @param {BigInteger} n modulo - * @returns {BigInteger} this ** e mod n - */ - modExp(e, n) { - // We use either Montgomery or normal reduction context - // Montgomery requires coprime n and R (montogmery multiplier) - // bn.js picks R as power of 2, so n must be odd - const nred = n.isEven() ? BN.red(n.value) : BN.mont(n.value); - const x = this.clone(); - x.value = x.value.toRed(nred).redPow(e.value).fromRed(); - return x; - } - - /** - * Compute the inverse of this value modulo n - * Note: this and and n must be relatively prime - * @param {BigInteger} n modulo - * @return {BigInteger} x such that this*x = 1 mod n - * @throws {Error} if the inverse does not exist - */ - modInv(n) { - // invm returns a wrong result if the inverse does not exist - if (!this.gcd(n).isOne()) { - throw new Error('Inverse does not exist'); - } - return new BigInteger(this.value.invm(n.value)); - } - - /** - * Compute greatest common divisor between this and n - * @param {BigInteger} n operand - * @return {BigInteger} gcd - */ - gcd(n) { - return new BigInteger(this.value.gcd(n.value)); - } - - /** - * Shift this to the left by x, in place - * @param {BigInteger} x shift value - */ - ileftShift(x) { - this.value.ishln(x.value.toNumber()); - return this; - } - - /** - * Shift this to the left by x - * @param {BigInteger} x shift value - * @returns {BigInteger} this << x - */ - leftShift(x) { - return this.clone().ileftShift(x); - } - - /** - * Shift this to the right by x, in place - * @param {BigInteger} x shift value - */ - irightShift(x) { - this.value.ishrn(x.value.toNumber()); - return this; - } - - /** - * Shift this to the right by x - * @param {BigInteger} x shift value - * @returns {BigInteger} this >> x - */ - rightShift(x) { - return this.clone().irightShift(x); - } - - /** - * Whether this value is equal to x - * @param {BigInteger} x - * @returns {Boolean} - */ - equal(x) { - return this.value.eq(x.value); - } - - /** - * Whether this value is less than x - * @param {BigInteger} x - * @returns {Boolean} - */ - lt(x) { - return this.value.lt(x.value); - } - - /** - * Whether this value is less than or equal to x - * @param {BigInteger} x - * @returns {Boolean} - */ - lte(x) { - return this.value.lte(x.value); - } - - /** - * Whether this value is greater than x - * @param {BigInteger} x - * @returns {Boolean} - */ - gt(x) { - return this.value.gt(x.value); - } - - /** - * Whether this value is greater than or equal to x - * @param {BigInteger} x - * @returns {Boolean} - */ - gte(x) { - return this.value.gte(x.value); - } - - isZero() { - return this.value.isZero(); - } - - isOne() { - return this.value.eq(new BN(1)); - } - - isNegative() { - return this.value.isNeg(); - } - - isEven() { - return this.value.isEven(); - } - - abs() { - const res = this.clone(); - res.value = res.value.abs(); - return res; - } - - /** - * Get this value as a string - * @returns {String} this value - */ - toString() { - return this.value.toString(); - } - - /** - * Get this value as an exact Number (max 53 bits) - * Fails if this value is too large - * @return {Number} - */ - toNumber() { - return this.value.toNumber(); - } - - /** - * Get value of i-th bit - * @param {Number} i bit index - * @returns {Number} bit value - */ - getBit(i) { - return this.value.testn(i) ? 1 : 0; - } - - /** - * Compute bit length - * @returns {Number} bit length - */ - bitLength() { - return this.value.bitLength(); - } - - /** - * Compute byte length - * @returns {Number} byte length - */ - byteLength() { - return this.value.byteLength(); - } - - /** - * Get Uint8Array representation of this number - * @param {String} endian endianess of output array (defaults to 'be') - * @param {Number} length of output array - * @return {Uint8Array} - */ - toUint8Array(endian = 'be', length) { - return this.value.toArrayLike(Uint8Array, endian, length); - } -} diff --git a/src/biginteger/index.js b/src/biginteger/index.js deleted file mode 100644 index 2cd1d54a..00000000 --- a/src/biginteger/index.js +++ /dev/null @@ -1,14 +0,0 @@ -import util from '../util'; -import BigInteger from './native.interface'; - -async function getBigInteger() { - if (util.detectBigInt()) { - return BigInteger; - } else { - const { default: BigInteger } = await import('./bn.interface'); - return BigInteger; - } -} - -// eslint-disable-next-line import/prefer-default-export -export { getBigInteger }; diff --git a/src/biginteger/native.interface.js b/src/biginteger/native.interface.js deleted file mode 100644 index 20397b13..00000000 --- a/src/biginteger/native.interface.js +++ /dev/null @@ -1,446 +0,0 @@ -/* eslint-disable new-cap */ - -/** - * BigInteger implementation of basic operations - * that wraps the native BigInt library. - * Operations are not constant time, - * but we try and limit timing leakage where we can - */ -export default class BigInteger { - /** - * Get a BigInteger (input must be big endian for strings and arrays) - * @param {Number|String|Uint8Array} n value to convert - * @throws {Error} on null or undefined input - */ - constructor(n) { - if (n === undefined) { - throw new Error('Invalid BigInteger input'); - } - - if (n instanceof Uint8Array) { - const bytes = n; - const hex = new Array(bytes.length); - for (let i = 0; i < bytes.length; i++) { - const hexByte = bytes[i].toString(16); - hex[i] = (bytes[i] <= 0xF) ? ('0' + hexByte) : hexByte; - } - this.value = BigInt('0x0' + hex.join('')); - } else { - this.value = BigInt(n); - } - } - - clone() { - return new BigInteger(this.value); - } - - /** - * BigInteger increment in place - */ - iinc() { - this.value++; - return this; - } - - /** - * BigInteger increment - * @returns {BigInteger} this + 1 - */ - inc() { - return this.clone().iinc(); - } - - /** - * BigInteger decrement in place - */ - idec() { - this.value--; - return this; - } - - /** - * BigInteger decrement - * @returns {BigInteger} this - 1 - */ - dec() { - return this.clone().idec(); - } - - /** - * BigInteger addition in place - * @param {BigInteger} x value to add - */ - iadd(x) { - this.value += x.value; - return this; - } - - /** - * BigInteger addition - * @param {BigInteger} x value to add - * @returns {BigInteger} this + x - */ - add(x) { - return this.clone().iadd(x); - } - - /** - * BigInteger subtraction in place - * @param {BigInteger} x value to subtract - */ - isub(x) { - this.value -= x.value; - return this; - } - - /** - * BigInteger subtraction - * @param {BigInteger} x value to subtract - * @returns {BigInteger} this - x - */ - sub(x) { - return this.clone().isub(x); - } - - /** - * BigInteger multiplication in place - * @param {BigInteger} x value to multiply - */ - imul(x) { - this.value *= x.value; - return this; - } - - /** - * BigInteger multiplication - * @param {BigInteger} x value to multiply - * @returns {BigInteger} this * x - */ - mul(x) { - return this.clone().imul(x); - } - - /** - * Compute value modulo m, in place - * @param {BigInteger} m modulo - */ - imod(m) { - this.value %= m.value; - if (this.isNegative()) { - this.iadd(m); - } - return this; - } - - /** - * Compute value modulo m - * @param {BigInteger} m modulo - * @returns {BigInteger} this mod m - */ - mod(m) { - return this.clone().imod(m); - } - - /** - * Compute modular exponentiation using square and multiply - * @param {BigInteger} e exponent - * @param {BigInteger} n modulo - * @returns {BigInteger} this ** e mod n - */ - modExp(e, n) { - if (n.isZero()) throw Error("Modulo cannot be zero"); - if (n.isOne()) return new BigInteger(0); - if (e.isNegative()) throw Error("Unsopported negative exponent"); - - let exp = e.value; - let x = this.value; - - x %= n.value; - let r = BigInt(1); - while (exp > BigInt(0)) { - const lsb = exp & BigInt(1); - exp >>= BigInt(1); // e / 2 - // Always compute multiplication step, to reduce timing leakage - const rx = (r * x) % n.value; - // Update r only if lsb is 1 (odd exponent) - r = lsb ? rx : r; - x = (x * x) % n.value; // Square - } - return new BigInteger(r); - } - - - /** - * Compute the inverse of this value modulo n - * Note: this and and n must be relatively prime - * @param {BigInteger} n modulo - * @return {BigInteger} x such that this*x = 1 mod n - * @throws {Error} if the inverse does not exist - */ - modInv(n) { - const { gcd, x } = this._egcd(n); - if (!gcd.isOne()) { - throw new Error('Inverse does not exist'); - } - return x.add(n).mod(n); - } - - /** - * Extended Eucleadian algorithm (http://anh.cs.luc.edu/331/notes/xgcd.pdf) - * Given a = this and b, compute (x, y) such that ax + by = gdc(a, b) - * @param {BigInteger} b second operand - * @returns { gcd, x, y: BigInteger } - */ - _egcd(b) { - let x = BigInt(0); - let y = BigInt(1); - let xPrev = BigInt(1); - let yPrev = BigInt(0); - - let a = this.value; - b = b.value; - - while (b !== BigInt(0)) { - const q = a / b; - let tmp = x; - x = xPrev - q * x; - xPrev = tmp; - - tmp = y; - y = yPrev - q * y; - yPrev = tmp; - - tmp = b; - b = a % b; - a = tmp; - } - - return { - x: new BigInteger(xPrev), - y: new BigInteger(yPrev), - gcd: new BigInteger(a) - }; - } - - /** - * Compute greatest common divisor between this and n - * @param {BigInteger} b operand - * @return {BigInteger} gcd - */ - gcd(b) { - let a = this.value; - b = b.value; - while (b !== BigInt(0)) { - const tmp = b; - b = a % b; - a = tmp; - } - return new BigInteger(a); - } - - /** - * Shift this to the left by x, in place - * @param {BigInteger} x shift value - */ - ileftShift(x) { - this.value <<= x.value; - return this; - } - - /** - * Shift this to the left by x - * @param {BigInteger} x shift value - * @returns {BigInteger} this << x - */ - leftShift(x) { - return this.clone().ileftShift(x); - } - - /** - * Shift this to the right by x, in place - * @param {BigInteger} x shift value - */ - irightShift(x) { - this.value >>= x.value; - return this; - } - - /** - * Shift this to the right by x - * @param {BigInteger} x shift value - * @returns {BigInteger} this >> x - */ - rightShift(x) { - return this.clone().irightShift(x); - } - - /** - * Whether this value is equal to x - * @param {BigInteger} x - * @returns {Boolean} - */ - equal(x) { - return this.value === x.value; - } - - /** - * Whether this value is less than x - * @param {BigInteger} x - * @returns {Boolean} - */ - lt(x) { - return this.value < x.value; - } - - /** - * Whether this value is less than or equal to x - * @param {BigInteger} x - * @returns {Boolean} - */ - lte(x) { - return this.value <= x.value; - } - - /** - * Whether this value is greater than x - * @param {BigInteger} x - * @returns {Boolean} - */ - gt(x) { - return this.value > x.value; - } - - /** - * Whether this value is greater than or equal to x - * @param {BigInteger} x - * @returns {Boolean} - */ - gte(x) { - return this.value >= x.value; - } - - isZero() { - return this.value === BigInt(0); - } - - isOne() { - return this.value === BigInt(1); - } - - isNegative() { - return this.value < BigInt(0); - } - - isEven() { - return !(this.value & BigInt(1)); - } - - abs() { - const res = this.clone(); - if (this.isNegative()) { - res.value = -res.value; - } - return res; - } - - /** - * Get this value as a string - * @returns {String} this value - */ - toString() { - return this.value.toString(); - } - - /** - * Get this value as an exact Number (max 53 bits) - * Fails if this value is too large - * @return {Number} - */ - toNumber() { - const number = Number(this.value); - if (number > Number.MAX_SAFE_INTEGER) { - // We throw and error to conform with the bn.js implementation - throw new Error('Number can only safely store up to 53 bits'); - } - return number; - } - - /** - * Get value of i-th bit - * @param {Number} i bit index - * @returns {Number} bit value - */ - getBit(i) { - const bit = (this.value >> BigInt(i)) & BigInt(1); - return (bit === BigInt(0)) ? 0 : 1; - } - - /** - * Compute bit length - * @returns {Number} bit length - */ - bitLength() { - const zero = new BigInteger(0); - const one = new BigInteger(1); - const negOne = new BigInteger(-1); - - // -1n >> -1n is -1n - // 1n >> 1n is 0n - const target = this.isNegative() ? negOne : zero; - let bitlen = 1; - const tmp = this.clone(); - while (!tmp.irightShift(one).equal(target)) { - bitlen++; - } - return bitlen; - } - - /** - * Compute byte length - * @returns {Number} byte length - */ - byteLength() { - const zero = new BigInteger(0); - const negOne = new BigInteger(-1); - - const target = this.isNegative() ? negOne : zero; - const eight = new BigInteger(8); - let len = 1; - const tmp = this.clone(); - while (!tmp.irightShift(eight).equal(target)) { - len++; - } - return len; - } - - /** - * Get Uint8Array representation of this number - * @param {String} endian endianess of output array (defaults to 'be') - * @param {Number} length of output array - * @return {Uint8Array} - */ - toUint8Array(endian = 'be', length) { - // we get and parse the hex string (https://coolaj86.com/articles/convert-js-bigints-to-typedarrays/) - // this is faster than shift+mod iterations - let hex = this.value.toString(16); - if (hex.length % 2 === 1) { - hex = '0' + hex; - } - - const rawLength = hex.length / 2; - const bytes = new Uint8Array(length || rawLength); - // parse hex - const offset = length ? (length - rawLength) : 0; - let i = 0; - while (i < rawLength) { - bytes[i + offset] = parseInt(hex.slice(2 * i, 2 * i + 2), 16); - i++; - } - - if (endian !== 'be') { - bytes.reverse(); - } - - return bytes; - } -} diff --git a/src/cleartext.js b/src/cleartext.js index c4fea507..8d8b57a6 100644 --- a/src/cleartext.js +++ b/src/cleartext.js @@ -24,134 +24,125 @@ * @module cleartext */ -import { armor, unarmor } from './encoding/armor'; +import armor from './encoding/armor'; import enums from './enums'; import util from './util'; -import { PacketList, LiteralDataPacket, SignaturePacket } from './packet'; +import packet from './packet'; import { Signature } from './signature'; import { createVerificationObjects, createSignaturePackets } from './message'; /** - * Class that represents an OpenPGP cleartext signed message. + * @class + * @classdesc Class that represents an OpenPGP cleartext signed message. * See {@link https://tools.ietf.org/html/rfc4880#section-7} + * @param {String} text The cleartext of the signed message + * @param {module:signature.Signature} signature The detached signature or an empty signature for unsigned messages */ -export class CleartextMessage { - /** - * @param {String} text The cleartext of the signed message - * @param {module:signature.Signature} signature The detached signature or an empty signature for unsigned messages - */ - constructor(text, signature) { - // normalize EOL to canonical form - this.text = util.removeTrailingSpaces(text).replace(/\r?\n/g, '\r\n'); - if (signature && !(signature instanceof Signature)) { - throw new Error('Invalid signature input'); - } - this.signature = signature || new Signature(new PacketList()); +export function CleartextMessage(text, signature) { + if (!(this instanceof CleartextMessage)) { + return new CleartextMessage(text, signature); } - - /** - * Returns the key IDs of the keys that signed the cleartext message - * @returns {Array} array of keyid objects - */ - getSigningKeyIds() { - const keyIds = []; - const signatureList = this.signature.packets; - signatureList.forEach(function(packet) { - keyIds.push(packet.issuerKeyId); - }); - return keyIds; + // normalize EOL to canonical form + this.text = util.removeTrailingSpaces(text).replace(/\r?\n/g, '\r\n'); + if (signature && !(signature instanceof Signature)) { + throw new Error('Invalid signature input'); } + this.signature = signature || new Signature(new packet.List()); +} - /** - * Sign the cleartext message - * @param {Array} privateKeys private keys with decrypted secret key data for signing - * @param {Signature} signature (optional) any existing detached signature - * @param {Date} date (optional) The creation time of the signature that should be created - * @param {Array} userIds (optional) user IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }] - * @returns {Promise} new cleartext message with signed content - * @async - */ - async sign(privateKeys, signature = null, date = new Date(), userIds = []) { - return new CleartextMessage(this.text, await this.signDetached(privateKeys, signature, date, userIds)); - } +/** + * Returns the key IDs of the keys that signed the cleartext message + * @returns {Array} array of keyid objects + */ +CleartextMessage.prototype.getSigningKeyIds = function() { + const keyIds = []; + const signatureList = this.signature.packets; + signatureList.forEach(function(packet) { + keyIds.push(packet.issuerKeyId); + }); + return keyIds; +}; - /** - * Sign the cleartext message - * @param {Array} privateKeys private keys with decrypted secret key data for signing - * @param {Signature} signature (optional) any existing detached signature - * @param {Date} date (optional) The creation time of the signature that should be created - * @param {Array} userIds (optional) user IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }] - * @returns {Promise} new detached signature of message content - * @async - */ - async signDetached(privateKeys, signature = null, date = new Date(), userIds = []) { - const literalDataPacket = new LiteralDataPacket(); - literalDataPacket.setText(this.text); - - return new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, date, userIds, true)); - } +/** + * Sign the cleartext message + * @param {Array} privateKeys private keys with decrypted secret key data for signing + * @param {Signature} signature (optional) any existing detached signature + * @param {Date} date (optional) The creation time of the signature that should be created + * @param {Array} userIds (optional) user IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }] + * @returns {Promise} new cleartext message with signed content + * @async + */ +CleartextMessage.prototype.sign = async function(privateKeys, signature = null, date = new Date(), userIds = []) { + return new CleartextMessage(this.text, await this.signDetached(privateKeys, signature, date, userIds)); +}; - /** - * Verify signatures of cleartext signed message - * @param {Array} keys array of keys to verify signatures - * @param {Date} date (optional) Verify the signature against the given date, i.e. check signature creation time < date < expiration time - * @returns {Promise>} list of signer's keyid and validity of signature - * @async - */ - verify(keys, date = new Date()) { - return this.verifyDetached(this.signature, keys, date); - } +/** + * Sign the cleartext message + * @param {Array} privateKeys private keys with decrypted secret key data for signing + * @param {Signature} signature (optional) any existing detached signature + * @param {Date} date (optional) The creation time of the signature that should be created + * @param {Array} userIds (optional) user IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }] + * @returns {Promise} new detached signature of message content + * @async + */ +CleartextMessage.prototype.signDetached = async function(privateKeys, signature = null, date = new Date(), userIds = []) { + const literalDataPacket = new packet.Literal(); + literalDataPacket.setText(this.text); - /** - * Verify signatures of cleartext signed message - * @param {Array} keys array of keys to verify signatures - * @param {Date} date (optional) Verify the signature against the given date, i.e. check signature creation time < date < expiration time - * @returns {Promise>} list of signer's keyid and validity of signature - * @async - */ - verifyDetached(signature, keys, date = new Date()) { - const signatureList = signature.packets; - const literalDataPacket = new LiteralDataPacket(); - // we assume that cleartext signature is generated based on UTF8 cleartext - literalDataPacket.setText(this.text); - return createVerificationObjects(signatureList, [literalDataPacket], keys, date, true); - } + return new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, date, userIds, true)); +}; - /** - * Get cleartext - * @returns {String} cleartext of message - */ - getText() { - // normalize end of line to \n - return this.text.replace(/\r\n/g, '\n'); - } +/** + * Verify signatures of cleartext signed message + * @param {Array} keys array of keys to verify signatures + * @param {Date} date (optional) Verify the signature against the given date, i.e. check signature creation time < date < expiration time + * @returns {Promise>} list of signer's keyid and validity of signature + * @async + */ +CleartextMessage.prototype.verify = function(keys, date = new Date()) { + return this.verifyDetached(this.signature, keys, date); +}; - /** - * Returns ASCII armored text of cleartext signed message - * @returns {String | ReadableStream} ASCII armor - */ - armor() { - let hashes = this.signature.packets.map(function(packet) { - return enums.read(enums.hash, packet.hashAlgorithm).toUpperCase(); - }); - hashes = hashes.filter(function(item, i, ar) { return ar.indexOf(item) === i; }); - const body = { - hash: hashes.join(), - text: this.text, - data: this.signature.packets.write() - }; - return armor(enums.armor.signed, body); - } +/** + * Verify signatures of cleartext signed message + * @param {Array} keys array of keys to verify signatures + * @param {Date} date (optional) Verify the signature against the given date, i.e. check signature creation time < date < expiration time + * @returns {Promise>} list of signer's keyid and validity of signature + * @async + */ +CleartextMessage.prototype.verifyDetached = function(signature, keys, date = new Date()) { + const signatureList = signature.packets; + const literalDataPacket = new packet.Literal(); + // we assume that cleartext signature is generated based on UTF8 cleartext + literalDataPacket.setText(this.text); + return createVerificationObjects(signatureList, [literalDataPacket], keys, date, true); +}; - /** - * Creates a new CleartextMessage object from text - * @param {String} text - * @static - */ - static fromText(text) { - return new CleartextMessage(text); - } -} +/** + * Get cleartext + * @returns {String} cleartext of message + */ +CleartextMessage.prototype.getText = function() { + // normalize end of line to \n + return this.text.replace(/\r\n/g, '\n'); +}; + +/** + * Returns ASCII armored text of cleartext signed message + * @returns {String | ReadableStream} ASCII armor + */ +CleartextMessage.prototype.armor = function() { + let hashes = this.signature.packets.map(function(packet) { + return enums.read(enums.hash, packet.hashAlgorithm).toUpperCase(); + }); + hashes = hashes.filter(function(item, i, ar) { return ar.indexOf(item) === i; }); + const body = { + hash: hashes.join(), + text: this.text, + data: this.signature.packets.write() + }; + return armor.encode(enums.armor.signed, body); +}; /** @@ -161,13 +152,13 @@ export class CleartextMessage { * @async * @static */ -export async function readArmoredCleartextMessage(armoredText) { - const input = await unarmor(armoredText); +export async function readArmored(armoredText) { + const input = await armor.decode(armoredText); if (input.type !== enums.armor.signed) { throw new Error('No cleartext signed message.'); } - const packetlist = new PacketList(); - await packetlist.read(input.data, { SignaturePacket }); + const packetlist = new packet.List(); + await packetlist.read(input.data); verifyHeaders(input.headers, packetlist); const signature = new Signature(packetlist); return new CleartextMessage(input.text, signature); @@ -176,7 +167,7 @@ export async function readArmoredCleartextMessage(armoredText) { /** * Compare hash algorithm specified in the armor header with signatures * @param {Array} headers Armor headers - * @param {PacketList} packetlist The packetlist with signature packets + * @param {module:packet.List} packetlist The packetlist with signature packets * @private */ function verifyHeaders(headers, packetlist) { @@ -218,3 +209,12 @@ function verifyHeaders(headers, packetlist) { throw new Error('Hash algorithm mismatch in armor header and signature'); } } + +/** + * Creates a new CleartextMessage object from text + * @param {String} text + * @static + */ +export function fromText(text) { + return new CleartextMessage(text); +} diff --git a/src/config/config.js b/src/config/config.js index 4cfaf95b..9d90747e 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -25,14 +25,14 @@ import enums from '../enums'; export default { /** * @memberof module:config - * @property {Integer} preferHashAlgorithm Default hash algorithm {@link module:enums.hash} + * @property {Integer} prefer_hash_algorithm Default hash algorithm {@link module:enums.hash} */ - preferHashAlgorithm: enums.hash.sha256, + prefer_hash_algorithm: enums.hash.sha256, /** * @memberof module:config - * @property {Integer} encryptionCipher Default encryption cipher {@link module:enums.symmetric} + * @property {Integer} encryption_cipher Default encryption cipher {@link module:enums.symmetric} */ - encryptionCipher: enums.symmetric.aes256, + encryption_cipher: enums.symmetric.aes256, /** * @memberof module:config * @property {Integer} compression Default compression algorithm {@link module:enums.compression} @@ -40,178 +40,193 @@ export default { compression: enums.compression.uncompressed, /** * @memberof module:config - * @property {Integer} deflateLevel Default zip/zlib compression level, between 1 and 9 + * @property {Integer} deflate_level Default zip/zlib compression level, between 1 and 9 */ - deflateLevel: 6, + deflate_level: 6, /** * Use Authenticated Encryption with Additional Data (AEAD) protection for symmetric encryption. - * Note: not all OpenPGP implementations are compatible with this option. + * **NOT INTEROPERABLE WITH OTHER OPENPGP IMPLEMENTATIONS** * **FUTURE OPENPGP.JS VERSIONS MAY BREAK COMPATIBILITY WHEN USING THIS OPTION** * @see {@link https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-07|RFC4880bis-07} * @memberof module:config - * @property {Boolean} aeadProtect + * @property {Boolean} aead_protect */ - aeadProtect: false, + aead_protect: false, /** * Default Authenticated Encryption with Additional Data (AEAD) encryption mode - * Only has an effect when aeadProtect is set to true. + * Only has an effect when aead_protect is set to true. * @memberof module:config - * @property {Integer} aeadMode Default AEAD mode {@link module:enums.aead} + * @property {Integer} aead_mode Default AEAD mode {@link module:enums.aead} */ - aeadMode: enums.aead.eax, + aead_mode: enums.aead.eax, /** * Chunk Size Byte for Authenticated Encryption with Additional Data (AEAD) mode - * Only has an effect when aeadProtect is set to true. + * Only has an effect when aead_protect is set to true. * Must be an integer value from 0 to 56. * @memberof module:config - * @property {Integer} aeadChunkSizeByte + * @property {Integer} aead_chunk_size_byte */ - aeadChunkSizeByte: 12, + aead_chunk_size_byte: 12, /** * Use V5 keys. - * Note: not all OpenPGP implementations are compatible with this option. + * **NOT INTEROPERABLE WITH OTHER OPENPGP IMPLEMENTATIONS** * **FUTURE OPENPGP.JS VERSIONS MAY BREAK COMPATIBILITY WHEN USING THIS OPTION** * @memberof module:config - * @property {Boolean} v5Keys + * @property {Boolean} v5_keys */ - v5Keys: false, + v5_keys: false, /** * {@link https://tools.ietf.org/html/rfc4880#section-3.7.1.3|RFC4880 3.7.1.3}: * Iteration Count Byte for S2K (String to Key) * @memberof module:config - * @property {Integer} s2kIterationCountByte + * @property {Integer} s2k_iteration_count_byte */ - s2kIterationCountByte: 224, + s2k_iteration_count_byte: 224, /** Use integrity protection for symmetric encryption * @memberof module:config - * @property {Boolean} integrityProtect + * @property {Boolean} integrity_protect */ - integrityProtect: true, + integrity_protect: true, /** * @memberof module:config - * @property {Boolean} ignoreMdcError Fail on decrypt if message is not integrity protected + * @property {Boolean} ignore_mdc_error Fail on decrypt if message is not integrity protected */ - ignoreMdcError: false, + ignore_mdc_error: false, /** * @memberof module:config - * @property {Boolean} allowUnauthenticatedStream Stream unauthenticated data before integrity has been checked + * @property {Boolean} allow_unauthenticated_stream Stream unauthenticated data before integrity has been checked */ - allowUnauthenticatedStream: false, + allow_unauthenticated_stream: false, /** * @memberof module:config - * @property {Boolean} checksumRequired Do not throw error when armor is missing a checksum + * @property {Boolean} checksum_required Do not throw error when armor is missing a checksum */ - checksumRequired: false, + checksum_required: false, /** * @memberof module:config - * @property {Boolean} rsaBlinding + * @property {Boolean} rsa_blinding */ - rsaBlinding: true, - /** - * @memberof module:config - * @property {Number} minRsaBits Minimum RSA key size allowed for key generation - */ - minRsaBits: 2048, + rsa_blinding: true, /** * Work-around for rare GPG decryption bug when encrypting with multiple passwords. * **Slower and slightly less secure** * @memberof module:config - * @property {Boolean} passwordCollisionCheck + * @property {Boolean} password_collision_check */ - passwordCollisionCheck: false, + password_collision_check: false, /** * @memberof module:config - * @property {Boolean} revocationsExpire If true, expired revocation signatures are ignored + * @property {Boolean} revocations_expire If true, expired revocation signatures are ignored */ - revocationsExpire: false, + revocations_expire: false, /** * Allow decryption using RSA keys without `encrypt` flag. * This setting is potentially insecure, but it is needed to get around an old openpgpjs bug * where key flags were ignored when selecting a key for encryption. * @memberof module:config - * @property {Boolean} allowInsecureDecryptionWithSigningKeys + * @property {Boolean} allow_insecure_decryption_with_signing_keys */ - allowInsecureDecryptionWithSigningKeys: false, + allow_insecure_decryption_with_signing_keys: false, /** * @memberof module:config - * @property {Boolean} useNative Use native Node.js crypto/zlib and WebCrypto APIs when available + * @property {Boolean} use_native Use native Node.js crypto/zlib and WebCrypto APIs when available + */ + use_native: true, + /** + * @memberof module:config + * @property {Integer} min_bytes_for_web_crypto The minimum amount of bytes for which to use native WebCrypto APIs when available */ - useNative: true, + min_bytes_for_web_crypto: 1000, /** * @memberof module:config - * @property {Integer} minBytesForWebCrypto The minimum amount of bytes for which to use native WebCrypto APIs when available + * @property {Boolean} Use transferable objects between the Web Worker and main thread */ - minBytesForWebCrypto: 1000, + zero_copy: false, /** * @memberof module:config * @property {Boolean} debug If enabled, debug messages will be printed */ - debug: false, + debug: false, /** * @memberof module:config * @property {Boolean} tolerant Ignore unsupported/unrecognizable packets instead of throwing an error */ - tolerant: true, + tolerant: true, /** * @memberof module:config - * @property {Boolean} showVersion Whether to include {@link module:config/config.versionString} in armored messages + * @property {Boolean} show_version Whether to include {@link module:config/config.versionstring} in armored messages */ - showVersion: false, + show_version: true, /** * @memberof module:config - * @property {Boolean} showComment Whether to include {@link module:config/config.commentString} in armored messages + * @property {Boolean} show_comment Whether to include {@link module:config/config.commentstring} in armored messages */ - showComment: false, + show_comment: true, /** * @memberof module:config - * @property {String} versionString A version string to be included in armored messages + * @property {String} versionstring A version string to be included in armored messages */ - versionString: "OpenPGP.js VERSION", + versionstring: "OpenPGP.js VERSION", /** * @memberof module:config - * @property {String} commentString A comment string to be included in armored messages + * @property {String} commentstring A comment string to be included in armored messages */ - commentString: "https://openpgpjs.org", + commentstring: "https://openpgpjs.org", /** * @memberof module:config * @property {String} keyserver */ - keyserver: "https://keyserver.ubuntu.com", + keyserver: "https://keyserver.ubuntu.com", /** * @memberof module:config - * @property {String} nodeStore + * @property {String} node_store */ - nodeStore: "./openpgp.store", + node_store: "./openpgp.store", /** * Max userid string length (used for parsing) * @memberof module:config - * @property {Integer} maxUseridLength + * @property {Integer} max_userid_length */ - maxUseridLength: 1024 * 5, + max_userid_length: 1024 * 5, /** * Contains notatations that are considered "known". Known notations do not trigger * validation error when the notation is marked as critical. * @memberof module:config - * @property {Array} knownNotations + * @property {Array} known_notations + */ + known_notations: ["preferred-email-encoding@pgp.com", "pka-address@gnupg.org"], + /** + * @memberof module:config + * @property {Boolean} use_indutny_elliptic Whether to use the indutny/elliptic library. When false, certain curves will not be supported. + */ + use_indutny_elliptic: true, + /** + * @memberof module:config + * @property {Boolean} external_indutny_elliptic Whether to lazily load the indutny/elliptic library from an external path on demand. + */ + external_indutny_elliptic: false, + /** + * @memberof module:config + * @property {String} indutny_elliptic_path The path to load the indutny/elliptic library from. Only has an effect if `config.external_indutny_elliptic` is true. */ - knownNotations: ["preferred-email-encoding@pgp.com", "pka-address@gnupg.org"], + indutny_elliptic_path: './elliptic.min.js', /** * @memberof module:config - * @property {Boolean} useIndutnyElliptic Whether to use the indutny/elliptic library. When false, certain curves will not be supported. + * @property {Object} indutny_elliptic_fetch_options Options object to pass to `fetch` when loading the indutny/elliptic library. Only has an effect if `config.external_indutny_elliptic` is true. */ - useIndutnyElliptic: true, + indutny_elliptic_fetch_options: {}, /** * @memberof module:config * @property {Set} reject_hash_algorithms Reject insecure hash algorithms {@link module:enums.hash} */ - rejectHashAlgorithms: new globalThis.Set([enums.hash.md5, enums.hash.ripemd]), + reject_hash_algorithms: new global.Set([enums.hash.md5, enums.hash.ripemd]), /** * @memberof module:config * @property {Set} reject_message_hash_algorithms Reject insecure message hash algorithms {@link module:enums.hash} */ - rejectMessageHashAlgorithms: new globalThis.Set([enums.hash.md5, enums.hash.ripemd, enums.hash.sha1]) + reject_message_hash_algorithms: new global.Set([enums.hash.md5, enums.hash.ripemd, enums.hash.sha1]) }; diff --git a/src/config/localStorage.js b/src/config/localStorage.js index f3ae4e16..fb6e20f0 100644 --- a/src/config/localStorage.js +++ b/src/config/localStorage.js @@ -5,30 +5,31 @@ /** * This object is used for storing and retrieving configuration from HTML5 local storage. + * @constructor */ -class LocalStorage { - /** - * Reads the config out of the HTML5 local storage - * and initializes the object config. - * if config is null the default config will be used - */ - read() { - const raw = globalThis.localStorage.getItem("config"); - const cf = (raw === null ? null : JSON.parse(raw)); - if (cf === null) { - this.config = this.default_config; - this.write(); - } else { - this.config = cf; - } - } +function LocalStorage() {} - /** - * Writes the config to HTML5 local storage - */ - write() { - globalThis.localStorage.setItem("config", JSON.stringify(this.config)); +/** + * Reads the config out of the HTML5 local storage + * and initializes the object config. + * if config is null the default config will be used + */ +LocalStorage.prototype.read = function () { + const raw = global.localStorage.getItem("config"); + const cf = (raw === null ? null : JSON.parse(raw)); + if (cf === null) { + this.config = this.default_config; + this.write(); + } else { + this.config = cf; } -} +}; + +/** + * Writes the config to HTML5 local storage + */ +LocalStorage.prototype.write = function () { + global.localStorage.setItem("config", JSON.stringify(this.config)); +}; export default LocalStorage; diff --git a/src/crypto/aes_kw.js b/src/crypto/aes_kw.js index 14d5839c..8588a0a1 100644 --- a/src/crypto/aes_kw.js +++ b/src/crypto/aes_kw.js @@ -23,17 +23,10 @@ * @module crypto/aes_kw */ -import * as cipher from './cipher'; +import cipher from './cipher'; import util from '../util'; -/** - * AES key wrap - * @function - * @param {Uint8Array} key - * @param {Uint8Array} data - * @returns {Uint8Array} - */ -export function wrap(key, data) { +function wrap(key, data) { const aes = new cipher["aes" + (key.length * 8)](key); const IV = new Uint32Array([0xA6A6A6A6, 0xA6A6A6A6]); const P = unpack(data); @@ -65,15 +58,7 @@ export function wrap(key, data) { return pack(A, R); } -/** - * AES key unwrap - * @function - * @param {String} key - * @param {String} data - * @returns {Uint8Array} - * @throws {Error} - */ -export function unwrap(key, data) { +function unwrap(key, data) { const aes = new cipher["aes" + (key.length * 8)](key); const IV = new Uint32Array([0xA6A6A6A6, 0xA6A6A6A6]); const C = unpack(data); @@ -146,3 +131,23 @@ function pack() { } return new Uint8Array(buffer); } + +export default { + /** + * AES key wrap + * @function + * @param {String} key + * @param {String} data + * @returns {Uint8Array} + */ + wrap, + /** + * AES key unwrap + * @function + * @param {String} key + * @param {String} data + * @returns {Uint8Array} + * @throws {Error} + */ + unwrap +}; diff --git a/src/crypto/cfb.js b/src/crypto/cfb.js index 67a3289d..623ddb49 100644 --- a/src/crypto/cfb.js +++ b/src/crypto/cfb.js @@ -24,10 +24,10 @@ * @module crypto/cfb */ -import { AES_CFB } from 'asmcrypto.js/dist_es8/aes/cfb'; +import { AES_CFB } from 'asmcrypto.js/dist_es5/aes/cfb'; import stream from 'web-stream-tools'; -import * as cipher from './cipher'; +import cipher from './cipher'; import config from '../config'; import util from '../util'; @@ -38,6 +38,7 @@ const Buffer = util.getNodeBuffer(); const knownAlgos = nodeCrypto ? nodeCrypto.getCiphers() : []; const nodeAlgos = { idea: knownAlgos.includes('idea-cfb') ? 'idea-cfb' : undefined, /* Unused, not implemented */ + '3des': knownAlgos.includes('des-ede3-cfb') ? 'des-ede3-cfb' : undefined, tripledes: knownAlgos.includes('des-ede3-cfb') ? 'des-ede3-cfb' : undefined, cast5: knownAlgos.includes('cast5-cfb') ? 'cast5-cfb' : undefined, blowfish: knownAlgos.includes('bf-cfb') ? 'bf-cfb' : undefined, @@ -47,90 +48,92 @@ const nodeAlgos = { /* twofish is not implemented in OpenSSL */ }; -export async function encrypt(algo, key, plaintext, iv) { - if (util.getNodeCrypto() && nodeAlgos[algo]) { // Node crypto library. - return nodeEncrypt(algo, key, plaintext, iv); - } - if (algo.substr(0, 3) === 'aes') { - return aesEncrypt(algo, key, plaintext, iv); - } +export default { + encrypt: function(algo, key, plaintext, iv) { + if (util.getNodeCrypto() && nodeAlgos[algo]) { // Node crypto library. + return nodeEncrypt(algo, key, plaintext, iv); + } + if (algo.substr(0, 3) === 'aes') { + return aesEncrypt(algo, key, plaintext, iv); + } - const cipherfn = new cipher[algo](key); - const block_size = cipherfn.blockSize; + const cipherfn = new cipher[algo](key); + const block_size = cipherfn.blockSize; - const blockc = iv.slice(); - let pt = new Uint8Array(); - const process = chunk => { - if (chunk) { - pt = util.concatUint8Array([pt, chunk]); - } - const ciphertext = new Uint8Array(pt.length); - let i; - let j = 0; - while (chunk ? pt.length >= block_size : pt.length) { - const encblock = cipherfn.encrypt(blockc); - for (i = 0; i < block_size; i++) { - blockc[i] = pt[i] ^ encblock[i]; - ciphertext[j++] = blockc[i]; + const blockc = iv.slice(); + let pt = new Uint8Array(); + const process = chunk => { + if (chunk) { + pt = util.concatUint8Array([pt, chunk]); } - pt = pt.subarray(block_size); + const ciphertext = new Uint8Array(pt.length); + let i; + let j = 0; + while (chunk ? pt.length >= block_size : pt.length) { + const encblock = cipherfn.encrypt(blockc); + for (i = 0; i < block_size; i++) { + blockc[i] = pt[i] ^ encblock[i]; + ciphertext[j++] = blockc[i]; + } + pt = pt.subarray(block_size); + } + return ciphertext.subarray(0, j); + }; + return stream.transform(plaintext, process, process); + }, + + decrypt: async function(algo, key, ciphertext, iv) { + if (util.getNodeCrypto() && nodeAlgos[algo]) { // Node crypto library. + return nodeDecrypt(algo, key, ciphertext, iv); + } + if (algo.substr(0, 3) === 'aes') { + return aesDecrypt(algo, key, ciphertext, iv); } - return ciphertext.subarray(0, j); - }; - return stream.transform(plaintext, process, process); -} - -export async function decrypt(algo, key, ciphertext, iv) { - if (util.getNodeCrypto() && nodeAlgos[algo]) { // Node crypto library. - return nodeDecrypt(algo, key, ciphertext, iv); - } - if (algo.substr(0, 3) === 'aes') { - return aesDecrypt(algo, key, ciphertext, iv); - } - const cipherfn = new cipher[algo](key); - const block_size = cipherfn.blockSize; + const cipherfn = new cipher[algo](key); + const block_size = cipherfn.blockSize; - let blockp = iv; - let ct = new Uint8Array(); - const process = chunk => { - if (chunk) { - ct = util.concatUint8Array([ct, chunk]); - } - const plaintext = new Uint8Array(ct.length); - let i; - let j = 0; - while (chunk ? ct.length >= block_size : ct.length) { - const decblock = cipherfn.encrypt(blockp); - blockp = ct; - for (i = 0; i < block_size; i++) { - plaintext[j++] = blockp[i] ^ decblock[i]; + let blockp = iv; + let ct = new Uint8Array(); + const process = chunk => { + if (chunk) { + ct = util.concatUint8Array([ct, chunk]); } - ct = ct.subarray(block_size); - } - return plaintext.subarray(0, j); - }; - return stream.transform(ciphertext, process, process); -} + const plaintext = new Uint8Array(ct.length); + let i; + let j = 0; + while (chunk ? ct.length >= block_size : ct.length) { + const decblock = cipherfn.encrypt(blockp); + blockp = ct; + for (i = 0; i < block_size; i++) { + plaintext[j++] = blockp[i] ^ decblock[i]; + } + ct = ct.subarray(block_size); + } + return plaintext.subarray(0, j); + }; + return stream.transform(ciphertext, process, process); + } +}; function aesEncrypt(algo, key, pt, iv) { if ( util.getWebCrypto() && key.length !== 24 && // Chrome doesn't support 192 bit keys, see https://www.chromium.org/blink/webcrypto#TOC-AES-support !util.isStream(pt) && - pt.length >= 3000 * config.minBytesForWebCrypto // Default to a 3MB minimum. Chrome is pretty slow for small messages, see: https://bugs.chromium.org/p/chromium/issues/detail?id=701188#c2 + pt.length >= 3000 * config.min_bytes_for_web_crypto // Default to a 3MB minimum. Chrome is pretty slow for small messages, see: https://bugs.chromium.org/p/chromium/issues/detail?id=701188#c2 ) { // Web Crypto return webEncrypt(algo, key, pt, iv); } // asm.js fallback const cfb = new AES_CFB(key, iv); - return stream.transform(pt, value => cfb.aes.AES_Encrypt_process(value), () => cfb.aes.AES_Encrypt_finish()); + return stream.transform(pt, value => cfb.AES_Encrypt_process(value), () => cfb.AES_Encrypt_finish()); } function aesDecrypt(algo, key, ct, iv) { if (util.isStream(ct)) { const cfb = new AES_CFB(key, iv); - return stream.transform(ct, value => cfb.aes.AES_Decrypt_process(value), () => cfb.aes.AES_Decrypt_finish()); + return stream.transform(ct, value => cfb.AES_Decrypt_process(value), () => cfb.AES_Decrypt_finish()); } return AES_CFB.decrypt(ct, key, iv); } diff --git a/src/crypto/cipher/aes.js b/src/crypto/cipher/aes.js index ff389480..9a82dbb8 100644 --- a/src/crypto/cipher/aes.js +++ b/src/crypto/cipher/aes.js @@ -2,7 +2,7 @@ * @requires asmcrypto.js */ -import { AES_ECB } from 'asmcrypto.js/dist_es8/aes/ecb'; +import { AES_ECB } from 'asmcrypto.js/dist_es5/aes/ecb'; // TODO use webCrypto or nodeCrypto when possible. function aes(length) { diff --git a/src/crypto/cipher/des.js b/src/crypto/cipher/des.js index 7c335d34..2cd394e4 100644 --- a/src/crypto/cipher/des.js +++ b/src/crypto/cipher/des.js @@ -432,7 +432,7 @@ function des_removePadding(message, padding) { // added by Recurity Labs -export function TripleDES(key) { +function TripleDES(key) { this.key = []; for (let i = 0; i < 3; i++) { @@ -459,7 +459,7 @@ TripleDES.blockSize = TripleDES.prototype.blockSize = 8; // This is "original" DES -export function DES(key) { +function DES(key) { this.key = key; this.encrypt = function(block, padding) { @@ -472,3 +472,5 @@ export function DES(key) { return des(keys, block, false, 0, null, padding); }; } + +export default { DES, TripleDES }; diff --git a/src/crypto/cipher/index.js b/src/crypto/cipher/index.js index daf3f222..7ae8a06c 100644 --- a/src/crypto/cipher/index.js +++ b/src/crypto/cipher/index.js @@ -9,80 +9,83 @@ */ import aes from './aes'; -import { DES, TripleDES } from './des.js'; -import Cast5 from './cast5'; -import TF from './twofish'; -import BF from './blowfish'; +import des from './des.js'; +import cast5 from './cast5'; +import twofish from './twofish'; +import blowfish from './blowfish'; -/** - * AES-128 encryption and decryption (ID 7) - * @function - * @param {String} key 128-bit key - * @see {@link https://github.com/asmcrypto/asmcrypto.js|asmCrypto} - * @see {@link https://csrc.nist.gov/publications/fips/fips197/fips-197.pdf|NIST FIPS-197} - * @returns {Object} - * @requires asmcrypto.js - */ -export const aes128 = aes(128); -/** - * AES-128 Block Cipher (ID 8) - * @function - * @param {String} key 192-bit key - * @see {@link https://github.com/asmcrypto/asmcrypto.js|asmCrypto} - * @see {@link https://csrc.nist.gov/publications/fips/fips197/fips-197.pdf|NIST FIPS-197} - * @returns {Object} - * @requires asmcrypto.js - */ -export const aes192 = aes(192); -/** - * AES-128 Block Cipher (ID 9) - * @function - * @param {String} key 256-bit key - * @see {@link https://github.com/asmcrypto/asmcrypto.js|asmCrypto} - * @see {@link https://csrc.nist.gov/publications/fips/fips197/fips-197.pdf|NIST FIPS-197} - * @returns {Object} - * @requires asmcrypto.js - */ -export const aes256 = aes(256); -// Not in OpenPGP specifications -export const des = DES; -/** - * Triple DES Block Cipher (ID 2) - * @function - * @param {String} key 192-bit key - * @see {@link https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-67r2.pdf|NIST SP 800-67} - * @returns {Object} - */ -export const tripledes = TripleDES; -/** - * CAST-128 Block Cipher (ID 3) - * @function - * @param {String} key 128-bit key - * @see {@link https://tools.ietf.org/html/rfc2144|The CAST-128 Encryption Algorithm} - * @returns {Object} - */ -export const cast5 = Cast5; -/** - * Twofish Block Cipher (ID 10) - * @function - * @param {String} key 256-bit key - * @see {@link https://tools.ietf.org/html/rfc4880#ref-TWOFISH|TWOFISH} - * @returns {Object} - */ -export const twofish = TF; -/** - * Blowfish Block Cipher (ID 4) - * @function - * @param {String} key 128-bit key - * @see {@link https://tools.ietf.org/html/rfc4880#ref-BLOWFISH|BLOWFISH} - * @returns {Object} - */ -export const blowfish = BF; -/** - * Not implemented - * @function - * @throws {Error} - */ -export const idea = function() { - throw new Error('IDEA symmetric-key algorithm not implemented'); +export default { + /** + * AES-128 encryption and decryption (ID 7) + * @function + * @param {String} key 128-bit key + * @see {@link https://github.com/asmcrypto/asmcrypto.js|asmCrypto} + * @see {@link https://csrc.nist.gov/publications/fips/fips197/fips-197.pdf|NIST FIPS-197} + * @returns {Object} + * @requires asmcrypto.js + */ + aes128: aes(128), + /** + * AES-128 Block Cipher (ID 8) + * @function + * @param {String} key 192-bit key + * @see {@link https://github.com/asmcrypto/asmcrypto.js|asmCrypto} + * @see {@link https://csrc.nist.gov/publications/fips/fips197/fips-197.pdf|NIST FIPS-197} + * @returns {Object} + * @requires asmcrypto.js + */ + aes192: aes(192), + /** + * AES-128 Block Cipher (ID 9) + * @function + * @param {String} key 256-bit key + * @see {@link https://github.com/asmcrypto/asmcrypto.js|asmCrypto} + * @see {@link https://csrc.nist.gov/publications/fips/fips197/fips-197.pdf|NIST FIPS-197} + * @returns {Object} + * @requires asmcrypto.js + */ + aes256: aes(256), + // Not in OpenPGP specifications + des: des.DES, + /** + * Triple DES Block Cipher (ID 2) + * @function + * @param {String} key 192-bit key + * @see {@link https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-67r2.pdf|NIST SP 800-67} + * @returns {Object} + */ + tripledes: des.TripleDES, + '3des': des.TripleDES, + /** + * CAST-128 Block Cipher (ID 3) + * @function + * @param {String} key 128-bit key + * @see {@link https://tools.ietf.org/html/rfc2144|The CAST-128 Encryption Algorithm} + * @returns {Object} + */ + cast5: cast5, + /** + * Twofish Block Cipher (ID 10) + * @function + * @param {String} key 256-bit key + * @see {@link https://tools.ietf.org/html/rfc4880#ref-TWOFISH|TWOFISH} + * @returns {Object} + */ + twofish: twofish, + /** + * Blowfish Block Cipher (ID 4) + * @function + * @param {String} key 128-bit key + * @see {@link https://tools.ietf.org/html/rfc4880#ref-BLOWFISH|BLOWFISH} + * @returns {Object} + */ + blowfish: blowfish, + /** + * Not implemented + * @function + * @throws {Error} + */ + idea: function() { + throw new Error('IDEA symmetric-key algorithm not implemented'); + } }; diff --git a/src/crypto/cmac.js b/src/crypto/cmac.js index 3eda17ea..e6ce66c1 100644 --- a/src/crypto/cmac.js +++ b/src/crypto/cmac.js @@ -6,7 +6,7 @@ * @module crypto/cmac */ -import { AES_CBC } from 'asmcrypto.js/dist_es8/aes/cbc'; +import { AES_CBC } from 'asmcrypto.js/dist_es5/aes/cbc'; import util from '../util'; const webCrypto = util.getWebCrypto(); diff --git a/src/crypto/crypto.js b/src/crypto/crypto.js index f0da2898..5f0bbe1f 100644 --- a/src/crypto/crypto.js +++ b/src/crypto/crypto.js @@ -25,6 +25,7 @@ * @requires crypto/random * @requires type/ecdh_symkey * @requires type/kdf_params + * @requires type/mpi * @requires type/oid * @requires enums * @requires util @@ -32,341 +33,364 @@ */ import publicKey from './public_key'; -import * as cipher from './cipher'; -import { getRandomBytes } from './random'; -import ECDHSymkey from '../type/ecdh_symkey'; -import KDFParams from '../type/kdf_params'; +import cipher from './cipher'; +import random from './random'; +import type_ecdh_symkey from '../type/ecdh_symkey'; +import type_kdf_params from '../type/kdf_params'; +import type_mpi from '../type/mpi'; +import type_oid from '../type/oid'; import enums from '../enums'; import util from '../util'; -import OID from '../type/oid'; -import { Curve } from './public_key/elliptic/curves'; +import pkcs1 from './pkcs1'; +import pkcs5 from './pkcs5'; -/** - * Encrypts data using specified algorithm and public key parameters. - * See {@link https://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1} for public key algorithms. - * @param {module:enums.publicKey} algo Public key algorithm - * @param {Object} publicParams Algorithm-specific public key parameters - * @param {Uint8Array} data Data to be encrypted - * @param {Uint8Array} fingerprint Recipient fingerprint - * @returns {Object} Encrypted session key parameters - * @async - */ -export async function publicKeyEncrypt(algo, publicParams, data, fingerprint) { - switch (algo) { - case enums.publicKey.rsaEncrypt: - case enums.publicKey.rsaEncryptSign: { - const { n, e } = publicParams; - const c = await publicKey.rsa.encrypt(data, n, e); - return { c }; - } - case enums.publicKey.elgamal: { - const { p, g, y } = publicParams; - return publicKey.elgamal.encrypt(data, p, g, y); - } - case enums.publicKey.ecdh: { - const { oid, Q, kdfParams } = publicParams; - const { publicKey: V, wrappedKey: C } = await publicKey.elliptic.ecdh.encrypt( - oid, kdfParams, data, Q, fingerprint); - return { V, C: new ECDHSymkey(C) }; +function constructParams(types, data) { + return types.map(function(type, i) { + if (data && data[i]) { + return new type(data[i]); } - default: - return []; - } + return new type(); + }); } -/** - * Decrypts data using specified algorithm and private key parameters. - * See {@link https://tools.ietf.org/html/rfc4880#section-5.5.3|RFC 4880 5.5.3} - * @param {module:enums.publicKey} algo Public key algorithm - * @param {Object} publicKeyParams Algorithm-specific public key parameters - * @param {Object} privateKeyParams Algorithm-specific private key parameters - * @param {Object} sessionKeyParams Encrypted session key parameters - * @param {Uint8Array} fingerprint Recipient fingerprint - * @returns {Uint8Array} Decrypted data - * @async - */ -export async function publicKeyDecrypt(algo, publicKeyParams, privateKeyParams, sessionKeyParams, fingerprint) { - switch (algo) { - case enums.publicKey.rsaEncryptSign: - case enums.publicKey.rsaEncrypt: { - const { c } = sessionKeyParams; - const { n, e } = publicKeyParams; - const { d, p, q, u } = privateKeyParams; - return publicKey.rsa.decrypt(c, n, e, d, p, q, u); +export default { + /** + * Encrypts data using specified algorithm and public key parameters. + * See {@link https://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1} for public key algorithms. + * @param {module:enums.publicKey} algo Public key algorithm + * @param {Array} pub_params Algorithm-specific public key parameters + * @param {String} data Data to be encrypted + * @param {String} fingerprint Recipient fingerprint + * @returns {Array} encrypted session key parameters + * @async + */ + publicKeyEncrypt: async function(algo, pub_params, data, fingerprint) { + const types = this.getEncSessionKeyParamTypes(algo); + switch (algo) { + case enums.publicKey.rsa_encrypt: + case enums.publicKey.rsa_encrypt_sign: { + data = util.str_to_Uint8Array(data); + const n = pub_params[0].toUint8Array(); + const e = pub_params[1].toUint8Array(); + const res = await publicKey.rsa.encrypt(data, n, e); + return constructParams(types, [res]); + } + case enums.publicKey.elgamal: { + data = new type_mpi(await pkcs1.eme.encode(data, pub_params[0].byteLength())); + const m = data.toBN(); + const p = pub_params[0].toBN(); + const g = pub_params[1].toBN(); + const y = pub_params[2].toBN(); + const res = await publicKey.elgamal.encrypt(m, p, g, y); + return constructParams(types, [res.c1, res.c2]); + } + case enums.publicKey.ecdh: { + data = new type_mpi(pkcs5.encode(data)); + const oid = pub_params[0]; + const Q = pub_params[1].toUint8Array(); + const kdfParams = pub_params[2]; + const { publicKey: V, wrappedKey: C } = await publicKey.elliptic.ecdh.encrypt( + oid, kdfParams, data, Q, fingerprint); + return constructParams(types, [V, C]); + } + default: + return []; } - case enums.publicKey.elgamal: { - const { c1, c2 } = sessionKeyParams; - const p = publicKeyParams.p; - const x = privateKeyParams.x; - return publicKey.elgamal.decrypt(c1, c2, p, x); - } - case enums.publicKey.ecdh: { - const { oid, Q, kdfParams } = publicKeyParams; - const { d } = privateKeyParams; - const { V, C } = sessionKeyParams; - return publicKey.elliptic.ecdh.decrypt( - oid, kdfParams, V, C.data, Q, d, fingerprint); - } - default: - throw new Error('Invalid public key encryption algorithm.'); - } -} + }, -/** - * Parse public key material in binary form to get the key parameters - * @param {module:enums.publicKey} algo The key algorithm - * @param {Uint8Array} bytes The key material to parse - * @returns {{ read: Number, publicParams: Object }} Number of read bytes plus key parameters referenced by name - */ -export function parsePublicKeyParams(algo, bytes) { - let read = 0; - switch (algo) { - case enums.publicKey.rsaEncrypt: - case enums.publicKey.rsaEncryptSign: - case enums.publicKey.rsaSign: { - const n = util.readMPI(bytes.subarray(read)); read += n.length + 2; - const e = util.readMPI(bytes.subarray(read)); read += e.length + 2; - return { read, publicParams: { n, e } }; - } - case enums.publicKey.dsa: { - const p = util.readMPI(bytes.subarray(read)); read += p.length + 2; - const q = util.readMPI(bytes.subarray(read)); read += q.length + 2; - const g = util.readMPI(bytes.subarray(read)); read += g.length + 2; - const y = util.readMPI(bytes.subarray(read)); read += y.length + 2; - return { read, publicParams: { p, q, g, y } }; - } - case enums.publicKey.elgamal: { - const p = util.readMPI(bytes.subarray(read)); read += p.length + 2; - const g = util.readMPI(bytes.subarray(read)); read += g.length + 2; - const y = util.readMPI(bytes.subarray(read)); read += y.length + 2; - return { read, publicParams: { p, g, y } }; + /** + * Decrypts data using specified algorithm and private key parameters. + * See {@link https://tools.ietf.org/html/rfc4880#section-5.5.3|RFC 4880 5.5.3} + * @param {module:enums.publicKey} algo Public key algorithm + * @param {Array} key_params Algorithm-specific public, private key parameters + * @param {Array} + data_params encrypted session key parameters + * @param {String} fingerprint Recipient fingerprint + * @returns {String} String containing the decrypted data + * @async + */ + publicKeyDecrypt: async function(algo, key_params, data_params, fingerprint) { + switch (algo) { + case enums.publicKey.rsa_encrypt_sign: + case enums.publicKey.rsa_encrypt: { + const c = data_params[0].toUint8Array(); + const n = key_params[0].toUint8Array(); // n = pq + const e = key_params[1].toUint8Array(); + const d = key_params[2].toUint8Array(); // de = 1 mod (p-1)(q-1) + const p = key_params[3].toUint8Array(); + const q = key_params[4].toUint8Array(); + const u = key_params[5].toUint8Array(); // p^-1 mod q + return publicKey.rsa.decrypt(c, n, e, d, p, q, u); + } + case enums.publicKey.elgamal: { + const c1 = data_params[0].toBN(); + const c2 = data_params[1].toBN(); + const p = key_params[0].toBN(); + const x = key_params[3].toBN(); + const result = new type_mpi(await publicKey.elgamal.decrypt(c1, c2, p, x)); // MPI and BN.js discard any leading zeros + return pkcs1.eme.decode( + util.Uint8Array_to_str(result.toUint8Array('be', p.byteLength())) // re-introduce leading zeros + ); + } + case enums.publicKey.ecdh: { + const oid = key_params[0]; + const kdfParams = key_params[2]; + const V = data_params[0].toUint8Array(); + const C = data_params[1].data; + const Q = key_params[1].toUint8Array(); + const d = key_params[3].toUint8Array(); + const result = new type_mpi(await publicKey.elliptic.ecdh.decrypt( + oid, kdfParams, V, C, Q, d, fingerprint)); + return pkcs5.decode(result.toString()); + } + default: + throw new Error('Invalid public key encryption algorithm.'); } - case enums.publicKey.ecdsa: { - const oid = new OID(); read += oid.read(bytes); - const Q = util.readMPI(bytes.subarray(read)); read += Q.length + 2; - return { read: read, publicParams: { oid, Q } }; - } - case enums.publicKey.eddsa: { - const oid = new OID(); read += oid.read(bytes); - let Q = util.readMPI(bytes.subarray(read)); read += Q.length + 2; - Q = util.leftPad(Q, 33); - return { read: read, publicParams: { oid, Q } }; - } - case enums.publicKey.ecdh: { - const oid = new OID(); read += oid.read(bytes); - const Q = util.readMPI(bytes.subarray(read)); read += Q.length + 2; - const kdfParams = new KDFParams(); read += kdfParams.read(bytes.subarray(read)); - return { read: read, publicParams: { oid, Q, kdfParams } }; - } - default: - throw new Error('Invalid public key encryption algorithm.'); - } -} + }, -/** - * Parse private key material in binary form to get the key parameters - * @param {module:enums.publicKey} algo The key algorithm - * @param {Uint8Array} bytes The key material to parse - * @param {Object} publicParams (ECC only) public params, needed to format some private params - * @returns {{ read: Number, privateParams: Object }} Number of read bytes plus the key parameters referenced by name - */ -export function parsePrivateKeyParams(algo, bytes, publicParams) { - let read = 0; - switch (algo) { - case enums.publicKey.rsaEncrypt: - case enums.publicKey.rsaEncryptSign: - case enums.publicKey.rsaSign: { - const d = util.readMPI(bytes.subarray(read)); read += d.length + 2; - const p = util.readMPI(bytes.subarray(read)); read += p.length + 2; - const q = util.readMPI(bytes.subarray(read)); read += q.length + 2; - const u = util.readMPI(bytes.subarray(read)); read += u.length + 2; - return { read, privateParams: { d, p, q, u } }; - } - case enums.publicKey.dsa: - case enums.publicKey.elgamal: { - const x = util.readMPI(bytes.subarray(read)); read += x.length + 2; - return { read, privateParams: { x } }; + /** Returns the types comprising the private key of an algorithm + * @param {module:enums.publicKey} algo The public key algorithm + * @returns {Array} The array of types + */ + getPrivKeyParamTypes: function(algo) { + switch (algo) { + // Algorithm-Specific Fields for RSA secret keys: + // - multiprecision integer (MPI) of RSA secret exponent d. + // - MPI of RSA secret prime value p. + // - MPI of RSA secret prime value q (p < q). + // - MPI of u, the multiplicative inverse of p, mod q. + case enums.publicKey.rsa_encrypt: + case enums.publicKey.rsa_encrypt_sign: + case enums.publicKey.rsa_sign: + return [type_mpi, type_mpi, type_mpi, type_mpi]; + // Algorithm-Specific Fields for Elgamal secret keys: + // - MPI of Elgamal secret exponent x. + case enums.publicKey.elgamal: + return [type_mpi]; + // Algorithm-Specific Fields for DSA secret keys: + // - MPI of DSA secret exponent x. + case enums.publicKey.dsa: + return [type_mpi]; + // Algorithm-Specific Fields for ECDSA or ECDH secret keys: + // - MPI of an integer representing the secret key. + case enums.publicKey.ecdh: + case enums.publicKey.ecdsa: + case enums.publicKey.eddsa: + return [type_mpi]; + default: + throw new Error('Invalid public key encryption algorithm.'); } - case enums.publicKey.ecdsa: - case enums.publicKey.ecdh: { - const curve = new Curve(publicParams.oid); - let d = util.readMPI(bytes.subarray(read)); read += d.length + 2; - d = util.leftPad(d, curve.payloadSize); - return { read, privateParams: { d } }; - } - case enums.publicKey.eddsa: { - let seed = util.readMPI(bytes.subarray(read)); read += seed.length + 2; - seed = util.leftPad(seed, 32); - return { read, privateParams: { seed } }; - } - default: - throw new Error('Invalid public key encryption algorithm.'); - } -} + }, -/** Returns the types comprising the encrypted session key of an algorithm - * @param {module:enums.publicKey} algo The key algorithm - * @param {Uint8Array} bytes The key material to parse - * @returns {Object} The session key parameters referenced by name - */ -export function parseEncSessionKeyParams(algo, bytes) { - let read = 0; - switch (algo) { - // Algorithm-Specific Fields for RSA encrypted session keys: - // - MPI of RSA encrypted value m**e mod n. - case enums.publicKey.rsaEncrypt: - case enums.publicKey.rsaEncryptSign: { - const c = util.readMPI(bytes.subarray(read)); - return { c }; + /** Returns the types comprising the public key of an algorithm + * @param {module:enums.publicKey} algo The public key algorithm + * @returns {Array} The array of types + */ + getPubKeyParamTypes: function(algo) { + switch (algo) { + // Algorithm-Specific Fields for RSA public keys: + // - a multiprecision integer (MPI) of RSA public modulus n; + // - an MPI of RSA public encryption exponent e. + case enums.publicKey.rsa_encrypt: + case enums.publicKey.rsa_encrypt_sign: + case enums.publicKey.rsa_sign: + return [type_mpi, type_mpi]; + // Algorithm-Specific Fields for Elgamal public keys: + // - MPI of Elgamal prime p; + // - MPI of Elgamal group generator g; + // - MPI of Elgamal public key value y (= g**x mod p where x is secret). + case enums.publicKey.elgamal: + return [type_mpi, type_mpi, type_mpi]; + // Algorithm-Specific Fields for DSA public keys: + // - MPI of DSA prime p; + // - MPI of DSA group order q (q is a prime divisor of p-1); + // - MPI of DSA group generator g; + // - MPI of DSA public-key value y (= g**x mod p where x is secret). + case enums.publicKey.dsa: + return [type_mpi, type_mpi, type_mpi, type_mpi]; + // Algorithm-Specific Fields for ECDSA/EdDSA public keys: + // - OID of curve; + // - MPI of EC point representing public key. + case enums.publicKey.ecdsa: + case enums.publicKey.eddsa: + return [type_oid, type_mpi]; + // Algorithm-Specific Fields for ECDH public keys: + // - OID of curve; + // - MPI of EC point representing public key. + // - KDF: variable-length field containing KDF parameters. + case enums.publicKey.ecdh: + return [type_oid, type_mpi, type_kdf_params]; + default: + throw new Error('Invalid public key encryption algorithm.'); } + }, + + /** Returns the types comprising the encrypted session key of an algorithm + * @param {module:enums.publicKey} algo The public key algorithm + * @returns {Array} The array of types + */ + getEncSessionKeyParamTypes: function(algo) { + switch (algo) { + // Algorithm-Specific Fields for RSA encrypted session keys: + // - MPI of RSA encrypted value m**e mod n. + case enums.publicKey.rsa_encrypt: + case enums.publicKey.rsa_encrypt_sign: + return [type_mpi]; - // Algorithm-Specific Fields for Elgamal encrypted session keys: - // - MPI of Elgamal value g**k mod p - // - MPI of Elgamal value m * y**k mod p - case enums.publicKey.elgamal: { - const c1 = util.readMPI(bytes.subarray(read)); read += c1.length + 2; - const c2 = util.readMPI(bytes.subarray(read)); - return { c1, c2 }; + // Algorithm-Specific Fields for Elgamal encrypted session keys: + // - MPI of Elgamal value g**k mod p + // - MPI of Elgamal value m * y**k mod p + case enums.publicKey.elgamal: + return [type_mpi, type_mpi]; + // Algorithm-Specific Fields for ECDH encrypted session keys: + // - MPI containing the ephemeral key used to establish the shared secret + // - ECDH Symmetric Key + case enums.publicKey.ecdh: + return [type_mpi, type_ecdh_symkey]; + default: + throw new Error('Invalid public key encryption algorithm.'); } - // Algorithm-Specific Fields for ECDH encrypted session keys: - // - MPI containing the ephemeral key used to establish the shared secret - // - ECDH Symmetric Key - case enums.publicKey.ecdh: { - const V = util.readMPI(bytes.subarray(read)); read += V.length + 2; - const C = new ECDHSymkey(); C.read(bytes.subarray(read)); - return { V, C }; + }, + + /** Generate algorithm-specific key parameters + * @param {module:enums.publicKey} algo The public key algorithm + * @param {Integer} bits Bit length for RSA keys + * @param {module:type/oid} oid Object identifier for ECC keys + * @returns {Array} The array of parameters + * @async + */ + generateParams: function(algo, bits, oid) { + const types = [].concat(this.getPubKeyParamTypes(algo), this.getPrivKeyParamTypes(algo)); + switch (algo) { + case enums.publicKey.rsa_encrypt: + case enums.publicKey.rsa_encrypt_sign: + case enums.publicKey.rsa_sign: { + return publicKey.rsa.generate(bits, "10001").then(function(keyObject) { + return constructParams( + types, [keyObject.n, keyObject.e, keyObject.d, keyObject.p, keyObject.q, keyObject.u] + ); + }); + } + case enums.publicKey.dsa: + case enums.publicKey.elgamal: + throw new Error('Unsupported algorithm for key generation.'); + case enums.publicKey.ecdsa: + case enums.publicKey.eddsa: + return publicKey.elliptic.generate(oid).then(function (keyObject) { + return constructParams(types, [keyObject.oid, keyObject.Q, keyObject.d]); + }); + case enums.publicKey.ecdh: + return publicKey.elliptic.generate(oid).then(function (keyObject) { + return constructParams(types, [ + keyObject.oid, + keyObject.Q, + { hash: keyObject.hash, cipher: keyObject.cipher }, + keyObject.d + ]); + }); + default: + throw new Error('Invalid public key algorithm.'); } - default: - throw new Error('Invalid public key encryption algorithm.'); - } -} + }, -/** - * Convert params to MPI and serializes them in the proper order - * @param {module:enums.publicKey} algo The public key algorithm - * @param {Object} params The key parameters indexed by name - * @returns {Uint8Array} The array containing the MPIs - */ -export function serializeParams(algo, params) { - const orderedParams = Object.keys(params).map(name => { - const param = params[name]; - return util.isUint8Array(param) ? util.uint8ArrayToMpi(param) : param.write(); - }); - return util.concatUint8Array(orderedParams); -} + /** + * Validate algorithm-specific key parameters + * @param {module:enums.publicKey} algo The public key algorithm + * @param {Array} params The array of parameters + * @returns {Promise whether the parameters are valid + * @async + */ + validateParams: async function(algo, params) { + switch (algo) { + case enums.publicKey.rsa_encrypt: + case enums.publicKey.rsa_encrypt_sign: + case enums.publicKey.rsa_sign: { + if (params.length < 6) { + throw new Error('Missing key parameters'); + } + const n = params[0].toUint8Array(); + const e = params[1].toUint8Array(); + const d = params[2].toUint8Array(); + const p = params[3].toUint8Array(); + const q = params[4].toUint8Array(); + const u = params[5].toUint8Array(); + return publicKey.rsa.validateParams(n, e, d, p, q, u); + } + case enums.publicKey.dsa: { + if (params.length < 5) { + throw new Error('Missing key parameters'); + } + const p = params[0].toUint8Array(); + const q = params[1].toUint8Array(); + const g = params[2].toUint8Array(); + const y = params[3].toUint8Array(); + const x = params[4].toUint8Array(); + return publicKey.dsa.validateParams(p, q, g, y, x); + } + case enums.publicKey.elgamal: { + if (params.length < 4) { + throw new Error('Missing key parameters'); + } + const p = params[0].toUint8Array(); + const g = params[1].toUint8Array(); + const y = params[2].toUint8Array(); + const x = params[3].toUint8Array(); + return publicKey.elgamal.validateParams(p, g, y, x); + } + case enums.publicKey.ecdsa: + case enums.publicKey.ecdh: { + const expectedLen = algo === enums.publicKey.ecdh ? 3 : 2; + if (params.length < expectedLen) { + throw new Error('Missing key parameters'); + } -/** - * Generate algorithm-specific key parameters - * @param {module:enums.publicKey} algo The public key algorithm - * @param {Integer} bits Bit length for RSA keys - * @param {module:type/oid} oid Object identifier for ECC keys - * @returns {{ publicParams: {Object}, privateParams: {Object} }} The parameters referenced by name - * @async - */ -export function generateParams(algo, bits, oid) { - switch (algo) { - case enums.publicKey.rsaEncrypt: - case enums.publicKey.rsaEncryptSign: - case enums.publicKey.rsaSign: { - return publicKey.rsa.generate(bits, 65537).then(({ n, e, d, p, q, u }) => ({ - privateParams: { d, p, q, u }, - publicParams: { n, e } - })); - } - case enums.publicKey.ecdsa: - return publicKey.elliptic.generate(oid).then(({ oid, Q, secret }) => ({ - privateParams: { d: secret }, - publicParams: { oid: new OID(oid), Q } - })); - case enums.publicKey.eddsa: - return publicKey.elliptic.generate(oid).then(({ oid, Q, secret }) => ({ - privateParams: { seed: secret }, - publicParams: { oid: new OID(oid), Q } - })); - case enums.publicKey.ecdh: - return publicKey.elliptic.generate(oid).then(({ oid, Q, secret, hash, cipher }) => ({ - privateParams: { d: secret }, - publicParams: { - oid: new OID(oid), - Q, - kdfParams: new KDFParams({ hash, cipher }) + const algoModule = publicKey.elliptic[enums.read(enums.publicKey, algo)]; + const { oid, Q, d } = algoModule.parseParams(params); + return algoModule.validateParams(oid, Q, d); + } + case enums.publicKey.eddsa: { + const expectedLen = 3; + if (params.length < expectedLen) { + throw new Error('Missing key parameters'); } - })); - case enums.publicKey.dsa: - case enums.publicKey.elgamal: - throw new Error('Unsupported algorithm for key generation.'); - default: - throw new Error('Invalid public key algorithm.'); - } -} -/** - * Validate algorithm-specific key parameters - * @param {module:enums.publicKey} algo The public key algorithm - * @param {Object} publicParams Algorithm-specific public key parameters - * @param {Object} privateParams Algorithm-specific private key parameters - * @returns {Promise} Whether the parameters are valid - * @async - */ -export async function validateParams(algo, publicParams, privateParams) { - if (!publicParams || !privateParams) { - throw new Error('Missing key parameters'); - } - switch (algo) { - case enums.publicKey.rsaEncrypt: - case enums.publicKey.rsaEncryptSign: - case enums.publicKey.rsaSign: { - const { n, e } = publicParams; - const { d, p, q, u } = privateParams; - return publicKey.rsa.validateParams(n, e, d, p, q, u); - } - case enums.publicKey.dsa: { - const { p, q, g, y } = publicParams; - const { x } = privateParams; - return publicKey.dsa.validateParams(p, q, g, y, x); - } - case enums.publicKey.elgamal: { - const { p, g, y } = publicParams; - const { x } = privateParams; - return publicKey.elgamal.validateParams(p, g, y, x); - } - case enums.publicKey.ecdsa: - case enums.publicKey.ecdh: { - const algoModule = publicKey.elliptic[enums.read(enums.publicKey, algo)]; - const { oid, Q } = publicParams; - const { d } = privateParams; - return algoModule.validateParams(oid, Q, d); + const { oid, Q, seed } = publicKey.elliptic.eddsa.parseParams(params); + return publicKey.elliptic.eddsa.validateParams(oid, Q, seed); + } + default: + throw new Error('Invalid public key algorithm.'); } - case enums.publicKey.eddsa: { - const { oid, Q } = publicParams; - const { seed } = privateParams; - return publicKey.elliptic.eddsa.validateParams(oid, Q, seed); - } - default: - throw new Error('Invalid public key algorithm.'); - } -} + }, -/** - * Generates a random byte prefix for the specified algorithm - * See {@link https://tools.ietf.org/html/rfc4880#section-9.2|RFC 4880 9.2} for algorithms. - * @param {module:enums.symmetric} algo Symmetric encryption algorithm - * @returns {Uint8Array} Random bytes with length equal to the block size of the cipher, plus the last two bytes repeated. - * @async - */ -export async function getPrefixRandom(algo) { - const prefixrandom = await getRandomBytes(cipher[algo].blockSize); - const repeat = new Uint8Array([prefixrandom[prefixrandom.length - 2], prefixrandom[prefixrandom.length - 1]]); - return util.concat([prefixrandom, repeat]); -} + /** + * Generates a random byte prefix for the specified algorithm + * See {@link https://tools.ietf.org/html/rfc4880#section-9.2|RFC 4880 9.2} for algorithms. + * @param {module:enums.symmetric} algo Symmetric encryption algorithm + * @returns {Uint8Array} Random bytes with length equal to the block size of the cipher, plus the last two bytes repeated. + * @async + */ + getPrefixRandom: async function(algo) { + const prefixrandom = await random.getRandomBytes(cipher[algo].blockSize); + const repeat = new Uint8Array([prefixrandom[prefixrandom.length - 2], prefixrandom[prefixrandom.length - 1]]); + return util.concat([prefixrandom, repeat]); + }, -/** - * Generating a session key for the specified symmetric algorithm - * See {@link https://tools.ietf.org/html/rfc4880#section-9.2|RFC 4880 9.2} for algorithms. - * @param {module:enums.symmetric} algo Symmetric encryption algorithm - * @returns {Uint8Array} Random bytes as a string to be used as a key - * @async - */ -export function generateSessionKey(algo) { - return getRandomBytes(cipher[algo].keySize); -} + /** + * Generating a session key for the specified symmetric algorithm + * See {@link https://tools.ietf.org/html/rfc4880#section-9.2|RFC 4880 9.2} for algorithms. + * @param {module:enums.symmetric} algo Symmetric encryption algorithm + * @returns {Uint8Array} Random bytes as a string to be used as a key + * @async + */ + generateSessionKey: function(algo) { + return random.getRandomBytes(cipher[algo].keySize); + }, + + constructParams: constructParams +}; diff --git a/src/crypto/eax.js b/src/crypto/eax.js index e59cf3ff..0844b8af 100644 --- a/src/crypto/eax.js +++ b/src/crypto/eax.js @@ -24,7 +24,7 @@ * @module crypto/eax */ -import { AES_CTR } from 'asmcrypto.js/dist_es8/aes/ctr'; +import { AES_CTR } from 'asmcrypto.js/dist_es5/aes/ctr'; import CMAC from './cmac'; import util from '../util'; diff --git a/src/crypto/gcm.js b/src/crypto/gcm.js index 64b75567..f028c3b0 100644 --- a/src/crypto/gcm.js +++ b/src/crypto/gcm.js @@ -23,7 +23,7 @@ * @module crypto/gcm */ -import { AES_GCM } from 'asmcrypto.js/dist_es8/aes/gcm'; +import { AES_GCM } from 'asmcrypto.js/dist_es5/aes/gcm'; import util from '../util'; const webCrypto = util.getWebCrypto(); // no GCM support in IE11, Safari 9 diff --git a/src/crypto/hash/index.js b/src/crypto/hash/index.js index aeefce36..a8aecd69 100644 --- a/src/crypto/hash/index.js +++ b/src/crypto/hash/index.js @@ -11,8 +11,8 @@ * @module crypto/hash */ -import { Sha1 } from 'asmcrypto.js/dist_es8/hash/sha1/sha1'; -import { Sha256 } from 'asmcrypto.js/dist_es8/hash/sha256/sha256'; +import { Sha1 } from 'asmcrypto.js/dist_es5/hash/sha1/sha1'; +import { Sha256 } from 'asmcrypto.js/dist_es5/hash/sha256/sha256'; import sha224 from 'hash.js/lib/hash/sha/224'; import sha384 from 'hash.js/lib/hash/sha/384'; import sha512 from 'hash.js/lib/hash/sha/512'; @@ -37,7 +37,7 @@ function node_hash(type) { function hashjs_hash(hash, webCryptoHash) { return async function(data) { - if (!util.isStream(data) && webCrypto && webCryptoHash && data.length >= config.minBytesForWebCrypto) { + if (!util.isStream(data) && webCrypto && webCryptoHash && data.length >= config.min_bytes_for_web_crypto) { return new Uint8Array(await webCrypto.digest(webCryptoHash, data)); } const hashInstance = hash(); @@ -54,7 +54,7 @@ function asmcrypto_hash(hash, webCryptoHash) { return stream.transform(data, value => { hashInstance.process(value); }, () => hashInstance.finish().result); - } else if (webCrypto && webCryptoHash && data.length >= config.minBytesForWebCrypto) { + } else if (webCrypto && webCryptoHash && data.length >= config.min_bytes_for_web_crypto) { return new Uint8Array(await webCrypto.digest(webCryptoHash, data)); } else { return hash.bytes(data); diff --git a/src/crypto/hash/md5.js b/src/crypto/hash/md5.js index 126c8fb1..fb277030 100644 --- a/src/crypto/hash/md5.js +++ b/src/crypto/hash/md5.js @@ -20,8 +20,8 @@ import util from '../../util'; // MD5 Digest async function md5(entree) { - const digest = md51(util.uint8ArrayToStr(entree)); - return util.hexToUint8Array(hex(digest)); + const digest = md51(util.Uint8Array_to_str(entree)); + return util.hex_to_Uint8Array(hex(digest)); } function md5cycle(x, k) { diff --git a/src/crypto/index.js b/src/crypto/index.js index ac08ed0f..c136af1e 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -9,19 +9,19 @@ * @module crypto */ -import * as cipher from './cipher'; +import cipher from './cipher'; import hash from './hash'; -import * as cfb from './cfb'; +import cfb from './cfb'; import gcm from './gcm'; import eax from './eax'; import ocb from './ocb'; import publicKey from './public_key'; -import * as signature from './signature'; -import * as random from './random'; -import * as pkcs1 from './pkcs1'; -import * as pkcs5 from './pkcs5'; -import * as crypto from './crypto'; -import * as aes_kw from './aes_kw'; +import signature from './signature'; +import random from './random'; +import pkcs1 from './pkcs1'; +import pkcs5 from './pkcs5'; +import crypto from './crypto'; +import aes_kw from './aes_kw'; // TODO move cfb and gcm to cipher const mod = { @@ -33,7 +33,7 @@ const mod = { cfb: cfb, /** @see module:crypto/gcm */ gcm: gcm, - experimentalGcm: gcm, + experimental_gcm: gcm, /** @see module:crypto/eax */ eax: eax, /** @see module:crypto/ocb */ diff --git a/src/crypto/ocb.js b/src/crypto/ocb.js index 8547e27e..f193e2af 100644 --- a/src/crypto/ocb.js +++ b/src/crypto/ocb.js @@ -22,7 +22,7 @@ * @module crypto/ocb */ -import * as ciphers from './cipher'; +import ciphers from './cipher'; import util from '../util'; diff --git a/src/crypto/pkcs1.js b/src/crypto/pkcs1.js index d91f234e..4bc783c2 100644 --- a/src/crypto/pkcs1.js +++ b/src/crypto/pkcs1.js @@ -19,14 +19,21 @@ * @fileoverview Provides EME-PKCS1-v1_5 encoding and decoding and EMSA-PKCS1-v1_5 encoding function * @see module:crypto/public_key/rsa * @see module:crypto/public_key/elliptic/ecdh - * @see PublicKeyEncryptedSessionKeyPacket + * @see module:packet.PublicKeyEncryptedSessionKey * @requires crypto/random * @requires crypto/hash + * @requires util * @module crypto/pkcs1 */ -import { getRandomBytes } from './random'; +import random from './random'; import hash from './hash'; +import util from '../util'; + +/** @namespace */ +const eme = {}; +/** @namespace */ +const emsa = {}; /** * ASN1 object identifiers for hashes @@ -49,18 +56,17 @@ hash_headers[11] = [0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, /** * Create padding with secure random data * @private - * @param {Integer} length Length of the padding in bytes - * @returns {Uint8Array} Random padding + * @param {Integer} length Length of the padding in bytes + * @returns {String} Padding as string * @async */ async function getPkcs1Padding(length) { - const result = new Uint8Array(length); - let count = 0; - while (count < length) { - const randomBytes = await getRandomBytes(length - count); + let result = ''; + while (result.length < length) { + const randomBytes = await random.getRandomBytes(length - result.length); for (let i = 0; i < randomBytes.length; i++) { if (randomBytes[i] !== 0) { - result[count++] = randomBytes[i]; + result += String.fromCharCode(randomBytes[i]); } } } @@ -70,49 +76,49 @@ async function getPkcs1Padding(length) { /** * Create a EME-PKCS1-v1_5 padded message * @see {@link https://tools.ietf.org/html/rfc4880#section-13.1.1|RFC 4880 13.1.1} - * @param {Uint8Array} message message to be encoded - * @param {Integer} keyLength the length in octets of the key modulus - * @returns {Promise} EME-PKCS1 padded message + * @param {String} M message to be encoded + * @param {Integer} k the length in octets of the key modulus + * @returns {Promise} EME-PKCS1 padded message * @async */ -export async function emeEncode(message, keyLength) { - const mLength = message.length; +eme.encode = async function(M, k) { + const mLen = M.length; // length checking - if (mLength > keyLength - 11) { + if (mLen > k - 11) { throw new Error('Message too long'); } // Generate an octet string PS of length k - mLen - 3 consisting of // pseudo-randomly generated nonzero octets - const PS = await getPkcs1Padding(keyLength - mLength - 3); + const PS = await getPkcs1Padding(k - mLen - 3); // Concatenate PS, the message M, and other padding to form an // encoded message EM of length k octets as EM = 0x00 || 0x02 || PS || 0x00 || M. - const encoded = new Uint8Array(keyLength); - // 0x00 byte - encoded[1] = 2; - encoded.set(PS, 2); - // 0x00 bytes - encoded.set(message, keyLength - mLength); - return encoded; -} + return String.fromCharCode(0) + + String.fromCharCode(2) + + PS + + String.fromCharCode(0) + + M; +}; /** * Decode a EME-PKCS1-v1_5 padded message * @see {@link https://tools.ietf.org/html/rfc4880#section-13.1.2|RFC 4880 13.1.2} - * @param {Uint8Array} encoded encoded message bytes - * @returns {Uint8Array} message + * @param {String} EM encoded message, an octet string + * @returns {String} message, an octet string */ -export function emeDecode(encoded) { +eme.decode = function(EM) { + const firstOct = EM.charCodeAt(0); + const secondOct = EM.charCodeAt(1); let i = 2; - while (encoded[i] !== 0 && i < encoded.length) { + while (EM.charCodeAt(i) !== 0 && i < EM.length) { i++; } const psLen = i - 2; - const separator = encoded[i++]; - if (encoded[0] === 0 && encoded[1] === 2 && psLen >= 8 && separator === 0) { - return encoded.subarray(i); + const separator = EM.charCodeAt(i++); + if (firstOct === 0 && secondOct === 2 && psLen >= 8 && separator === 0) { + return EM.substr(i); } throw new Error('Decryption error'); -} +}; /** * Create a EMSA-PKCS1-v1_5 padded message @@ -120,34 +126,41 @@ export function emeDecode(encoded) { * @param {Integer} algo Hash algorithm type used * @param {Uint8Array} hashed message to be encoded * @param {Integer} emLen intended length in octets of the encoded message - * @returns {Uint8Array} encoded message + * @returns {String} encoded message */ -export async function emsaEncode(algo, hashed, emLen) { +emsa.encode = async function(algo, hashed, emLen) { let i; - if (hashed.length !== hash.getHashByteLength(algo)) { + const H = util.Uint8Array_to_str(hashed); + if (H.length !== hash.getHashByteLength(algo)) { throw new Error('Invalid hash length'); } // produce an ASN.1 DER value for the hash function used. // Let T be the full hash prefix - const hashPrefix = new Uint8Array(hash_headers[algo].length); + let T = ''; for (i = 0; i < hash_headers[algo].length; i++) { - hashPrefix[i] = hash_headers[algo][i]; + T += String.fromCharCode(hash_headers[algo][i]); } - // and let tLen be the length in octets prefix and hashed data - const tLen = hashPrefix.length + hashed.length; + // add hash value to prefix + T += H; + // and let tLen be the length in octets of T + const tLen = T.length; if (emLen < tLen + 11) { throw new Error('Intended encoded message length too short'); } // an octet string PS consisting of emLen - tLen - 3 octets with hexadecimal value 0xFF // The length of PS will be at least 8 octets - const PS = new Uint8Array(emLen - tLen - 3).fill(0xff); + let PS = ''; + for (i = 0; i < (emLen - tLen - 3); i++) { + PS += String.fromCharCode(0xff); + } + // Concatenate PS, the hash prefix T, and other padding to form the + // encoded message EM as EM = 0x00 || 0x01 || PS || 0x00 || T. + const EM = String.fromCharCode(0x00) + + String.fromCharCode(0x01) + + PS + + String.fromCharCode(0x00) + + T; + return util.str_to_hex(EM); +}; - // Concatenate PS, the hash prefix, hashed data, and other padding to form the - // encoded message EM as EM = 0x00 || 0x01 || PS || 0x00 || prefix || hashed - const EM = new Uint8Array(emLen); - EM[1] = 0x01; - EM.set(PS, 2); - EM.set(hashPrefix, emLen - tLen); - EM.set(hashed, emLen - hashed.length); - return EM; -} +export default { eme, emsa }; diff --git a/src/crypto/pkcs5.js b/src/crypto/pkcs5.js index acb1577b..23c93b2d 100644 --- a/src/crypto/pkcs5.js +++ b/src/crypto/pkcs5.js @@ -15,42 +15,41 @@ // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -import util from '../util'; - /** * @fileoverview Functions to add and remove PKCS5 padding - * @see PublicKeyEncryptedSessionKeyPacket + * @see module:packet.PublicKeyEncryptedSessionKey * @module crypto/pkcs5 */ /** - * Add pkcs5 padding to a message - * @param {Uint8Array} message message to pad - * @returns {Uint8Array} padded message + * Add pkcs5 padding to a text. + * @param {String} msg Text to add padding + * @returns {String} Text with padding added */ -export function encode(message) { - const c = 8 - (message.length % 8); - const padded = new Uint8Array(message.length + c).fill(c); - padded.set(message); - return padded; +function encode(msg) { + const c = 8 - (msg.length % 8); + const padding = String.fromCharCode(c).repeat(c); + return msg + padding; } /** - * Remove pkcs5 padding from a message - * @param {Uint8Array} message message to remove padding from - * @returns {Uint8Array} message without padding + * Remove pkcs5 padding from a string. + * @param {String} msg Text to remove padding from + * @returns {String} Text with padding removed */ -export function decode(message) { - const len = message.length; +function decode(msg) { + const len = msg.length; if (len > 0) { - const c = message[len - 1]; + const c = msg.charCodeAt(len - 1); if (c >= 1) { - const provided = message.subarray(len - c); - const computed = new Uint8Array(c).fill(c); - if (util.equalsUint8Array(provided, computed)) { - return message.subarray(0, len - c); + const provided = msg.substr(len - c); + const computed = String.fromCharCode(c).repeat(c); + if (provided === computed) { + return msg.substr(0, len - c); } } } throw new Error('Invalid padding'); } + +export default { encode, decode }; diff --git a/src/crypto/public_key/dsa.js b/src/crypto/public_key/dsa.js index a501eb39..15f1c94e 100644 --- a/src/crypto/public_key/dsa.js +++ b/src/crypto/public_key/dsa.js @@ -17,13 +17,19 @@ /** * @fileoverview A Digital signature algorithm implementation + * @requires bn.js * @requires crypto/random * @requires util * @module crypto/public_key/dsa */ -import { getRandomBigInteger } from '../random'; + +import BN from 'bn.js'; +import random from '../random'; import util from '../../util'; -import { isProbablePrime } from './prime'; +import prime from './prime'; + +const one = new BN(1); +const zero = new BN(0); /* TODO regarding the hash function, read: @@ -31,168 +37,152 @@ import { isProbablePrime } from './prime'; https://tools.ietf.org/html/rfc4880#section-14 */ -/** - * DSA Sign function - * @param {Integer} hash_algo - * @param {Uint8Array} hashed - * @param {Uint8Array} g - * @param {Uint8Array} p - * @param {Uint8Array} q - * @param {Uint8Array} x - * @returns {{ r: Uint8Array, s: Uint8Array }} - * @async - */ -export async function sign(hash_algo, hashed, g, p, q, x) { - const BigInteger = await util.getBigInteger(); - const one = new BigInteger(1); - p = new BigInteger(p); - q = new BigInteger(q); - g = new BigInteger(g); - x = new BigInteger(x); - - let k; - let r; - let s; - let t; - g = g.mod(p); - x = x.mod(q); - // If the output size of the chosen hash is larger than the number of - // bits of q, the hash result is truncated to fit by taking the number - // of leftmost bits equal to the number of bits of q. This (possibly - // truncated) hash function result is treated as a number and used - // directly in the DSA signature algorithm. - const h = new BigInteger(hashed.subarray(0, q.byteLength())).mod(q); - // FIPS-186-4, section 4.6: - // The values of r and s shall be checked to determine if r = 0 or s = 0. - // If either r = 0 or s = 0, a new value of k shall be generated, and the - // signature shall be recalculated. It is extremely unlikely that r = 0 - // or s = 0 if signatures are generated properly. - while (true) { - // See Appendix B here: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf - k = await getRandomBigInteger(one, q); // returns in [1, q-1] - r = g.modExp(k, p).imod(q); // (g**k mod p) mod q - if (r.isZero()) { - continue; - } - const xr = x.mul(r).imod(q); - t = h.add(xr).imod(q); // H(m) + x*r mod q - s = k.modInv(q).imul(t).imod(q); // k**-1 * (H(m) + x*r) mod q - if (s.isZero()) { - continue; - } - break; - } - return { - r: r.toUint8Array('be', q.byteLength()), - s: s.toUint8Array('be', q.byteLength()) - }; -} - -/** - * DSA Verify function - * @param {Integer} hash_algo - * @param {Uint8Array} r - * @param {Uint8Array} s - * @param {Uint8Array} hashed - * @param {Uint8Array} g - * @param {Uint8Array} p - * @param {Uint8Array} q - * @param {Uint8Array} y - * @returns {boolean} - * @async - */ -export async function verify(hash_algo, r, s, hashed, g, p, q, y) { - const BigInteger = await util.getBigInteger(); - const zero = new BigInteger(0); - r = new BigInteger(r); - s = new BigInteger(s); - - p = new BigInteger(p); - q = new BigInteger(q); - g = new BigInteger(g); - y = new BigInteger(y); - - if (r.lte(zero) || r.gte(q) || - s.lte(zero) || s.gte(q)) { - util.printDebug("invalid DSA Signature"); - return false; - } - const h = new BigInteger(hashed.subarray(0, q.byteLength())).imod(q); - const w = s.modInv(q); // s**-1 mod q - if (w.isZero()) { - util.printDebug("invalid DSA Signature"); - return false; - } - - g = g.mod(p); - y = y.mod(p); - const u1 = h.mul(w).imod(q); // H(m) * w mod q - const u2 = r.mul(w).imod(q); // r * w mod q - const t1 = g.modExp(u1, p); // g**u1 mod p - const t2 = y.modExp(u2, p); // y**u2 mod p - const v = t1.mul(t2).imod(p).imod(q); // (g**u1 * y**u2 mod p) mod q - return v.equal(r); -} - -/** - * Validate DSA parameters - * @param {Uint8Array} p DSA prime - * @param {Uint8Array} q DSA group order - * @param {Uint8Array} g DSA sub-group generator - * @param {Uint8Array} y DSA public key - * @param {Uint8Array} x DSA private key - * @returns {Promise} whether params are valid - * @async - */ -export async function validateParams(p, q, g, y, x) { - const BigInteger = await util.getBigInteger(); - p = new BigInteger(p); - q = new BigInteger(q); - g = new BigInteger(g); - y = new BigInteger(y); - const one = new BigInteger(1); - // Check that 1 < g < p - if (g.lte(one) || g.gte(p)) { - return false; - } - +export default { /** - * Check that subgroup order q divides p-1 + * DSA Sign function + * @param {Integer} hash_algo + * @param {Uint8Array} hashed + * @param {BN} g + * @param {BN} p + * @param {BN} q + * @param {BN} x + * @returns {{ r: BN, s: BN }} + * @async */ - if (!p.dec().mod(q).isZero()) { - return false; - } + sign: async function(hash_algo, hashed, g, p, q, x) { + let k; + let r; + let s; + let t; + const redp = new BN.red(p); + const redq = new BN.red(q); + const gred = g.toRed(redp); + const xred = x.toRed(redq); + // If the output size of the chosen hash is larger than the number of + // bits of q, the hash result is truncated to fit by taking the number + // of leftmost bits equal to the number of bits of q. This (possibly + // truncated) hash function result is treated as a number and used + // directly in the DSA signature algorithm. + const h = new BN(hashed.subarray(0, q.byteLength())).toRed(redq); + // FIPS-186-4, section 4.6: + // The values of r and s shall be checked to determine if r = 0 or s = 0. + // If either r = 0 or s = 0, a new value of k shall be generated, and the + // signature shall be recalculated. It is extremely unlikely that r = 0 + // or s = 0 if signatures are generated properly. + while (true) { + // See Appendix B here: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf + k = await random.getRandomBN(one, q); // returns in [1, q-1] + r = gred.redPow(k).fromRed().toRed(redq); // (g**k mod p) mod q + if (zero.cmp(r) === 0) { + continue; + } + t = h.redAdd(xred.redMul(r)); // H(m) + x*r mod q + s = k.toRed(redq).redInvm().redMul(t); // k**-1 * (H(m) + x*r) mod q + if (zero.cmp(s) === 0) { + continue; + } + break; + } + return { + r: r.toArrayLike(Uint8Array, 'be', q.byteLength()), + s: s.toArrayLike(Uint8Array, 'be', q.byteLength()) + }; + }, /** - * g has order q - * Check that g ** q = 1 mod p + * DSA Verify function + * @param {Integer} hash_algo + * @param {BN} r + * @param {BN} s + * @param {Uint8Array} hashed + * @param {BN} g + * @param {BN} p + * @param {BN} q + * @param {BN} y + * @returns {boolean} + * @async */ - if (!g.modExp(q, p).isOne()) { - return false; - } + verify: async function(hash_algo, r, s, hashed, g, p, q, y) { + if (zero.ucmp(r) >= 0 || r.ucmp(q) >= 0 || + zero.ucmp(s) >= 0 || s.ucmp(q) >= 0) { + util.print_debug("invalid DSA Signature"); + return null; + } + const redp = new BN.red(p); + const redq = new BN.red(q); + const h = new BN(hashed.subarray(0, q.byteLength())); + const w = s.toRed(redq).redInvm(); // s**-1 mod q + if (zero.cmp(w) === 0) { + util.print_debug("invalid DSA Signature"); + return null; + } + const u1 = h.toRed(redq).redMul(w); // H(m) * w mod q + const u2 = r.toRed(redq).redMul(w); // r * w mod q + const t1 = g.toRed(redp).redPow(u1.fromRed()); // g**u1 mod p + const t2 = y.toRed(redp).redPow(u2.fromRed()); // y**u2 mod p + const v = t1.redMul(t2).fromRed().mod(q); // (g**u1 * y**u2 mod p) mod q + return v.cmp(r) === 0; + }, /** - * Check q is large and probably prime (we mainly want to avoid small factors) + * Validate DSA parameters + * @param {Uint8Array} p DSA prime + * @param {Uint8Array} q DSA group order + * @param {Uint8Array} g DSA sub-group generator + * @param {Uint8Array} y DSA public key + * @param {Uint8Array} x DSA private key + * @returns {Promise} whether params are valid + * @async */ - const qSize = new BigInteger(q.bitLength()); - const n150 = new BigInteger(150); - if (qSize.lt(n150) || !(await isProbablePrime(q, null, 32))) { - return false; - } + validateParams: async function (p, q, g, y, x) { + p = new BN(p); + q = new BN(q); + g = new BN(g); + y = new BN(y); + const one = new BN(1); + // Check that 1 < g < p + if (g.lte(one) || g.gte(p)) { + return false; + } - /** - * Re-derive public key y' = g ** x mod p - * Expect y == y' - * - * Blinded exponentiation computes g**{rq + x} to compare to y - */ - x = new BigInteger(x); - const two = new BigInteger(2); - const r = await getRandomBigInteger(two.leftShift(qSize.dec()), two.leftShift(qSize)); // draw r of same size as q - const rqx = q.mul(r).add(x); - if (!y.equal(g.modExp(rqx, p))) { - return false; - } + /** + * Check that subgroup order q divides p-1 + */ + if (!p.sub(one).mod(q).isZero()) { + return false; + } - return true; -} + const pred = new BN.red(p); + const gModP = g.toRed(pred); + /** + * g has order q + * Check that g ** q = 1 mod p + */ + if (!gModP.redPow(q).eq(one)) { + return false; + } + + /** + * Check q is large and probably prime (we mainly want to avoid small factors) + */ + const qSize = q.bitLength(); + if (qSize < 150 || !(await prime.isProbablePrime(q, null, 32))) { + return false; + } + + /** + * Re-derive public key y' = g ** x mod p + * Expect y == y' + * + * Blinded exponentiation computes g**{rq + x} to compare to y + */ + x = new BN(x); + const r = await random.getRandomBN(new BN(2).shln(qSize - 1), new BN(2).shln(qSize)); // draw r of same size as q + const rqx = q.mul(r).add(x); + if (!y.eq(gModP.redPow(rqx))) { + return false; + } + + return true; + } +}; diff --git a/src/crypto/public_key/elgamal.js b/src/crypto/public_key/elgamal.js index 81fa505b..0e10f032 100644 --- a/src/crypto/public_key/elgamal.js +++ b/src/crypto/public_key/elgamal.js @@ -17,129 +17,120 @@ /** * @fileoverview ElGamal implementation + * @requires bn.js * @requires crypto/random - * @requires util * @module crypto/public_key/elgamal */ -import util from '../../util'; -import { getRandomBigInteger } from '../random'; -import { emeEncode, emeDecode } from '../pkcs1'; +import BN from 'bn.js'; +import random from '../random'; -/** - * ElGamal Encryption function - * Note that in OpenPGP, the message needs to be padded with PKCS#1 (same as RSA) - * @param {Uint8Array} data to be padded and encrypted - * @param {Uint8Array} p - * @param {Uint8Array} g - * @param {Uint8Array} y - * @returns {{ c1: Uint8Array, c2: Uint8Array }} - * @async - */ -export async function encrypt(data, p, g, y) { - const BigInteger = await util.getBigInteger(); - p = new BigInteger(p); - g = new BigInteger(g); - y = new BigInteger(y); - - const padded = await emeEncode(data, p.byteLength()); - const m = new BigInteger(padded); - - // OpenPGP uses a "special" version of ElGamal where g is generator of the full group Z/pZ* - // hence g has order p-1, and to avoid that k = 0 mod p-1, we need to pick k in [1, p-2] - const k = await getRandomBigInteger(new BigInteger(1), p.dec()); - return { - c1: g.modExp(k, p).toUint8Array(), - c2: y.modExp(k, p).imul(m).imod(p).toUint8Array() - }; -} +export default { + /** + * ElGamal Encryption function + * @param {BN} m + * @param {BN} p + * @param {BN} g + * @param {BN} y + * @returns {{ c1: BN, c2: BN }} + * @async + */ + encrypt: async function(m, p, g, y) { + const redp = new BN.red(p); + const mred = m.toRed(redp); + const gred = g.toRed(redp); + const yred = y.toRed(redp); + // OpenPGP uses a "special" version of ElGamal where g is generator of the full group Z/pZ* + // hence g has order p-1, and to avoid that k = 0 mod p-1, we need to pick k in [1, p-2] + const k = await random.getRandomBN(new BN(1), p.subn(1)); + return { + c1: gred.redPow(k).fromRed(), + c2: yred.redPow(k).redMul(mred).fromRed() + }; + }, -/** - * ElGamal Encryption function - * @param {Uint8Array} c1 - * @param {Uint8Array} c2 - * @param {Uint8Array} p - * @param {Uint8Array} x - * @returns {Uint8Array} unpadded message - * @async - */ -export async function decrypt(c1, c2, p, x) { - const BigInteger = await util.getBigInteger(); - c1 = new BigInteger(c1); - c2 = new BigInteger(c2); - p = new BigInteger(p); - x = new BigInteger(x); + /** + * ElGamal Encryption function + * @param {BN} c1 + * @param {BN} c2 + * @param {BN} p + * @param {BN} x + * @returns BN + * @async + */ + decrypt: async function(c1, c2, p, x) { + const redp = new BN.red(p); + const c1red = c1.toRed(redp); + const c2red = c2.toRed(redp); + return c1red.redPow(x).redInvm().redMul(c2red).fromRed(); + }, - const padded = c1.modExp(x, p).modInv(p).imul(c2).imod(p); - return emeDecode(padded.toUint8Array('be', p.byteLength())); -} + /** + * Validate ElGamal parameters + * @param {Uint8Array} p ElGamal prime + * @param {Uint8Array} g ElGamal group generator + * @param {Uint8Array} y ElGamal public key + * @param {Uint8Array} x ElGamal private exponent + * @returns {Promise} whether params are valid + * @async + */ + validateParams: async function (p, g, y, x) { + p = new BN(p); + g = new BN(g); + y = new BN(y); -/** - * Validate ElGamal parameters - * @param {Uint8Array} p ElGamal prime - * @param {Uint8Array} g ElGamal group generator - * @param {Uint8Array} y ElGamal public key - * @param {Uint8Array} x ElGamal private exponent - * @returns {Promise} whether params are valid - * @async - */ -export async function validateParams(p, g, y, x) { - const BigInteger = await util.getBigInteger(); - p = new BigInteger(p); - g = new BigInteger(g); - y = new BigInteger(y); + const one = new BN(1); + // Check that 1 < g < p + if (g.lte(one) || g.gte(p)) { + return false; + } - const one = new BigInteger(1); - // Check that 1 < g < p - if (g.lte(one) || g.gte(p)) { - return false; - } + // Expect p-1 to be large + const pSize = p.subn(1).bitLength(); + if (pSize < 1023) { + return false; + } - // Expect p-1 to be large - const pSize = new BigInteger(p.bitLength()); - const n1023 = new BigInteger(1023); - if (pSize.lt(n1023)) { - return false; - } + const pred = new BN.red(p); + const gModP = g.toRed(pred); + /** + * g should have order p-1 + * Check that g ** (p-1) = 1 mod p + */ + if (!gModP.redPow(p.subn(1)).eq(one)) { + return false; + } - /** - * g should have order p-1 - * Check that g ** (p-1) = 1 mod p - */ - if (!g.modExp(p.dec(), p).isOne()) { - return false; - } + /** + * Since p-1 is not prime, g might have a smaller order that divides p-1 + * We want to make sure that the order is large enough to hinder a small subgroup attack + * + * We just check g**i != 1 for all i up to a threshold + */ + let res = g; + const i = new BN(1); + const threshold = new BN(2).shln(17); // we want order > threshold + while (i.lt(threshold)) { + res = res.mul(g).mod(p); + if (res.eqn(1)) { + return false; + } + i.iaddn(1); + } - /** - * Since p-1 is not prime, g might have a smaller order that divides p-1 - * We want to make sure that the order is large enough to hinder a small subgroup attack - * - * We just check g**i != 1 for all i up to a threshold - */ - let res = g; - const i = new BigInteger(1); - const threshold = new BigInteger(2).leftShift(new BigInteger(17)); // we want order > threshold - while (i.lt(threshold)) { - res = res.mul(g).imod(p); - if (res.isOne()) { + /** + * Re-derive public key y' = g ** x mod p + * Expect y == y' + * + * Blinded exponentiation computes g**{r(p-1) + x} to compare to y + */ + x = new BN(x); + const r = await random.getRandomBN(new BN(2).shln(pSize - 1), new BN(2).shln(pSize)); // draw r of same size as p-1 + const rqx = p.subn(1).mul(r).add(x); + if (!y.eq(gModP.redPow(rqx))) { return false; } - i.iinc(); - } - /** - * Re-derive public key y' = g ** x mod p - * Expect y == y' - * - * Blinded exponentiation computes g**{r(p-1) + x} to compare to y - */ - x = new BigInteger(x); - const two = new BigInteger(2); - const r = await getRandomBigInteger(two.leftShift(pSize.dec()), two.leftShift(pSize)); // draw r of same size as p-1 - const rqx = p.dec().imul(r).iadd(x); - if (!y.equal(g.modExp(rqx, p))) { - return false; + return true; } - - return true; -} +}; diff --git a/src/crypto/public_key/elliptic/curves.js b/src/crypto/public_key/elliptic/curves.js index 7370f970..4ba6b617 100644 --- a/src/crypto/public_key/elliptic/curves.js +++ b/src/crypto/public_key/elliptic/curves.js @@ -17,6 +17,7 @@ /** * @fileoverview Wrapper of an instance of an Elliptic Curve + * @requires bn.js * @requires tweetnacl * @requires crypto/public_key/elliptic/key * @requires crypto/random @@ -27,11 +28,11 @@ * @module crypto/public_key/elliptic/curve */ +import BN from 'bn.js'; import nacl from 'tweetnacl/nacl-fast-light.js'; -import { getRandomBytes } from '../../random'; +import random from '../../random'; import enums from '../../../enums'; import util from '../../../util'; -import { uint8ArrayToB64, b64ToUint8Array } from '../../../encoding/base64'; import OID from '../../../type/oid'; import { keyFromPublic, keyFromPrivate, getIndutnyCurve } from './indutnyKey'; @@ -136,91 +137,88 @@ const curves = { } }; -class Curve { - constructor(oid_or_name, params) { - try { - if (util.isArray(oid_or_name) || - util.isUint8Array(oid_or_name)) { - // by oid byte array - oid_or_name = new OID(oid_or_name); - } - if (oid_or_name instanceof OID) { - // by curve OID - oid_or_name = oid_or_name.getName(); - } - // by curve name or oid string - this.name = enums.write(enums.curve, oid_or_name); - } catch (err) { - throw new Error('Not valid curve'); +/** + * @constructor + */ +function Curve(oid_or_name, params) { + try { + if (util.isArray(oid_or_name) || + util.isUint8Array(oid_or_name)) { + // by oid byte array + oid_or_name = new OID(oid_or_name); + } + if (oid_or_name instanceof OID) { + // by curve OID + oid_or_name = oid_or_name.getName(); } - params = params || curves[this.name]; + // by curve name or oid string + this.name = enums.write(enums.curve, oid_or_name); + } catch (err) { + throw new Error('Not valid curve'); + } + params = params || curves[this.name]; - this.keyType = params.keyType; + this.keyType = params.keyType; - this.oid = params.oid; - this.hash = params.hash; - this.cipher = params.cipher; - this.node = params.node && curves[this.name]; - this.web = params.web && curves[this.name]; - this.payloadSize = params.payloadSize; - if (this.web && util.getWebCrypto()) { - this.type = 'web'; - } else if (this.node && util.getNodeCrypto()) { - this.type = 'node'; - } else if (this.name === 'curve25519') { - this.type = 'curve25519'; - } else if (this.name === 'ed25519') { - this.type = 'ed25519'; - } + this.oid = params.oid; + this.hash = params.hash; + this.cipher = params.cipher; + this.node = params.node && curves[this.name]; + this.web = params.web && curves[this.name]; + this.payloadSize = params.payloadSize; + if (this.web && util.getWebCrypto()) { + this.type = 'web'; + } else if (this.node && util.getNodeCrypto()) { + this.type = 'node'; + } else if (this.name === 'curve25519') { + this.type = 'curve25519'; + } else if (this.name === 'ed25519') { + this.type = 'ed25519'; } +} - async genKeyPair() { - let keyPair; - switch (this.type) { - case 'web': - try { - return await webGenKeyPair(this.name); - } catch (err) { - util.printDebugError("Browser did not support generating ec key " + err.message); - break; - } - case 'node': - return nodeGenKeyPair(this.name); - case 'curve25519': { - const privateKey = await getRandomBytes(32); - privateKey[0] = (privateKey[0] & 127) | 64; - privateKey[31] &= 248; - const secretKey = privateKey.slice().reverse(); - keyPair = nacl.box.keyPair.fromSecretKey(secretKey); - const publicKey = util.concatUint8Array([new Uint8Array([0x40]), keyPair.publicKey]); - return { publicKey, privateKey }; - } - case 'ed25519': { - const privateKey = await getRandomBytes(32); - const keyPair = nacl.sign.keyPair.fromSeed(privateKey); - const publicKey = util.concatUint8Array([new Uint8Array([0x40]), keyPair.publicKey]); - return { publicKey, privateKey }; +Curve.prototype.genKeyPair = async function () { + let keyPair; + switch (this.type) { + case 'web': + try { + return await webGenKeyPair(this.name); + } catch (err) { + util.print_debug_error("Browser did not support generating ec key " + err.message); + break; } + case 'node': + return nodeGenKeyPair(this.name); + case 'curve25519': { + const privateKey = await random.getRandomBytes(32); + privateKey[0] = (privateKey[0] & 127) | 64; + privateKey[31] &= 248; + const secretKey = privateKey.slice().reverse(); + keyPair = nacl.box.keyPair.fromSecretKey(secretKey); + const publicKey = util.concatUint8Array([new Uint8Array([0x40]), keyPair.publicKey]); + return { publicKey, privateKey }; + } + case 'ed25519': { + const privateKey = await random.getRandomBytes(32); + const keyPair = nacl.sign.keyPair.fromSeed(privateKey); + const publicKey = util.concatUint8Array([new Uint8Array([0x40]), keyPair.publicKey]); + return { publicKey, privateKey }; } - const indutnyCurve = await getIndutnyCurve(this.name); - keyPair = await indutnyCurve.genKeyPair({ - entropy: util.uint8ArrayToStr(await getRandomBytes(32)) - }); - return { publicKey: new Uint8Array(keyPair.getPublic('array', false)), privateKey: keyPair.getPrivate().toArrayLike(Uint8Array) }; } -} + const indutnyCurve = await getIndutnyCurve(this.name); + keyPair = await indutnyCurve.genKeyPair({ + entropy: util.Uint8Array_to_str(await random.getRandomBytes(32)) + }); + return { publicKey: new Uint8Array(keyPair.getPublic('array', false)), privateKey: keyPair.getPrivate().toArrayLike(Uint8Array) }; +}; async function generate(curve) { - const BigInteger = await util.getBigInteger(); - curve = new Curve(curve); const keyPair = await curve.genKeyPair(); - const Q = new BigInteger(keyPair.publicKey).toUint8Array(); - const secret = new BigInteger(keyPair.privateKey).toUint8Array('be', curve.payloadSize); return { oid: curve.oid, - Q, - secret, + Q: new BN(keyPair.publicKey), + d: new BN(keyPair.privateKey), hash: curve.hash, cipher: curve.cipher }; @@ -284,6 +282,7 @@ async function validateStandardParams(algo, oid, Q, d) { * Re-derive public point Q' = dG from private key * Expect Q == Q' */ + d = new BN(d); const dG = keyFromPrivate(curve, d).getPublic(); if (!dG.eq(Q)) { return false; @@ -292,8 +291,10 @@ async function validateStandardParams(algo, oid, Q, d) { return true; } +export default Curve; + export { - Curve, curves, webCurves, nodeCurves, generate, getPreferredHashAlgo, jwkToRawPublic, rawPublicToJwk, privateToJwk, validateStandardParams + curves, webCurves, nodeCurves, generate, getPreferredHashAlgo, jwkToRawPublic, rawPublicToJwk, privateToJwk, validateStandardParams }; ////////////////////////// @@ -312,7 +313,7 @@ async function webGenKeyPair(name) { return { publicKey: jwkToRawPublic(publicKey), - privateKey: b64ToUint8Array(privateKey.d, true) + privateKey: util.b64_to_Uint8Array(privateKey.d, true) }; } @@ -338,8 +339,8 @@ async function nodeGenKeyPair(name) { * @returns {Uint8Array} raw public key */ function jwkToRawPublic(jwk) { - const bufX = b64ToUint8Array(jwk.x); - const bufY = b64ToUint8Array(jwk.y); + const bufX = util.b64_to_Uint8Array(jwk.x); + const bufY = util.b64_to_Uint8Array(jwk.y); const publicKey = new Uint8Array(bufX.length + bufY.length + 1); publicKey[0] = 0x04; publicKey.set(bufX, 1); @@ -362,8 +363,8 @@ function rawPublicToJwk(payloadSize, name, publicKey) { const jwk = { kty: "EC", crv: name, - x: uint8ArrayToB64(bufX, true), - y: uint8ArrayToB64(bufY, true), + x: util.Uint8Array_to_b64(bufX, true), + y: util.Uint8Array_to_b64(bufY, true), ext: true }; return jwk; @@ -379,6 +380,6 @@ function rawPublicToJwk(payloadSize, name, publicKey) { */ function privateToJwk(payloadSize, name, publicKey, privateKey) { const jwk = rawPublicToJwk(payloadSize, name, publicKey); - jwk.d = uint8ArrayToB64(privateKey, true); + jwk.d = util.Uint8Array_to_b64(privateKey, true); return jwk; } diff --git a/src/crypto/public_key/elliptic/ecdh.js b/src/crypto/public_key/elliptic/ecdh.js index 04c97c18..9c9566c6 100644 --- a/src/crypto/public_key/elliptic/ecdh.js +++ b/src/crypto/public_key/elliptic/ecdh.js @@ -17,6 +17,7 @@ /** * @fileoverview Key encryption and decryption for RFC 6637 ECDH + * @requires bn.js * @requires tweetnacl * @requires crypto/public_key/elliptic/curve * @requires crypto/aes_kw @@ -29,16 +30,15 @@ * @module crypto/public_key/elliptic/ecdh */ +import BN from 'bn.js'; import nacl from 'tweetnacl/nacl-fast-light.js'; -import { Curve, jwkToRawPublic, rawPublicToJwk, privateToJwk, validateStandardParams } from './curves'; -import * as aes_kw from '../../aes_kw'; -import * as cipher from '../../cipher'; -import { getRandomBytes } from '../../random'; +import Curve, { jwkToRawPublic, rawPublicToJwk, privateToJwk, validateStandardParams } from './curves'; +import aes_kw from '../../aes_kw'; +import cipher from '../../cipher'; +import random from '../../random'; import hash from '../../hash'; import enums from '../../../enums'; import util from '../../../util'; -import { b64ToUint8Array } from '../../../encoding/base64'; -import * as pkcs5 from '../../pkcs5'; import { keyFromPublic, keyFromPrivate, getIndutnyCurve } from './indutnyKey'; const webCrypto = util.getWebCrypto(); @@ -52,7 +52,7 @@ const nodeCrypto = util.getNodeCrypto(); * @returns {Promise} whether params are valid * @async */ -export async function validateParams(oid, Q, d) { +async function validateParams(oid, Q, d) { return validateStandardParams(enums.publicKey.ecdh, oid, Q, d); } @@ -62,11 +62,36 @@ function buildEcdhParam(public_algo, oid, kdfParams, fingerprint) { oid.write(), new Uint8Array([public_algo]), kdfParams.write(), - util.strToUint8Array("Anonymous Sender "), + util.str_to_Uint8Array("Anonymous Sender "), fingerprint.subarray(0, 20) ]); } +/** + * Parses MPI params and returns them as byte arrays of fixed length + * @param {Array} params key parameters + * @returns {Object} parameters in the form + * { oid, kdfParams, d: Uint8Array, Q: Uint8Array } + */ +function parseParams(params) { + if (params.length < 3 || params.length > 4) { + throw new Error('Unexpected number of parameters'); + } + + const oid = params[0]; + const curve = new Curve(oid); + const parsedParams = { oid }; + // The public point never has leading zeros, as it is prefixed by 0x40 or 0x04 + parsedParams.Q = params[1].toUint8Array(); + parsedParams.kdfParams = params[2]; + + if (params.length === 4) { + parsedParams.d = params[3].toUint8Array('be', curve.payloadSize); + } + + return parsedParams; +} + // Key Derivation Function (RFC 6637) async function kdf(hash_algo, X, length, param, stripLeading = false, stripTrailing = false) { // Note: X is little endian for Curve25519, big-endian for all others. @@ -102,7 +127,7 @@ async function kdf(hash_algo, X, length, param, stripLeading = false, stripTrail async function genPublicEphemeralKey(curve, Q) { switch (curve.type) { case 'curve25519': { - const d = await getRandomBytes(32); + const d = await random.getRandomBytes(32); const { secretKey, sharedKey } = await genPrivateEphemeralKey(curve, Q, null, d); let { publicKey } = nacl.box.keyPair.fromSecretKey(secretKey); publicKey = util.concatUint8Array([new Uint8Array([0x40]), publicKey]); @@ -113,7 +138,7 @@ async function genPublicEphemeralKey(curve, Q) { try { return await webPublicEphemeralKey(curve, Q); } catch (err) { - util.printDebugError(err); + util.print_debug_error(err); } } break; @@ -128,21 +153,19 @@ async function genPublicEphemeralKey(curve, Q) { * * @param {module:type/oid} oid Elliptic curve object identifier * @param {module:type/kdf_params} kdfParams KDF params including cipher and algorithm to use - * @param {Uint8Array} data Unpadded session key data + * @param {module:type/mpi} m Value derived from session key (RFC 6637) * @param {Uint8Array} Q Recipient public key * @param {Uint8Array} fingerprint Recipient fingerprint * @returns {Promise<{publicKey: Uint8Array, wrappedKey: Uint8Array}>} * @async */ -export async function encrypt(oid, kdfParams, data, Q, fingerprint) { - const m = pkcs5.encode(data); - +async function encrypt(oid, kdfParams, m, Q, fingerprint) { const curve = new Curve(oid); const { publicKey, sharedKey } = await genPublicEphemeralKey(curve, Q); const param = buildEcdhParam(enums.publicKey.ecdh, oid, kdfParams, fingerprint); const cipher_algo = enums.read(enums.symmetric, kdfParams.cipher); const Z = await kdf(kdfParams.hash, sharedKey, cipher[cipher_algo].keySize, param); - const wrappedKey = aes_kw.wrap(Z, m); + const wrappedKey = aes_kw.wrap(Z, m.toString()); return { publicKey, wrappedKey }; } @@ -173,7 +196,7 @@ async function genPrivateEphemeralKey(curve, V, Q, d) { try { return await webPrivateEphemeralKey(curve, V, Q, d); } catch (err) { - util.printDebugError(err); + util.print_debug_error(err); } } break; @@ -193,10 +216,10 @@ async function genPrivateEphemeralKey(curve, V, Q, d) { * @param {Uint8Array} Q Recipient public key * @param {Uint8Array} d Recipient private key * @param {Uint8Array} fingerprint Recipient fingerprint - * @returns {Promise} Value derived from session key + * @returns {Promise} Value derived from session key * @async */ -export async function decrypt(oid, kdfParams, V, C, Q, d, fingerprint) { +async function decrypt(oid, kdfParams, V, C, Q, d, fingerprint) { const curve = new Curve(oid); const { sharedKey } = await genPrivateEphemeralKey(curve, V, Q, d); const param = buildEcdhParam(enums.publicKey.ecdh, oid, kdfParams, fingerprint); @@ -206,7 +229,7 @@ export async function decrypt(oid, kdfParams, V, C, Q, d, fingerprint) { try { // Work around old go crypto bug and old OpenPGP.js bug, respectively. const Z = await kdf(kdfParams.hash, sharedKey, cipher[cipher_algo].keySize, param, i === 1, i === 2); - return pkcs5.decode(aes_kw.unwrap(Z, C)); + return new BN(aes_kw.unwrap(Z, C)); } catch (e) { err = e; } @@ -263,7 +286,7 @@ async function webPrivateEphemeralKey(curve, V, Q, d) { ); [S, secret] = await Promise.all([S, secret]); const sharedKey = new Uint8Array(S); - const secretKey = b64ToUint8Array(secret.d, true); + const secretKey = util.b64_to_Uint8Array(secret.d, true); return { secretKey, sharedKey }; } @@ -387,3 +410,5 @@ async function nodePublicEphemeralKey(curve, Q) { const publicKey = new Uint8Array(sender.getPublicKey()); return { publicKey, sharedKey }; } + +export default { encrypt, decrypt, genPublicEphemeralKey, genPrivateEphemeralKey, buildEcdhParam, kdf, webPublicEphemeralKey, webPrivateEphemeralKey, ellipticPublicEphemeralKey, ellipticPrivateEphemeralKey, nodePublicEphemeralKey, nodePrivateEphemeralKey, validateParams, parseParams }; diff --git a/src/crypto/public_key/elliptic/ecdsa.js b/src/crypto/public_key/elliptic/ecdsa.js index 6dacca49..40ecbf1e 100644 --- a/src/crypto/public_key/elliptic/ecdsa.js +++ b/src/crypto/public_key/elliptic/ecdsa.js @@ -17,6 +17,7 @@ /** * @fileoverview Implementation of ECDSA following RFC6637 for Openpgpjs + * @requires bn.js * @requires web-stream-tools * @requires enums * @requires util @@ -24,11 +25,12 @@ * @module crypto/public_key/elliptic/ecdsa */ +import BN from 'bn.js'; import enums from '../../../enums'; import util from '../../../util'; -import { getRandomBytes } from '../../random'; +import random from '../../random'; import hash from '../../hash'; -import { Curve, webCurves, privateToJwk, rawPublicToJwk, validateStandardParams } from './curves'; +import Curve, { webCurves, privateToJwk, rawPublicToJwk, validateStandardParams } from './curves'; import { getIndutnyCurve, keyFromPrivate, keyFromPublic } from './indutnyKey'; const webCrypto = util.getWebCrypto(); @@ -46,7 +48,7 @@ const nodeCrypto = util.getNodeCrypto(); * s: Uint8Array}} Signature of the message * @async */ -export async function sign(oid, hash_algo, message, publicKey, privateKey, hashed) { +async function sign(oid, hash_algo, message, publicKey, privateKey, hashed) { const curve = new Curve(oid); if (message && !util.isStream(message)) { const keyPair = { publicKey, privateKey }; @@ -63,7 +65,7 @@ export async function sign(oid, hash_algo, message, publicKey, privateKey, hashe if (curve.name !== 'p521' && (err.name === 'DataError' || err.name === 'OperationError')) { throw err; } - util.printDebugError("Browser did not support signing: " + err.message); + util.print_debug_error("Browser did not support verifying: " + err.message); } break; } @@ -91,7 +93,7 @@ export async function sign(oid, hash_algo, message, publicKey, privateKey, hashe * @returns {Boolean} * @async */ -export async function verify(oid, hash_algo, signature, message, publicKey, hashed) { +async function verify(oid, hash_algo, signature, message, publicKey, hashed) { const curve = new Curve(oid); if (message && !util.isStream(message)) { switch (curve.type) { @@ -101,12 +103,12 @@ export async function verify(oid, hash_algo, signature, message, publicKey, hash return await webVerify(curve, hash_algo, signature, message, publicKey); } catch (err) { // We do not fallback if the error is related to key integrity - // Unfortunately Safari does not support p521 and throws a DataError when using it + // Unfortunaley Safari does not support p521 and throws a DataError when using it // So we need to always fallback for that curve if (curve.name !== 'p521' && (err.name === 'DataError' || err.name === 'OperationError')) { throw err; } - util.printDebugError("Browser did not support verifying: " + err.message); + util.print_debug_error("Browser did not support verifying: " + err.message); } break; case 'node': @@ -125,7 +127,7 @@ export async function verify(oid, hash_algo, signature, message, publicKey, hash * @returns {Promise} whether params are valid * @async */ -export async function validateParams(oid, Q, d) { +async function validateParams(oid, Q, d) { const curve = new Curve(oid); // Reject curves x25519 and ed25519 if (curve.keyType !== enums.publicKey.ecdsa) { @@ -137,7 +139,7 @@ export async function validateParams(oid, Q, d) { switch (curve.type) { case 'web': case 'node': { - const message = await getRandomBytes(8); + const message = await random.getRandomBytes(8); const hashAlgo = enums.hash.sha256; const hashed = await hash.digest(hashAlgo, message); try { @@ -152,6 +154,32 @@ export async function validateParams(oid, Q, d) { } } +/** + * Parses MPI params and returns them as byte arrays of fixed length + * @param {Array} params key parameters + * @returns {Object} parameters in the form + * { oid, d: Uint8Array, Q: Uint8Array } + */ +function parseParams(params) { + if (params.length < 2 || params.length > 3) { + throw new Error('Unexpected number of parameters'); + } + + const oid = params[0]; + const curve = new Curve(oid); + const parsedParams = { oid }; + // The public point never has leading zeros, as it is prefixed by 0x40 or 0x04 + parsedParams.Q = params[1].toUint8Array(); + if (params.length === 3) { + parsedParams.d = params[2].toUint8Array('be', curve.payloadSize); + } + + return parsedParams; +} + + +export default { sign, verify, ellipticVerify, ellipticSign, validateParams, parseParams }; + ////////////////////////// // // @@ -207,6 +235,7 @@ async function webSign(curve, hash_algo, message, keyPair) { } async function webVerify(curve, hash_algo, { r, s }, message, publicKey) { + const len = curve.payloadSize; const jwk = rawPublicToJwk(curve.payloadSize, webCurves[curve.name], publicKey); const key = await webCrypto.importKey( "jwk", @@ -220,7 +249,10 @@ async function webVerify(curve, hash_algo, { r, s }, message, publicKey) { ["verify"] ); - const signature = util.concatUint8Array([r, s]).buffer; + const signature = util.concatUint8Array([ + new Uint8Array(len - r.length), r, + new Uint8Array(len - s.length), s + ]).buffer; return webCrypto.verify( { @@ -251,8 +283,6 @@ async function nodeSign(curve, hash_algo, message, keyPair) { } async function nodeVerify(curve, hash_algo, { r, s }, message, publicKey) { - const { default: BN } = await import('bn.js'); - const verify = nodeCrypto.createVerify(enums.read(enums.hash, hash_algo)); verify.write(message); verify.end(); diff --git a/src/crypto/public_key/elliptic/eddsa.js b/src/crypto/public_key/elliptic/eddsa.js index f3845228..7dc158ec 100644 --- a/src/crypto/public_key/elliptic/eddsa.js +++ b/src/crypto/public_key/elliptic/eddsa.js @@ -38,17 +38,17 @@ nacl.hash = bytes => new Uint8Array(sha512().update(bytes).digest()); * @param {Uint8Array} publicKey Public key * @param {Uint8Array} privateKey Private key used to sign the message * @param {Uint8Array} hashed The hashed message - * @returns {{r: Uint8Array, - * s: Uint8Array}} Signature of the message + * @returns {{R: Uint8Array, + * S: Uint8Array}} Signature of the message * @async */ -export async function sign(oid, hash_algo, message, publicKey, privateKey, hashed) { +async function sign(oid, hash_algo, message, publicKey, privateKey, hashed) { const secretKey = util.concatUint8Array([privateKey, publicKey.subarray(1)]); const signature = nacl.sign.detached(hashed, secretKey); // EdDSA signature params are returned in little-endian format return { - r: signature.subarray(0, 32), - s: signature.subarray(32) + R: signature.subarray(0, 32), + S: signature.subarray(32) }; } @@ -56,18 +56,19 @@ export async function sign(oid, hash_algo, message, publicKey, privateKey, hashe * Verifies if a signature is valid for a message * @param {module:type/oid} oid Elliptic curve object identifier * @param {module:enums.hash} hash_algo Hash algorithm used in the signature - * @param {{r: Uint8Array, - s: Uint8Array}} signature Signature to verify the message + * @param {{R: Uint8Array, + S: Uint8Array}} signature Signature to verify the message * @param {Uint8Array} m Message to verify * @param {Uint8Array} publicKey Public key used to verify the message * @param {Uint8Array} hashed The hashed message * @returns {Boolean} * @async */ -export async function verify(oid, hash_algo, { r, s }, m, publicKey, hashed) { - const signature = util.concatUint8Array([r, s]); +async function verify(oid, hash_algo, { R, S }, m, publicKey, hashed) { + const signature = util.concatUint8Array([R, S]); return nacl.sign.detached.verify(hashed, signature, publicKey.subarray(1)); } + /** * Validate EdDSA parameters * @param {module:type/oid} oid Elliptic curve object identifier @@ -76,7 +77,7 @@ export async function verify(oid, hash_algo, { r, s }, m, publicKey, hashed) { * @returns {Promise} whether params are valid * @async */ -export async function validateParams(oid, Q, k) { +async function validateParams(oid, Q, k) { // Check whether the given curve is supported if (oid.getName() !== 'ed25519') { return false; @@ -90,3 +91,29 @@ export async function validateParams(oid, Q, k) { const dG = new Uint8Array([0x40, ...publicKey]); // Add public key prefix return util.equalsUint8Array(Q, dG); } + +/** + * Parses MPI params and returns them as byte arrays of fixed length + * @param {Array} params key parameters + * @returns {Object} parameters in the form + * { oid, seed: Uint8Array, Q: Uint8Array } + */ +function parseParams(params) { + if (params.length < 2 || params.length > 3) { + throw new Error('Unexpected number of parameters'); + } + + const parsedParams = { + oid: params[0], + Q: params[1].toUint8Array('be', 33) + }; + + if (params.length === 3) { + parsedParams.seed = params[2].toUint8Array('be', 32); + } + + return parsedParams; +} + + +export default { sign, verify, validateParams, parseParams }; diff --git a/src/crypto/public_key/elliptic/index.js b/src/crypto/public_key/elliptic/index.js index 4fac26fd..aa271fff 100644 --- a/src/crypto/public_key/elliptic/index.js +++ b/src/crypto/public_key/elliptic/index.js @@ -24,11 +24,11 @@ * @module crypto/public_key/elliptic */ -import { Curve, generate, getPreferredHashAlgo } from './curves'; -import * as ecdsa from './ecdsa'; -import * as eddsa from './eddsa'; -import * as ecdh from './ecdh'; +import Curve, { generate, getPreferredHashAlgo } from './curves'; +import ecdsa from './ecdsa'; +import eddsa from './eddsa'; +import ecdh from './ecdh'; -export { +export default { Curve, ecdh, ecdsa, eddsa, generate, getPreferredHashAlgo }; diff --git a/src/crypto/public_key/elliptic/indutnyKey.js b/src/crypto/public_key/elliptic/indutnyKey.js index c1022524..de947cb8 100644 --- a/src/crypto/public_key/elliptic/indutnyKey.js +++ b/src/crypto/public_key/elliptic/indutnyKey.js @@ -22,7 +22,9 @@ * @module crypto/public_key/elliptic/indutnyKey */ +import { loadScript, dl } from '../../../lightweight_helper'; import config from '../../../config'; +import util from '../../../util'; export function keyFromPrivate(indutnyCurve, priv) { const keyPair = indutnyCurve.keyPair({ priv: priv }); @@ -37,10 +39,47 @@ export function keyFromPublic(indutnyCurve, pub) { return keyPair; } +/** + * Load elliptic on demand to global.openpgp.elliptic + * @returns {Promise} + */ +async function loadEllipticPromise() { + const path = config.indutny_elliptic_path; + const options = config.indutny_elliptic_fetch_options; + const ellipticDlPromise = dl(path, options).catch(() => dl(path, options)); + const ellipticContents = await ellipticDlPromise; + const mainUrl = URL.createObjectURL(new Blob([ellipticContents], { type: 'text/javascript' })); + await loadScript(mainUrl); + URL.revokeObjectURL(mainUrl); + if (!global.openpgp.elliptic) { + throw new Error('Elliptic library failed to load correctly'); + } + return global.openpgp.elliptic; +} + +let ellipticPromise; + +function loadElliptic() { + if (!config.external_indutny_elliptic) { + return require('elliptic'); + } + if (util.detectNode()) { + // eslint-disable-next-line + return require(config.indutny_elliptic_path); + } + if (!ellipticPromise) { + ellipticPromise = loadEllipticPromise().catch(e => { + ellipticPromise = undefined; + throw e; + }); + } + return ellipticPromise; +} + export async function getIndutnyCurve(name) { - if (!config.useIndutnyElliptic) { + if (!config.use_indutny_elliptic) { throw new Error('This curve is only supported in the full build of OpenPGP.js'); } - const { default: elliptic } = await import('elliptic'); + const elliptic = await loadElliptic(); return new elliptic.ec(name); } diff --git a/src/crypto/public_key/index.js b/src/crypto/public_key/index.js index e9a81025..4e2edbcf 100644 --- a/src/crypto/public_key/index.js +++ b/src/crypto/public_key/index.js @@ -9,10 +9,10 @@ */ import nacl from 'tweetnacl/nacl-fast-light.js'; -import * as rsa from './rsa'; -import * as elgamal from './elgamal'; -import * as elliptic from './elliptic'; -import * as dsa from './dsa'; +import rsa from './rsa'; +import elgamal from './elgamal'; +import elliptic from './elliptic'; +import dsa from './dsa'; export default { /** @see module:crypto/public_key/rsa */ diff --git a/src/crypto/public_key/prime.js b/src/crypto/public_key/prime.js index d2f39a4a..1e657236 100644 --- a/src/crypto/public_key/prime.js +++ b/src/crypto/public_key/prime.js @@ -17,26 +17,29 @@ /** * @fileoverview Algorithms for probabilistic random prime generation + * @requires bn.js * @requires crypto/random * @module crypto/public_key/prime */ -import util from '../../util'; -import { getRandomBigInteger } from '../random'; +import BN from 'bn.js'; +import random from '../random'; + +export default { + randomProbablePrime, isProbablePrime, fermat, millerRabin, divisionTest +}; /** * Probabilistic random number generator * @param {Integer} bits Bit length of the prime - * @param {BigInteger} e Optional RSA exponent to check against the prime + * @param {BN} e Optional RSA exponent to check against the prime * @param {Integer} k Optional number of iterations of Miller-Rabin test - * @returns BigInteger + * @returns BN * @async */ -export async function randomProbablePrime(bits, e, k) { - const BigInteger = await util.getBigInteger(); - const one = new BigInteger(1); - const min = one.leftShift(new BigInteger(bits - 1)); - const thirty = new BigInteger(30); +async function randomProbablePrime(bits, e, k) { + const min = new BN(1).shln(bits - 1); + const thirty = new BN(30); /* * We can avoid any multiples of 3 and 5 by looking at n mod 30 * n mod 30 = 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @@ -45,15 +48,15 @@ export async function randomProbablePrime(bits, e, k) { */ const adds = [1, 6, 5, 4, 3, 2, 1, 4, 3, 2, 1, 2, 1, 4, 3, 2, 1, 2, 1, 4, 3, 2, 1, 6, 5, 4, 3, 2, 1, 2]; - const n = await getRandomBigInteger(min, min.leftShift(one)); + let n = await random.getRandomBN(min, min.shln(1)); let i = n.mod(thirty).toNumber(); do { - n.iadd(new BigInteger(adds[i])); + n.iaddn(adds[i]); i = (i + adds[i]) % adds.length; // If reached the maximum, go back to the minimum. if (n.bitLength() > bits) { - n.imod(min.leftShift(one)).iadd(min); + n = n.mod(min.shln(1)).iadd(min); i = n.mod(thirty).toNumber(); } } while (!await isProbablePrime(n, e, k)); @@ -62,20 +65,20 @@ export async function randomProbablePrime(bits, e, k) { /** * Probabilistic primality testing - * @param {BigInteger} n Number to test - * @param {BigInteger} e Optional RSA exponent to check against the prime - * @param {Integer} k Optional number of iterations of Miller-Rabin test + * @param {BN} n Number to test + * @param {BN} e Optional RSA exponent to check against the prime + * @param {Integer} k Optional number of iterations of Miller-Rabin test * @returns {boolean} * @async */ -export async function isProbablePrime(n, e, k) { - if (e && !n.dec().gcd(e).isOne()) { +async function isProbablePrime(n, e, k) { + if (e && !n.subn(1).gcd(e).eqn(1)) { return false; } - if (!await divisionTest(n)) { + if (!divisionTest(n)) { return false; } - if (!await fermat(n)) { + if (!fermat(n)) { return false; } if (!await millerRabin(n, k)) { @@ -88,26 +91,24 @@ export async function isProbablePrime(n, e, k) { /** * Tests whether n is probably prime or not using Fermat's test with b = 2. - * Fails if b^(n-1) mod n != 1. - * @param {BigInteger} n Number to test - * @param {BigInteger} b Optional Fermat test base + * Fails if b^(n-1) mod n === 1. + * @param {BN} n Number to test + * @param {Integer} b Optional Fermat test base * @returns {boolean} */ -export async function fermat(n, b) { - const BigInteger = await util.getBigInteger(); - b = b || new BigInteger(2); - return b.modExp(n.dec(), n).isOne(); +function fermat(n, b) { + b = b || new BN(2); + return b.toRed(BN.mont(n)).redPow(n.subn(1)).fromRed().cmpn(1) === 0; } -export async function divisionTest(n) { - const BigInteger = await util.getBigInteger(); - return smallPrimes.every(m => { - return n.mod(new BigInteger(m)) !== 0; +function divisionTest(n) { + return small_primes.every(m => { + return n.modn(m) !== 0; }); } // https://github.com/gpg/libgcrypt/blob/master/cipher/primegen.c -const smallPrimes = [ +const small_primes = [ 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, @@ -222,43 +223,45 @@ const smallPrimes = [ /** * Tests whether n is probably prime or not using the Miller-Rabin test. * See HAC Remark 4.28. - * @param {BigInteger} n Number to test - * @param {Integer} k Optional number of iterations of Miller-Rabin test - * @param {Function} rand Optional function to generate potential witnesses + * @param {BN} n Number to test + * @param {Integer} k Optional number of iterations of Miller-Rabin test + * @param {Function} rand Optional function to generate potential witnesses * @returns {boolean} * @async */ -export async function millerRabin(n, k, rand) { - const BigInteger = await util.getBigInteger(); +async function millerRabin(n, k, rand) { const len = n.bitLength(); + const red = BN.mont(n); + const rone = new BN(1).toRed(red); if (!k) { k = Math.max(1, (len / 48) | 0); } - const n1 = n.dec(); // n - 1 + const n1 = n.subn(1); + const rn1 = n1.toRed(red); // Find d and s, (n - 1) = (2 ^ s) * d; let s = 0; - while (!n1.getBit(s)) { s++; } - const d = n.rightShift(new BigInteger(s)); + while (!n1.testn(s)) { s++; } + const d = n.shrn(s); for (; k > 0; k--) { - const a = rand ? rand() : await getRandomBigInteger(new BigInteger(2), n1); + const a = rand ? rand() : await random.getRandomBN(new BN(2), n1); - let x = a.modExp(d, n); - if (x.isOne() || x.equal(n1)) { + let x = a.toRed(red).redPow(d); + if (x.eq(rone) || x.eq(rn1)) { continue; } let i; for (i = 1; i < s; i++) { - x = x.mul(x).mod(n); + x = x.redSqr(); - if (x.isOne()) { + if (x.eq(rone)) { return false; } - if (x.equal(n1)) { + if (x.eq(rn1)) { break; } } diff --git a/src/crypto/public_key/rsa.js b/src/crypto/public_key/rsa.js index 9bb7e2f2..7d8ea6b1 100644 --- a/src/crypto/public_key/rsa.js +++ b/src/crypto/public_key/rsa.js @@ -17,6 +17,7 @@ /** * @fileoverview RSA implementation + * @requires bn.js * @requires crypto/public_key/prime * @requires crypto/random * @requires config @@ -24,13 +25,14 @@ * @module crypto/public_key/rsa */ -import { randomProbablePrime } from './prime'; -import { getRandomBigInteger } from '../random'; +import BN from 'bn.js'; +import prime from './prime'; +import random from '../random'; import config from '../../config'; import util from '../../util'; -import { uint8ArrayToB64, b64ToUint8Array } from '../../encoding/base64'; -import { emsaEncode, emeEncode, emeDecode } from '../pkcs1'; +import pkcs1 from '../pkcs1'; import enums from '../../enums'; +import type_mpi from '../../type/mpi'; const webCrypto = util.getWebCrypto(); const nodeCrypto = util.getNodeCrypto(); @@ -74,480 +76,480 @@ const RSAPublicKey = util.detectNode() ? asn1.define('RSAPubliceKey', function ( }) : undefined; /* eslint-enable no-invalid-this */ -/** Create signature - * @param {module:enums.hash} hash_algo Hash algorithm - * @param {Uint8Array} data message - * @param {Uint8Array} n RSA public modulus - * @param {Uint8Array} e RSA public exponent - * @param {Uint8Array} d RSA private exponent - * @param {Uint8Array} p RSA private prime p - * @param {Uint8Array} q RSA private prime q - * @param {Uint8Array} u RSA private coefficient - * @param {Uint8Array} hashed hashed message - * @returns {Uint8Array} RSA Signature - * @async - */ -export async function sign(hash_algo, data, n, e, d, p, q, u, hashed) { - if (data && !util.isStream(data)) { - if (util.getWebCrypto()) { - try { - return await webSign(enums.read(enums.webHash, hash_algo), data, n, e, d, p, q, u); - } catch (err) { - util.printDebugError(err); +export default { + /** Create signature + * @param {module:enums.hash} hash_algo Hash algorithm + * @param {Uint8Array} data message + * @param {Uint8Array} n RSA public modulus + * @param {Uint8Array} e RSA public exponent + * @param {Uint8Array} d RSA private exponent + * @param {Uint8Array} p RSA private prime p + * @param {Uint8Array} q RSA private prime q + * @param {Uint8Array} u RSA private coefficient + * @param {Uint8Array} hashed hashed message + * @returns {Uint8Array} RSA Signature + * @async + */ + sign: async function(hash_algo, data, n, e, d, p, q, u, hashed) { + if (data && !util.isStream(data)) { + if (util.getWebCrypto()) { + try { + return await this.webSign(enums.read(enums.webHash, hash_algo), data, n, e, d, p, q, u); + } catch (err) { + util.print_debug_error(err); + } + } else if (util.getNodeCrypto()) { + return this.nodeSign(hash_algo, data, n, e, d, p, q, u); } - } else if (util.getNodeCrypto()) { - return nodeSign(hash_algo, data, n, e, d, p, q, u); } - } - return bnSign(hash_algo, n, d, hashed); -} + return this.bnSign(hash_algo, n, d, hashed); + }, -/** - * Verify signature - * @param {module:enums.hash} hash_algo Hash algorithm - * @param {Uint8Array} data message - * @param {Uint8Array} s signature - * @param {Uint8Array} n RSA public modulus - * @param {Uint8Array} e RSA public exponent - * @param {Uint8Array} hashed hashed message - * @returns {Boolean} - * @async - */ -export async function verify(hash_algo, data, s, n, e, hashed) { - if (data && !util.isStream(data)) { - if (util.getWebCrypto()) { - try { - return await webVerify(enums.read(enums.webHash, hash_algo), data, s, n, e); - } catch (err) { - util.printDebugError(err); + /** + * Verify signature + * @param {module:enums.hash} hash_algo Hash algorithm + * @param {Uint8Array} data message + * @param {Uint8Array} s signature + * @param {Uint8Array} n RSA public modulus + * @param {Uint8Array} e RSA public exponent + * @param {Uint8Array} hashed hashed message + * @returns {Boolean} + * @async + */ + verify: async function(hash_algo, data, s, n, e, hashed) { + if (data && !util.isStream(data)) { + if (util.getWebCrypto()) { + try { + return await this.webVerify(enums.read(enums.webHash, hash_algo), data, s, n, e); + } catch (err) { + util.print_debug_error(err); + } + } else if (util.getNodeCrypto()) { + return this.nodeVerify(hash_algo, data, s, n, e); } - } else if (util.getNodeCrypto()) { - return nodeVerify(hash_algo, data, s, n, e); } - } - return bnVerify(hash_algo, s, n, e, hashed); -} - -/** - * Encrypt message - * @param {Uint8Array} data message - * @param {Uint8Array} n RSA public modulus - * @param {Uint8Array} e RSA public exponent - * @returns {Uint8Array} RSA Ciphertext - * @async - */ -export async function encrypt(data, n, e) { - if (util.getNodeCrypto()) { - return nodeEncrypt(data, n, e); - } - return bnEncrypt(data, n, e); -} + return this.bnVerify(hash_algo, s, n, e, hashed); + }, -/** - * Decrypt RSA message - * @param {Uint8Array} m message - * @param {Uint8Array} n RSA public modulus - * @param {Uint8Array} e RSA public exponent - * @param {Uint8Array} d RSA private exponent - * @param {Uint8Array} p RSA private prime p - * @param {Uint8Array} q RSA private prime q - * @param {Uint8Array} u RSA private coefficient - * @returns {String} RSA Plaintext - * @async - */ -export async function decrypt(data, n, e, d, p, q, u) { - if (util.getNodeCrypto()) { - return nodeDecrypt(data, n, e, d, p, q, u); - } - return bnDecrypt(data, n, e, d, p, q, u); -} - -/** - * Generate a new random private key B bits long with public exponent E. - * - * When possible, webCrypto or nodeCrypto is used. Otherwise, primes are generated using - * 40 rounds of the Miller-Rabin probabilistic random prime generation algorithm. - * @see module:crypto/public_key/prime - * @param {Integer} bits RSA bit length - * @param {Integer} e RSA public exponent - * @returns {{n, e, d, - * p, q ,u: Uint8Array}} RSA public modulus, RSA public exponent, RSA private exponent, - * RSA private prime p, RSA private prime q, u = p ** -1 mod q - * @async - */ -export async function generate(bits, e) { - const BigInteger = await util.getBigInteger(); - - e = new BigInteger(e); - - // Native RSA keygen using Web Crypto - if (util.getWebCrypto()) { - let keyPair; - let keyGenOpt; - if ((globalThis.crypto && globalThis.crypto.subtle) || globalThis.msCrypto) { - // current standard spec - keyGenOpt = { - name: 'RSASSA-PKCS1-v1_5', - modulusLength: bits, // the specified keysize in bits - publicExponent: e.toUint8Array(), // take three bytes (max 65537) for exponent - hash: { - name: 'SHA-1' // not required for actual RSA keys, but for crypto api 'sign' and 'verify' - } - }; - keyPair = webCrypto.generateKey(keyGenOpt, true, ['sign', 'verify']); - keyPair = await promisifyIE11Op(keyPair, 'Error generating RSA key pair.'); - } else if (globalThis.crypto && globalThis.crypto.webkitSubtle) { - // outdated spec implemented by old Webkit - keyGenOpt = { - name: 'RSA-OAEP', - modulusLength: bits, // the specified keysize in bits - publicExponent: e.toUint8Array(), // take three bytes (max 65537) for exponent - hash: { - name: 'SHA-1' // not required for actual RSA keys, but for crypto api 'sign' and 'verify' - } - }; - keyPair = await webCrypto.generateKey(keyGenOpt, true, ['encrypt', 'decrypt']); - } else { - throw new Error('Unknown WebCrypto implementation'); + /** + * Encrypt message + * @param {Uint8Array} data message + * @param {Uint8Array} n RSA public modulus + * @param {Uint8Array} e RSA public exponent + * @returns {Uint8Array} RSA Ciphertext + * @async + */ + encrypt: async function(data, n, e) { + if (util.getNodeCrypto()) { + return this.nodeEncrypt(data, n, e); } + return this.bnEncrypt(data, n, e); + }, - // export the generated keys as JsonWebKey (JWK) - // https://tools.ietf.org/html/draft-ietf-jose-json-web-key-33 - let jwk = webCrypto.exportKey('jwk', keyPair.privateKey); - jwk = await promisifyIE11Op(jwk, 'Error exporting RSA key pair.'); - - // parse raw ArrayBuffer bytes to jwk/json (WebKit/Safari/IE11 quirk) - if (jwk instanceof ArrayBuffer) { - jwk = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(jwk))); + /** + * Decrypt RSA message + * @param {Uint8Array} m message + * @param {Uint8Array} n RSA public modulus + * @param {Uint8Array} e RSA public exponent + * @param {Uint8Array} d RSA private exponent + * @param {Uint8Array} p RSA private prime p + * @param {Uint8Array} q RSA private prime q + * @param {Uint8Array} u RSA private coefficient + * @returns {String} RSA Plaintext + * @async + */ + decrypt: async function(data, n, e, d, p, q, u) { + if (util.getNodeCrypto()) { + return this.nodeDecrypt(data, n, e, d, p, q, u); } - // map JWK parameters to corresponding OpenPGP names - return { - n: b64ToUint8Array(jwk.n), - e: e.toUint8Array(), - d: b64ToUint8Array(jwk.d), - // switch p and q - p: b64ToUint8Array(jwk.q), - q: b64ToUint8Array(jwk.p), - // Since p and q are switched in places, u is the inverse of jwk.q - u: b64ToUint8Array(jwk.qi) - }; - } else if (util.getNodeCrypto() && nodeCrypto.generateKeyPair && RSAPrivateKey) { - const opts = { - modulusLength: bits, - publicExponent: e.toNumber(), - publicKeyEncoding: { type: 'pkcs1', format: 'der' }, - privateKeyEncoding: { type: 'pkcs1', format: 'der' } - }; - const prv = await new Promise((resolve, reject) => nodeCrypto.generateKeyPair('rsa', opts, (err, _, der) => { - if (err) { - reject(err); - } else { - resolve(RSAPrivateKey.decode(der, 'der')); - } - })); - /** - * OpenPGP spec differs from DER spec, DER: `u = (inverse of q) mod p`, OpenPGP: `u = (inverse of p) mod q`. - * @link https://tools.ietf.org/html/rfc3447#section-3.2 - * @link https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-08#section-5.6.1 - */ - return { - n: prv.modulus.toArrayLike(Uint8Array), - e: prv.publicExponent.toArrayLike(Uint8Array), - d: prv.privateExponent.toArrayLike(Uint8Array), - // switch p and q - p: prv.prime2.toArrayLike(Uint8Array), - q: prv.prime1.toArrayLike(Uint8Array), - // Since p and q are switched in places, we can keep u as defined by DER - u: prv.coefficient.toArrayLike(Uint8Array) - }; - } - - // RSA keygen fallback using 40 iterations of the Miller-Rabin test - // See https://stackoverflow.com/a/6330138 for justification - // Also see section C.3 here: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST - let q = await randomProbablePrime(bits - (bits >> 1), e, 40); - let p = await randomProbablePrime(bits >> 1, e, 40); - - if (q.lt(p)) { - [p, q] = [q, p]; - } - const phi = p.dec().imul(q.dec()); - return { - n: p.mul(q).toUint8Array(), - e: e.toUint8Array(), - d: e.modInv(phi).toUint8Array(), - p: p.toUint8Array(), - q: q.toUint8Array(), - // dp: d.mod(p.subn(1)), - // dq: d.mod(q.subn(1)), - u: p.modInv(q).toUint8Array() - }; -} - -/** - * Validate RSA parameters - * @param {Uint8Array} n RSA public modulus - * @param {Uint8Array} e RSA public exponent - * @param {Uint8Array} d RSA private exponent - * @param {Uint8Array} p RSA private prime p - * @param {Uint8Array} q RSA private prime q - * @param {Uint8Array} u RSA inverse of p w.r.t. q - * @returns {Promise} whether params are valid - * @async - */ -export async function validateParams(n, e, d, p, q, u) { - const BigInteger = await util.getBigInteger(); - n = new BigInteger(n); - p = new BigInteger(p); - q = new BigInteger(q); - - // expect pq = n - if (!p.mul(q).equal(n)) { - return false; - } - - const two = new BigInteger(2); - // expect p*u = 1 mod q - u = new BigInteger(u); - if (!p.mul(u).mod(q).isOne()) { - return false; - } + return this.bnDecrypt(data, n, e, d, p, q, u); + }, - e = new BigInteger(e); - d = new BigInteger(d); /** - * In RSA pkcs#1 the exponents (d, e) are inverses modulo lcm(p-1, q-1) - * We check that [de = 1 mod (p-1)] and [de = 1 mod (q-1)] - * By CRT on coprime factors of (p-1, q-1) it follows that [de = 1 mod lcm(p-1, q-1)] + * Generate a new random private key B bits long with public exponent E. * - * We blind the multiplication with r, and check that rde = r mod lcm(p-1, q-1) + * When possible, webCrypto or nodeCrypto is used. Otherwise, primes are generated using + * 40 rounds of the Miller-Rabin probabilistic random prime generation algorithm. + * @see module:crypto/public_key/prime + * @param {Integer} B RSA bit length + * @param {String} E RSA public exponent in hex string + * @returns {{n: BN, e: BN, d: BN, + * p: BN, q: BN, u: BN}} RSA public modulus, RSA public exponent, RSA private exponent, + * RSA private prime p, RSA private prime q, u = q ** -1 mod p + * @async */ - const nSizeOver3 = new BigInteger(Math.floor(n.bitLength() / 3)); - const r = await getRandomBigInteger(two, two.leftShift(nSizeOver3)); // r in [ 2, 2^{|n|/3} ) < p and q - const rde = r.mul(d).mul(e); - - const areInverses = rde.mod(p.dec()).equal(r) && rde.mod(q.dec()).equal(r); - if (!areInverses) { - return false; - } + generate: async function(B, E) { + let key; + E = new BN(E, 16); - return true; -} - -async function bnSign(hash_algo, n, d, hashed) { - const BigInteger = await util.getBigInteger(); - n = new BigInteger(n); - const m = new BigInteger(await emsaEncode(hash_algo, hashed, n.byteLength())); - d = new BigInteger(d); - if (m.gte(n)) { - throw new Error('Message size cannot exceed modulus size'); - } - return m.modExp(d, n).toUint8Array('be', n.byteLength()); -} + // Native RSA keygen using Web Crypto + if (util.getWebCrypto()) { + let keyPair; + let keyGenOpt; + if ((global.crypto && global.crypto.subtle) || global.msCrypto) { + // current standard spec + keyGenOpt = { + name: 'RSASSA-PKCS1-v1_5', + modulusLength: B, // the specified keysize in bits + publicExponent: E.toArrayLike(Uint8Array), // take three bytes (max 65537) for exponent + hash: { + name: 'SHA-1' // not required for actual RSA keys, but for crypto api 'sign' and 'verify' + } + }; + keyPair = webCrypto.generateKey(keyGenOpt, true, ['sign', 'verify']); + keyPair = await promisifyIE11Op(keyPair, 'Error generating RSA key pair.'); + } else if (global.crypto && global.crypto.webkitSubtle) { + // outdated spec implemented by old Webkit + keyGenOpt = { + name: 'RSA-OAEP', + modulusLength: B, // the specified keysize in bits + publicExponent: E.toArrayLike(Uint8Array), // take three bytes (max 65537) for exponent + hash: { + name: 'SHA-1' // not required for actual RSA keys, but for crypto api 'sign' and 'verify' + } + }; + keyPair = await webCrypto.generateKey(keyGenOpt, true, ['encrypt', 'decrypt']); + } else { + throw new Error('Unknown WebCrypto implementation'); + } -async function webSign(hash_name, data, n, e, d, p, q, u) { - /** OpenPGP keys require that p < q, and Safari Web Crypto requires that p > q. - * We swap them in privateToJwk, so it usually works out, but nevertheless, - * not all OpenPGP keys are compatible with this requirement. - * OpenPGP.js used to generate RSA keys the wrong way around (p > q), and still - * does if the underlying Web Crypto does so (e.g. old MS Edge 50% of the time). - */ - const jwk = await privateToJwk(n, e, d, p, q, u); - const algo = { - name: "RSASSA-PKCS1-v1_5", - hash: { name: hash_name } - }; - const key = await webCrypto.importKey("jwk", jwk, algo, false, ["sign"]); - // add hash field for ms edge support - return new Uint8Array(await webCrypto.sign({ "name": "RSASSA-PKCS1-v1_5", "hash": hash_name }, key, data)); -} + // export the generated keys as JsonWebKey (JWK) + // https://tools.ietf.org/html/draft-ietf-jose-json-web-key-33 + let jwk = webCrypto.exportKey('jwk', keyPair.privateKey); + jwk = await promisifyIE11Op(jwk, 'Error exporting RSA key pair.'); -async function nodeSign(hash_algo, data, n, e, d, p, q, u) { - const { default: BN } = await import('bn.js'); - const pBNum = new BN(p); - const qBNum = new BN(q); - const dBNum = new BN(d); - const dq = dBNum.mod(qBNum.subn(1)); // d mod (q-1) - const dp = dBNum.mod(pBNum.subn(1)); // d mod (p-1) - const sign = nodeCrypto.createSign(enums.read(enums.hash, hash_algo)); - sign.write(data); - sign.end(); - const keyObject = { - version: 0, - modulus: new BN(n), - publicExponent: new BN(e), - privateExponent: new BN(d), - // switch p and q - prime1: new BN(q), - prime2: new BN(p), - // switch dp and dq - exponent1: dq, - exponent2: dp, - coefficient: new BN(u) - }; - if (typeof nodeCrypto.createPrivateKey !== 'undefined') { //from version 11.6.0 Node supports der encoded key objects - const der = RSAPrivateKey.encode(keyObject, 'der'); - return new Uint8Array(sign.sign({ key: der, format: 'der', type: 'pkcs1' })); - } - const pem = RSAPrivateKey.encode(keyObject, 'pem', { - label: 'RSA PRIVATE KEY' - }); - return new Uint8Array(sign.sign(pem)); -} + // parse raw ArrayBuffer bytes to jwk/json (WebKit/Safari/IE11 quirk) + if (jwk instanceof ArrayBuffer) { + jwk = JSON.parse(String.fromCharCode.apply(null, new Uint8Array(jwk))); + } + // map JWK parameters to BN + key = {}; + key.n = new BN(util.b64_to_Uint8Array(jwk.n)); + key.e = E; + key.d = new BN(util.b64_to_Uint8Array(jwk.d)); + // switch p and q + key.p = new BN(util.b64_to_Uint8Array(jwk.q)); + key.q = new BN(util.b64_to_Uint8Array(jwk.p)); + // Since p and q are switched in places, we could keep u + key.u = new BN(util.b64_to_Uint8Array(jwk.qi)); + return key; + } else if (util.getNodeCrypto() && nodeCrypto.generateKeyPair && RSAPrivateKey) { + const opts = { + modulusLength: Number(B.toString(10)), + publicExponent: Number(E.toString(10)), + publicKeyEncoding: { type: 'pkcs1', format: 'der' }, + privateKeyEncoding: { type: 'pkcs1', format: 'der' } + }; + const prv = await new Promise((resolve, reject) => nodeCrypto.generateKeyPair('rsa', opts, (err, _, der) => { + if (err) { + reject(err); + } else { + resolve(RSAPrivateKey.decode(der, 'der')); + } + })); + /** PGP spec differs from DER spec, DER: `(inverse of q) mod p`, PGP: `(inverse of p) mod q`. + * @link https://tools.ietf.org/html/rfc3447#section-3.2 + * @link https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-08#section-5.6.1 + */ + return { + n: prv.modulus, + e: prv.publicExponent, + d: prv.privateExponent, + // switch p and q + p: prv.prime2, + q: prv.prime1, + // Since p and q are switched in places, we could keep u + u: prv.coefficient // PGP type of u + }; + } -async function bnVerify(hash_algo, s, n, e, hashed) { - const BigInteger = await util.getBigInteger(); - n = new BigInteger(n); - s = new BigInteger(s); - e = new BigInteger(e); - if (s.gte(n)) { - throw new Error('Signature size cannot exceed modulus size'); - } - const EM1 = s.modExp(e, n).toUint8Array('be', n.byteLength()); - const EM2 = await emsaEncode(hash_algo, hashed, n.byteLength()); - return util.equalsUint8Array(EM1, EM2); -} + // RSA keygen fallback using 40 iterations of the Miller-Rabin test + // See https://stackoverflow.com/a/6330138 for justification + // Also see section C.3 here: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST + let q = await prime.randomProbablePrime(B - (B >> 1), E, 40); + let p = await prime.randomProbablePrime(B >> 1, E, 40); -async function webVerify(hash_name, data, s, n, e) { - const jwk = publicToJwk(n, e); - const key = await webCrypto.importKey("jwk", jwk, { - name: "RSASSA-PKCS1-v1_5", - hash: { name: hash_name } - }, false, ["verify"]); - // add hash field for ms edge support - return webCrypto.verify({ "name": "RSASSA-PKCS1-v1_5", "hash": hash_name }, key, s, data); -} + if (q.cmp(p) < 0) { + [p, q] = [q, p]; + } -async function nodeVerify(hash_algo, data, s, n, e) { - const { default: BN } = await import('bn.js'); + const phi = p.subn(1).mul(q.subn(1)); + return { + n: p.mul(q), + e: E, + d: E.invm(phi), + p: p, + q: q, + // dp: d.mod(p.subn(1)), + // dq: d.mod(q.subn(1)), + u: p.invm(q) + }; + }, - const verify = nodeCrypto.createVerify(enums.read(enums.hash, hash_algo)); - verify.write(data); - verify.end(); - const keyObject = { - modulus: new BN(n), - publicExponent: new BN(e) - }; - let key; - if (typeof nodeCrypto.createPrivateKey !== 'undefined') { //from version 11.6.0 Node supports der encoded key objects - const der = RSAPublicKey.encode(keyObject, 'der'); - key = { key: der, format: 'der', type: 'pkcs1' }; - } else { - key = RSAPublicKey.encode(keyObject, 'pem', { - label: 'RSA PUBLIC KEY' - }); - } - try { - return await verify.verify(key, s); - } catch (err) { - return false; - } -} + /** + * Validate RSA parameters + * @param {Uint8Array} n RSA public modulus + * @param {Uint8Array} e RSA public exponent + * @param {Uint8Array} d RSA private exponent + * @param {Uint8Array} p RSA private prime p + * @param {Uint8Array} q RSA private prime q + * @param {Uint8Array} u RSA inverse of p w.r.t. q + * @returns {Promise} whether params are valid + * @async + */ + validateParams: async function (n, e, d, p, q, u) { + n = new BN(n); + p = new BN(p); + q = new BN(q); + + // expect pq = n + if (!p.mul(q).eq(n)) { + return false; + } -async function nodeEncrypt(data, n, e) { - const { default: BN } = await import('bn.js'); + const one = new BN(1); + const two = new BN(2); + // expect p*u = 1 mod q + u = new BN(u); + if (!p.mul(u).umod(q).eq(one)) { + return false; + } - const keyObject = { - modulus: new BN(n), - publicExponent: new BN(e) - }; - let key; - if (typeof nodeCrypto.createPrivateKey !== 'undefined') { - const der = RSAPublicKey.encode(keyObject, 'der'); - key = { key: der, format: 'der', type: 'pkcs1', padding: nodeCrypto.constants.RSA_PKCS1_PADDING }; - } else { - const pem = RSAPublicKey.encode(keyObject, 'pem', { - label: 'RSA PUBLIC KEY' - }); - key = { key: pem, padding: nodeCrypto.constants.RSA_PKCS1_PADDING }; - } - return new Uint8Array(nodeCrypto.publicEncrypt(key, data)); -} + e = new BN(e); + d = new BN(d); + /** + * In RSA pkcs#1 the exponents (d, e) are inverses modulo lcm(p-1, q-1) + * We check that [de = 1 mod (p-1)] and [de = 1 mod (q-1)] + * By CRT on coprime factors of (p-1, q-1) it follows that [de = 1 mod lcm(p-1, q-1)] + * + * We blind the multiplication with r, and check that rde = r mod lcm(p-1, q-1) + */ + const r = await random.getRandomBN(two, two.shln(n.bitLength() / 3)); // r in [ 2, 2^{|n|/3} ) < p and q + const rde = r.mul(d).mul(e); -async function bnEncrypt(data, n, e) { - const BigInteger = await util.getBigInteger(); - n = new BigInteger(n); - data = new BigInteger(await emeEncode(data, n.byteLength())); - e = new BigInteger(e); - if (data.gte(n)) { - throw new Error('Message size cannot exceed modulus size'); - } - return data.modExp(e, n).toUint8Array('be', n.byteLength()); -} + const areInverses = rde.umod(p.sub(one)).eq(r) && rde.umod(q.sub(one)).eq(r); + if (!areInverses) { + return false; + } -async function nodeDecrypt(data, n, e, d, p, q, u) { - const { default: BN } = await import('bn.js'); + return true; + }, - const pBNum = new BN(p); - const qBNum = new BN(q); - const dBNum = new BN(d); - const dq = dBNum.mod(qBNum.subn(1)); // d mod (q-1) - const dp = dBNum.mod(pBNum.subn(1)); // d mod (p-1) - const keyObject = { - version: 0, - modulus: new BN(n), - publicExponent: new BN(e), - privateExponent: new BN(d), - // switch p and q - prime1: new BN(q), - prime2: new BN(p), - // switch dp and dq - exponent1: dq, - exponent2: dp, - coefficient: new BN(u) - }; - let key; - if (typeof nodeCrypto.createPrivateKey !== 'undefined') { - const der = RSAPrivateKey.encode(keyObject, 'der'); - key = { key: der, format: 'der' , type: 'pkcs1', padding: nodeCrypto.constants.RSA_PKCS1_PADDING }; - } else { + bnSign: async function (hash_algo, n, d, hashed) { + n = new BN(n); + const m = new BN(await pkcs1.emsa.encode(hash_algo, hashed, n.byteLength()), 16); + d = new BN(d); + if (n.cmp(m) <= 0) { + throw new Error('Message size cannot exceed modulus size'); + } + const nred = new BN.red(n); + return m.toRed(nred).redPow(d).toArrayLike(Uint8Array, 'be', n.byteLength()); + }, + + webSign: async function (hash_name, data, n, e, d, p, q, u) { + /** OpenPGP keys require that p < q, and Safari Web Crypto requires that p > q. + * We swap them in privateToJwk, so it usually works out, but nevertheless, + * not all OpenPGP keys are compatible with this requirement. + * OpenPGP.js used to generate RSA keys the wrong way around (p > q), and still + * does if the underlying Web Crypto does so (e.g. old MS Edge 50% of the time). + */ + const jwk = privateToJwk(n, e, d, p, q, u); + const algo = { + name: "RSASSA-PKCS1-v1_5", + hash: { name: hash_name } + }; + const key = await webCrypto.importKey("jwk", jwk, algo, false, ["sign"]); + // add hash field for ms edge support + return new Uint8Array(await webCrypto.sign({ "name": "RSASSA-PKCS1-v1_5", "hash": hash_name }, key, data)); + }, + + nodeSign: async function (hash_algo, data, n, e, d, p, q, u) { + const pBNum = new BN(p); + const qBNum = new BN(q); + const dBNum = new BN(d); + const dq = dBNum.mod(qBNum.subn(1)); // d mod (q-1) + const dp = dBNum.mod(pBNum.subn(1)); // d mod (p-1) + const sign = nodeCrypto.createSign(enums.read(enums.hash, hash_algo)); + sign.write(data); + sign.end(); + const keyObject = { + version: 0, + modulus: new BN(n), + publicExponent: new BN(e), + privateExponent: new BN(d), + // switch p and q + prime1: new BN(q), + prime2: new BN(p), + // switch dp and dq + exponent1: dq, + exponent2: dp, + coefficient: new BN(u) + }; + if (typeof nodeCrypto.createPrivateKey !== 'undefined') { //from version 11.6.0 Node supports der encoded key objects + const der = RSAPrivateKey.encode(keyObject, 'der'); + return new Uint8Array(sign.sign({ key: der, format: 'der', type: 'pkcs1' })); + } const pem = RSAPrivateKey.encode(keyObject, 'pem', { label: 'RSA PRIVATE KEY' }); - key = { key: pem, padding: nodeCrypto.constants.RSA_PKCS1_PADDING }; - } - try { - return new Uint8Array(nodeCrypto.privateDecrypt(key, data)); - } catch (err) { - throw new Error('Decryption error'); - } -} + return new Uint8Array(sign.sign(pem)); + }, + + bnVerify: async function (hash_algo, s, n, e, hashed) { + n = new BN(n); + s = new BN(s); + e = new BN(e); + if (n.cmp(s) <= 0) { + throw new Error('Signature size cannot exceed modulus size'); + } + const nred = new BN.red(n); + const EM1 = s.toRed(nred).redPow(e).toArrayLike(Uint8Array, 'be', n.byteLength()); + const EM2 = await pkcs1.emsa.encode(hash_algo, hashed, n.byteLength()); + return util.Uint8Array_to_hex(EM1) === EM2; + }, + + webVerify: async function (hash_name, data, s, n, e) { + const jwk = publicToJwk(n, e); + const key = await webCrypto.importKey("jwk", jwk, { + name: "RSASSA-PKCS1-v1_5", + hash: { name: hash_name } + }, false, ["verify"]); + // add hash field for ms edge support + return webCrypto.verify({ "name": "RSASSA-PKCS1-v1_5", "hash": hash_name }, key, s, data); + }, + + nodeVerify: async function (hash_algo, data, s, n, e) { + const verify = nodeCrypto.createVerify(enums.read(enums.hash, hash_algo)); + verify.write(data); + verify.end(); + const keyObject = { + modulus: new BN(n), + publicExponent: new BN(e) + }; + let key; + if (typeof nodeCrypto.createPrivateKey !== 'undefined') { //from version 11.6.0 Node supports der encoded key objects + const der = RSAPublicKey.encode(keyObject, 'der'); + key = { key: der, format: 'der', type: 'pkcs1' }; + } else { + key = RSAPublicKey.encode(keyObject, 'pem', { + label: 'RSA PUBLIC KEY' + }); + } + try { + return await verify.verify(key, s); + } catch (err) { + return false; + } + }, -async function bnDecrypt(data, n, e, d, p, q, u) { - const BigInteger = await util.getBigInteger(); - data = new BigInteger(data); - n = new BigInteger(n); - e = new BigInteger(e); - d = new BigInteger(d); - p = new BigInteger(p); - q = new BigInteger(q); - u = new BigInteger(u); - if (data.gte(n)) { - throw new Error('Data too large.'); - } - const dq = d.mod(q.dec()); // d mod (q-1) - const dp = d.mod(p.dec()); // d mod (p-1) - - let blinder; - let unblinder; - if (config.rsaBlinding) { - unblinder = (await getRandomBigInteger(new BigInteger(2), n)).mod(n); - blinder = unblinder.modInv(n).modExp(e, n); - data = data.mul(blinder).mod(n); - } + nodeEncrypt: async function (data, n, e) { + const keyObject = { + modulus: new BN(n), + publicExponent: new BN(e) + }; + let key; + if (typeof nodeCrypto.createPrivateKey !== 'undefined') { + const der = RSAPublicKey.encode(keyObject, 'der'); + key = { key: der, format: 'der', type: 'pkcs1', padding: nodeCrypto.constants.RSA_PKCS1_PADDING }; + } else { + const pem = RSAPublicKey.encode(keyObject, 'pem', { + label: 'RSA PUBLIC KEY' + }); + key = { key: pem, padding: nodeCrypto.constants.RSA_PKCS1_PADDING }; + } + return new Uint8Array(nodeCrypto.publicEncrypt(key, data)); + }, + + bnEncrypt: async function (data, n, e) { + n = new BN(n); + data = new type_mpi(await pkcs1.eme.encode(util.Uint8Array_to_str(data), n.byteLength())); + data = data.toBN(); + e = new BN(e); + if (n.cmp(data) <= 0) { + throw new Error('Message size cannot exceed modulus size'); + } + const nred = new BN.red(n); + return data.toRed(nred).redPow(e).toArrayLike(Uint8Array, 'be', n.byteLength()); + }, + + nodeDecrypt: function (data, n, e, d, p, q, u) { + const pBNum = new BN(p); + const qBNum = new BN(q); + const dBNum = new BN(d); + const dq = dBNum.mod(qBNum.subn(1)); // d mod (q-1) + const dp = dBNum.mod(pBNum.subn(1)); // d mod (p-1) + const keyObject = { + version: 0, + modulus: new BN(n), + publicExponent: new BN(e), + privateExponent: new BN(d), + // switch p and q + prime1: new BN(q), + prime2: new BN(p), + // switch dp and dq + exponent1: dq, + exponent2: dp, + coefficient: new BN(u) + }; + let key; + if (typeof nodeCrypto.createPrivateKey !== 'undefined') { + const der = RSAPrivateKey.encode(keyObject, 'der'); + key = { key: der, format: 'der' , type: 'pkcs1', padding: nodeCrypto.constants.RSA_PKCS1_PADDING }; + } else { + const pem = RSAPrivateKey.encode(keyObject, 'pem', { + label: 'RSA PRIVATE KEY' + }); + key = { key: pem, padding: nodeCrypto.constants.RSA_PKCS1_PADDING }; + } + try { + return util.Uint8Array_to_str(nodeCrypto.privateDecrypt(key, data)); + } catch (err) { + throw new Error('Decryption error'); + } + }, + + bnDecrypt: async function(data, n, e, d, p, q, u) { + data = new BN(data); + n = new BN(n); + e = new BN(e); + d = new BN(d); + p = new BN(p); + q = new BN(q); + u = new BN(u); + if (n.cmp(data) <= 0) { + throw new Error('Data too large.'); + } + const dq = d.mod(q.subn(1)); // d mod (q-1) + const dp = d.mod(p.subn(1)); // d mod (p-1) + const pred = new BN.red(p); + const qred = new BN.red(q); + const nred = new BN.red(n); + + let blinder; + let unblinder; + if (config.rsa_blinding) { + unblinder = (await random.getRandomBN(new BN(2), n)).toRed(nred); + blinder = unblinder.redInvm().redPow(e); + data = data.toRed(nred).redMul(blinder).fromRed(); + } - const mp = data.modExp(dp, p); // data**{d mod (q-1)} mod p - const mq = data.modExp(dq, q); // data**{d mod (p-1)} mod q - const h = u.mul(mq.sub(mp)).mod(q); // u * (mq-mp) mod q (operands already < q) + const mp = data.toRed(pred).redPow(dp); + const mq = data.toRed(qred).redPow(dq); + const t = mq.redSub(mp.fromRed().toRed(qred)); + const h = u.toRed(qred).redMul(t).fromRed(); - let result = h.mul(p).add(mp); // result < n due to relations above + let result = h.mul(p).add(mp).toRed(nred); - if (config.rsaBlinding) { - result = result.mul(unblinder).mod(n); - } + if (config.rsa_blinding) { + result = result.redMul(unblinder); + } - return emeDecode(result.toUint8Array('be', n.byteLength())); -} + result = new type_mpi(result).toUint8Array('be', n.byteLength()); // preserve leading zeros + return pkcs1.eme.decode(util.Uint8Array_to_str(result)); + }, + + prime: prime +}; /** Convert Openpgp private key params to jwk key according to * @link https://tools.ietf.org/html/rfc7517 @@ -559,28 +561,27 @@ async function bnDecrypt(data, n, e, d, p, q, u) { * @param {Uint8Array} q * @param {Uint8Array} u */ -async function privateToJwk(n, e, d, p, q, u) { - const BigInteger = await util.getBigInteger(); - const pNum = new BigInteger(p); - const qNum = new BigInteger(q); - const dNum = new BigInteger(d); - - let dq = dNum.mod(qNum.dec()); // d mod (q-1) - let dp = dNum.mod(pNum.dec()); // d mod (p-1) - dp = dp.toUint8Array(); - dq = dq.toUint8Array(); +function privateToJwk(n, e, d, p, q, u) { + const pBNum = new BN(p); + const qBNum = new BN(q); + const dBNum = new BN(d); + + let dq = dBNum.mod(qBNum.subn(1)); // d mod (q-1) + let dp = dBNum.mod(pBNum.subn(1)); // d mod (p-1) + dp = dp.toArrayLike(Uint8Array); + dq = dq.toArrayLike(Uint8Array); return { kty: 'RSA', - n: uint8ArrayToB64(n, true), - e: uint8ArrayToB64(e, true), - d: uint8ArrayToB64(d, true), + n: util.Uint8Array_to_b64(n, true), + e: util.Uint8Array_to_b64(e, true), + d: util.Uint8Array_to_b64(d, true), // switch p and q - p: uint8ArrayToB64(q, true), - q: uint8ArrayToB64(p, true), + p: util.Uint8Array_to_b64(q, true), + q: util.Uint8Array_to_b64(p, true), // switch dp and dq - dp: uint8ArrayToB64(dq, true), - dq: uint8ArrayToB64(dp, true), - qi: uint8ArrayToB64(u, true), + dp: util.Uint8Array_to_b64(dq, true), + dq: util.Uint8Array_to_b64(dp, true), + qi: util.Uint8Array_to_b64(u, true), ext: true }; } @@ -594,8 +595,8 @@ async function privateToJwk(n, e, d, p, q, u) { function publicToJwk(n, e) { return { kty: 'RSA', - n: uint8ArrayToB64(n, true), - e: uint8ArrayToB64(e, true), + n: util.Uint8Array_to_b64(n, true), + e: util.Uint8Array_to_b64(e, true), ext: true }; } diff --git a/src/crypto/random.js b/src/crypto/random.js index 64d5e042..b96569fe 100644 --- a/src/crypto/random.js +++ b/src/crypto/random.js @@ -19,126 +19,127 @@ /** * @fileoverview Provides tools for retrieving secure randomness from browsers or Node.js + * @requires bn.js * @requires util * @module crypto/random */ + +import BN from 'bn.js'; import util from '../util'; -// Do not use util.getNodeCrypto because we need this regardless of useNative setting +// Do not use util.getNodeCrypto because we need this regardless of use_native setting const nodeCrypto = util.detectNode() && require('crypto'); -/** - * Buffer for secure random numbers - */ -class RandomBuffer { - constructor() { - this.buffer = null; - this.size = null; - this.callback = null; - } - - /** - * Initialize buffer - * @param {Integer} size size of buffer - */ - init(size, callback) { - this.buffer = new Uint8Array(size); - this.size = 0; - this.callback = callback; - } - +export default { /** - * Concat array of secure random numbers to buffer - * @param {Uint8Array} buf + * Retrieve secure random byte array of the specified length + * @param {Integer} length Length in bytes to generate + * @returns {Uint8Array} Random byte array + * @async */ - set(buf) { - if (!this.buffer) { - throw new Error('RandomBuffer is not initialized'); + getRandomBytes: async function(length) { + const buf = new Uint8Array(length); + if (typeof crypto !== 'undefined' && crypto.getRandomValues) { + crypto.getRandomValues(buf); + } else if (typeof global !== 'undefined' && typeof global.msCrypto === 'object' && typeof global.msCrypto.getRandomValues === 'function') { + global.msCrypto.getRandomValues(buf); + } else if (nodeCrypto) { + const bytes = nodeCrypto.randomBytes(buf.length); + buf.set(bytes); + } else if (this.randomBuffer.buffer) { + await this.randomBuffer.get(buf); + } else { + throw new Error('No secure random number generator available.'); } - if (!(buf instanceof Uint8Array)) { - throw new Error('Invalid type: buf not an Uint8Array'); - } - const freeSpace = this.buffer.length - this.size; - if (buf.length > freeSpace) { - buf = buf.subarray(0, freeSpace); - } - // set buf with offset old size of buffer - this.buffer.set(buf, this.size); - this.size += buf.length; - } + return buf; + }, /** - * Take numbers out of buffer and copy to array - * @param {Uint8Array} buf the destination array + * Create a secure random MPI that is greater than or equal to min and less than max. + * @param {module:type/mpi} min Lower bound, included + * @param {module:type/mpi} max Upper bound, excluded + * @returns {module:BN} Random MPI + * @async */ - async get(buf) { - if (!this.buffer) { - throw new Error('RandomBuffer is not initialized'); - } - if (!(buf instanceof Uint8Array)) { - throw new Error('Invalid type: buf not an Uint8Array'); - } - if (this.size < buf.length) { - if (!this.callback) { - throw new Error('Random number buffer depleted'); - } - // Wait for random bytes from main context, then try again - await this.callback(); - return this.get(buf); + getRandomBN: async function(min, max) { + if (max.cmp(min) <= 0) { + throw new Error('Illegal parameter value: max <= min'); } - for (let i = 0; i < buf.length; i++) { - buf[i] = this.buffer[--this.size]; - // clear buffer value - this.buffer[this.size] = 0; - } - } -} + + const modulus = max.sub(min); + const bytes = modulus.byteLength(); + + // Using a while loop is necessary to avoid bias introduced by the mod operation. + // However, we request 64 extra random bits so that the bias is negligible. + // Section B.1.1 here: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf + const r = new BN(await this.getRandomBytes(bytes + 8)); + return r.mod(modulus).add(min); + }, + + randomBuffer: new RandomBuffer() +}; /** - * Retrieve secure random byte array of the specified length - * @param {Integer} length Length in bytes to generate - * @returns {Uint8Array} Random byte array - * @async + * Buffer for secure random numbers */ -export async function getRandomBytes(length) { - const buf = new Uint8Array(length); - if (typeof crypto !== 'undefined' && crypto.getRandomValues) { - crypto.getRandomValues(buf); - } else if (typeof globalThis !== 'undefined' && typeof globalThis.msCrypto === 'object' && typeof globalThis.msCrypto.getRandomValues === 'function') { - globalThis.msCrypto.getRandomValues(buf); - } else if (nodeCrypto) { - const bytes = nodeCrypto.randomBytes(buf.length); - buf.set(bytes); - } else if (randomBuffer.buffer) { - await randomBuffer.get(buf); - } else { - throw new Error('No secure random number generator available.'); - } - return buf; +function RandomBuffer() { + this.buffer = null; + this.size = null; + this.callback = null; } /** - * Create a secure random BigInteger that is greater than or equal to min and less than max. - * @param {module:BigInteger} min Lower bound, included - * @param {module:BigInteger} max Upper bound, excluded - * @returns {module:BigInteger} Random BigInteger - * @async + * Initialize buffer + * @param {Integer} size size of buffer */ -export async function getRandomBigInteger(min, max) { - const BigInteger = await util.getBigInteger(); +RandomBuffer.prototype.init = function(size, callback) { + this.buffer = new Uint8Array(size); + this.size = 0; + this.callback = callback; +}; - if (max.lt(min)) { - throw new Error('Illegal parameter value: max <= min'); +/** + * Concat array of secure random numbers to buffer + * @param {Uint8Array} buf + */ +RandomBuffer.prototype.set = function(buf) { + if (!this.buffer) { + throw new Error('RandomBuffer is not initialized'); } + if (!(buf instanceof Uint8Array)) { + throw new Error('Invalid type: buf not an Uint8Array'); + } + const freeSpace = this.buffer.length - this.size; + if (buf.length > freeSpace) { + buf = buf.subarray(0, freeSpace); + } + // set buf with offset old size of buffer + this.buffer.set(buf, this.size); + this.size += buf.length; +}; - const modulus = max.sub(min); - const bytes = modulus.byteLength(); - - // Using a while loop is necessary to avoid bias introduced by the mod operation. - // However, we request 64 extra random bits so that the bias is negligible. - // Section B.1.1 here: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf - const r = new BigInteger(await getRandomBytes(bytes + 8)); - return r.mod(modulus).add(min); -} - -export const randomBuffer = new RandomBuffer(); +/** + * Take numbers out of buffer and copy to array + * @param {Uint8Array} buf the destination array + */ +RandomBuffer.prototype.get = async function(buf) { + if (!this.buffer) { + throw new Error('RandomBuffer is not initialized'); + } + if (!(buf instanceof Uint8Array)) { + throw new Error('Invalid type: buf not an Uint8Array'); + } + if (this.size < buf.length) { + if (!this.callback) { + throw new Error('Random number buffer depleted'); + } + // Wait for random bytes from main context, then try again + await this.callback(); + return this.get(buf); + } + for (let i = 0; i < buf.length; i++) { + buf[i] = this.buffer[--this.size]; + // clear buffer value + this.buffer[this.size] = 0; + } +}; diff --git a/src/crypto/signature.js b/src/crypto/signature.js index 39946b7b..55976be4 100644 --- a/src/crypto/signature.js +++ b/src/crypto/signature.js @@ -1,158 +1,137 @@ /** * @fileoverview Provides functions for asymmetric signing and signature verification + * @requires crypto/crypto * @requires crypto/public_key * @requires enums * @requires util * @module crypto/signature */ +import crypto from './crypto'; import publicKey from './public_key'; import enums from '../enums'; import util from '../util'; -/** - * Parse signature in binary form to get the parameters. - * The returned values are only padded for EdDSA, since in the other cases their expected length - * depends on the key params, hence we delegate the padding to the signature verification function. - * See {@link https://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1} - * See {@link https://tools.ietf.org/html/rfc4880#section-5.2.2|RFC 4880 5.2.2.} - * @param {module:enums.publicKey} algo Public key algorithm - * @param {Uint8Array} signature Data for which the signature was created - * @returns {Object} True if signature is valid - * @async - */ -export function parseSignatureParams(algo, signature) { - let read = 0; - switch (algo) { - // Algorithm-Specific Fields for RSA signatures: - // - MPI of RSA signature value m**d mod n. - case enums.publicKey.rsaEncryptSign: - case enums.publicKey.rsaEncrypt: - case enums.publicKey.rsaSign: { - const s = util.readMPI(signature.subarray(read)); - // The signature needs to be the same length as the public key modulo n. - // We pad s on signature verification, where we have access to n. - return { s }; - } - // Algorithm-Specific Fields for DSA or ECDSA signatures: - // - MPI of DSA or ECDSA value r. - // - MPI of DSA or ECDSA value s. - case enums.publicKey.dsa: - case enums.publicKey.ecdsa: - { - const r = util.readMPI(signature.subarray(read)); read += r.length + 2; - const s = util.readMPI(signature.subarray(read)); - return { r, s }; +export default { + /** + * Verifies the signature provided for data using specified algorithms and public key parameters. + * See {@link https://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1} + * and {@link https://tools.ietf.org/html/rfc4880#section-9.4|RFC 4880 9.4} + * for public key and hash algorithms. + * @param {module:enums.publicKey} algo Public key algorithm + * @param {module:enums.hash} hash_algo Hash algorithm + * @param {Array} msg_MPIs Algorithm-specific signature parameters + * @param {Array} pub_MPIs Algorithm-specific public key parameters + * @param {Uint8Array} data Data for which the signature was created + * @param {Uint8Array} hashed The hashed data + * @returns {Boolean} True if signature is valid + * @async + */ + verify: async function(algo, hash_algo, msg_MPIs, pub_MPIs, data, hashed) { + const types = crypto.getPubKeyParamTypes(algo); + if (pub_MPIs.length < types.length) { + throw new Error('Missing public key parameters'); } - // Algorithm-Specific Fields for EdDSA signatures: - // - MPI of an EC point r. - // - EdDSA value s, in MPI, in the little endian representation - case enums.publicKey.eddsa: { - // When parsing little-endian MPI data, we always need to left-pad it, as done with big-endian values: - // https://www.ietf.org/archive/id/draft-ietf-openpgp-rfc4880bis-10.html#section-3.2-9 - let r = util.readMPI(signature.subarray(read)); read += r.length + 2; - r = util.leftPad(r, 32); - let s = util.readMPI(signature.subarray(read)); - s = util.leftPad(s, 32); - return { r, s }; + switch (algo) { + case enums.publicKey.rsa_encrypt_sign: + case enums.publicKey.rsa_encrypt: + case enums.publicKey.rsa_sign: { + const n = pub_MPIs[0].toUint8Array(); + const e = pub_MPIs[1].toUint8Array(); + const m = msg_MPIs[0].toUint8Array('be', n.length); + return publicKey.rsa.verify(hash_algo, data, m, n, e, hashed); + } + case enums.publicKey.dsa: { + const r = msg_MPIs[0].toBN(); + const s = msg_MPIs[1].toBN(); + const p = pub_MPIs[0].toBN(); + const q = pub_MPIs[1].toBN(); + const g = pub_MPIs[2].toBN(); + const y = pub_MPIs[3].toBN(); + return publicKey.dsa.verify(hash_algo, r, s, hashed, g, p, q, y); + } + case enums.publicKey.ecdsa: { + const { oid, Q } = publicKey.elliptic.ecdsa.parseParams(pub_MPIs); + const signature = { r: msg_MPIs[0].toUint8Array(), s: msg_MPIs[1].toUint8Array() }; + return publicKey.elliptic.ecdsa.verify(oid, hash_algo, signature, data, Q, hashed); + } + case enums.publicKey.eddsa: { + const { oid, Q } = publicKey.elliptic.eddsa.parseParams(pub_MPIs); + // EdDSA signature params are expected in little-endian format + const signature = { + R: msg_MPIs[0].toUint8Array('le', 32), + S: msg_MPIs[1].toUint8Array('le', 32) + }; + return publicKey.elliptic.eddsa.verify(oid, hash_algo, signature, data, Q, hashed); + } + default: + throw new Error('Invalid signature algorithm.'); } - default: - throw new Error('Invalid signature algorithm.'); - } -} + }, -/** - * Verifies the signature provided for data using specified algorithms and public key parameters. - * See {@link https://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1} - * and {@link https://tools.ietf.org/html/rfc4880#section-9.4|RFC 4880 9.4} - * for public key and hash algorithms. - * @param {module:enums.publicKey} algo Public key algorithm - * @param {module:enums.hash} hashAlgo Hash algorithm - * @param {Object} signature Named algorithm-specific signature parameters - * @param {Object} publicParams Algorithm-specific public key parameters - * @param {Uint8Array} data Data for which the signature was created - * @param {Uint8Array} hashed The hashed data - * @returns {Boolean} True if signature is valid - * @async - */ -export async function verify(algo, hashAlgo, signature, publicParams, data, hashed) { - switch (algo) { - case enums.publicKey.rsaEncryptSign: - case enums.publicKey.rsaEncrypt: - case enums.publicKey.rsaSign: { - const { n, e } = publicParams; - const s = util.leftPad(signature.s, n.length); // padding needed for webcrypto and node crypto - return publicKey.rsa.verify(hashAlgo, data, s, n, e, hashed); - } - case enums.publicKey.dsa: { - const { g, p, q, y } = publicParams; - const { r, s } = signature; // no need to pad, since we always handle them as BigIntegers - return publicKey.dsa.verify(hashAlgo, r, s, hashed, g, p, q, y); - } - case enums.publicKey.ecdsa: { - const { oid, Q } = publicParams; - const curveSize = new publicKey.elliptic.Curve(oid).payloadSize; - // padding needed for webcrypto - const r = util.leftPad(signature.r, curveSize); - const s = util.leftPad(signature.s, curveSize); - return publicKey.elliptic.ecdsa.verify(oid, hashAlgo, { r, s }, data, Q, hashed); - } - case enums.publicKey.eddsa: { - const { oid, Q } = publicParams; - // signature already padded on parsing - return publicKey.elliptic.eddsa.verify(oid, hashAlgo, signature, data, Q, hashed); - } - default: - throw new Error('Invalid signature algorithm.'); - } -} - -/** - * Creates a signature on data using specified algorithms and private key parameters. - * See {@link https://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1} - * and {@link https://tools.ietf.org/html/rfc4880#section-9.4|RFC 4880 9.4} - * for public key and hash algorithms. - * @param {module:enums.publicKey} algo Public key algorithm - * @param {module:enums.hash} hashAlgo Hash algorithm - * @param {Object} publicKeyParams Algorithm-specific public and private key parameters - * @param {Object} privateKeyParams Algorithm-specific public and private key parameters - * @param {Uint8Array} data Data to be signed - * @param {Uint8Array} hashed The hashed data - * @returns {Object} Signature Object containing named signature parameters - * @async - */ -export async function sign(algo, hashAlgo, publicKeyParams, privateKeyParams, data, hashed) { - if (!publicKeyParams || !privateKeyParams) { - throw new Error('Missing key parameters'); - } - switch (algo) { - case enums.publicKey.rsaEncryptSign: - case enums.publicKey.rsaEncrypt: - case enums.publicKey.rsaSign: { - const { n, e } = publicKeyParams; - const { d, p, q, u } = privateKeyParams; - const s = await publicKey.rsa.sign(hashAlgo, data, n, e, d, p, q, u, hashed); - return { s }; - } - case enums.publicKey.dsa: { - const { g, p, q } = publicKeyParams; - const { x } = privateKeyParams; - return publicKey.dsa.sign(hashAlgo, hashed, g, p, q, x); - } - case enums.publicKey.elgamal: { - throw new Error('Signing with Elgamal is not defined in the OpenPGP standard.'); - } - case enums.publicKey.ecdsa: { - const { oid, Q } = publicKeyParams; - const { d } = privateKeyParams; - return publicKey.elliptic.ecdsa.sign(oid, hashAlgo, data, Q, d, hashed); + /** + * Creates a signature on data using specified algorithms and private key parameters. + * See {@link https://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1} + * and {@link https://tools.ietf.org/html/rfc4880#section-9.4|RFC 4880 9.4} + * for public key and hash algorithms. + * @param {module:enums.publicKey} algo Public key algorithm + * @param {module:enums.hash} hash_algo Hash algorithm + * @param {Array} key_params Algorithm-specific public and private key parameters + * @param {Uint8Array} data Data to be signed + * @param {Uint8Array} hashed The hashed data + * @returns {Uint8Array} Signature + * @async + */ + sign: async function(algo, hash_algo, key_params, data, hashed) { + const types = [].concat(crypto.getPubKeyParamTypes(algo), crypto.getPrivKeyParamTypes(algo)); + if (key_params.length < types.length) { + throw new Error('Missing private key parameters'); } - case enums.publicKey.eddsa: { - const { oid, Q } = publicKeyParams; - const { seed } = privateKeyParams; - return publicKey.elliptic.eddsa.sign(oid, hashAlgo, data, Q, seed, hashed); + switch (algo) { + case enums.publicKey.rsa_encrypt_sign: + case enums.publicKey.rsa_encrypt: + case enums.publicKey.rsa_sign: { + const n = key_params[0].toUint8Array(); + const e = key_params[1].toUint8Array(); + const d = key_params[2].toUint8Array(); + const p = key_params[3].toUint8Array(); + const q = key_params[4].toUint8Array(); + const u = key_params[5].toUint8Array(); + const signature = await publicKey.rsa.sign(hash_algo, data, n, e, d, p, q, u, hashed); + return util.Uint8Array_to_MPI(signature); + } + case enums.publicKey.dsa: { + const p = key_params[0].toBN(); + const q = key_params[1].toBN(); + const g = key_params[2].toBN(); + const x = key_params[4].toBN(); + const signature = await publicKey.dsa.sign(hash_algo, hashed, g, p, q, x); + return util.concatUint8Array([ + util.Uint8Array_to_MPI(signature.r), + util.Uint8Array_to_MPI(signature.s) + ]); + } + case enums.publicKey.elgamal: { + throw new Error('Signing with Elgamal is not defined in the OpenPGP standard.'); + } + case enums.publicKey.ecdsa: { + const { oid, Q, d } = publicKey.elliptic.ecdsa.parseParams(key_params); + const signature = await publicKey.elliptic.ecdsa.sign(oid, hash_algo, data, Q, d, hashed); + return util.concatUint8Array([ + util.Uint8Array_to_MPI(signature.r), + util.Uint8Array_to_MPI(signature.s) + ]); + } + case enums.publicKey.eddsa: { + const { oid, Q, seed } = publicKey.elliptic.eddsa.parseParams(key_params); + const signature = await publicKey.elliptic.eddsa.sign(oid, hash_algo, data, Q, seed, hashed); + return util.concatUint8Array([ + util.Uint8Array_to_MPI(signature.R), + util.Uint8Array_to_MPI(signature.S) + ]); + } + default: + throw new Error('Invalid signature algorithm.'); } - default: - throw new Error('Invalid signature algorithm.'); } -} +}; diff --git a/src/encoding/armor.js b/src/encoding/armor.js index 4d39de16..259a7274 100644 --- a/src/encoding/armor.js +++ b/src/encoding/armor.js @@ -25,7 +25,7 @@ */ import stream from 'web-stream-tools'; -import * as base64 from './base64.js'; +import base64 from './base64.js'; import enums from '../enums.js'; import config from '../config'; import util from '../util'; @@ -55,14 +55,14 @@ function getType(text) { // Used for multi-part messages, where the armor is split amongst Y // parts, and this is the Xth part out of Y. if (/MESSAGE, PART \d+\/\d+/.test(header[1])) { - return enums.armor.multipartSection; + return enums.armor.multipart_section; } else // BEGIN PGP MESSAGE, PART X // Used for multi-part messages, where this is the Xth part of an // unspecified number of parts. Requires the MESSAGE-ID Armor // Header to be used. if (/MESSAGE, PART \d+/.test(header[1])) { - return enums.armor.multipartLast; + return enums.armor.multipart_last; } else // BEGIN PGP SIGNED MESSAGE if (/SIGNED MESSAGE/.test(header[1])) { @@ -76,12 +76,12 @@ function getType(text) { // BEGIN PGP PUBLIC KEY BLOCK // Used for armoring public keys. if (/PUBLIC KEY BLOCK/.test(header[1])) { - return enums.armor.publicKey; + return enums.armor.public_key; } else // BEGIN PGP PRIVATE KEY BLOCK // Used for armoring private keys. if (/PRIVATE KEY BLOCK/.test(header[1])) { - return enums.armor.privateKey; + return enums.armor.private_key; } else // BEGIN PGP SIGNATURE // Used for detached signatures, OpenPGP/MIME signatures, and @@ -102,16 +102,16 @@ function getType(text) { */ function addheader(customComment) { let result = ""; - if (config.showVersion) { - result += "Version: " + config.versionString + '\n'; + if (config.show_version) { + result += "Version: " + config.versionstring + '\r\n'; } - if (config.showComment) { - result += "Comment: " + config.commentString + '\n'; + if (config.show_comment) { + result += "Comment: " + config.commentstring + '\r\n'; } if (customComment) { - result += "Comment: " + customComment + '\n'; + result += "Comment: " + customComment + '\r\n'; } - result += '\n'; + result += '\r\n'; return result; } @@ -199,7 +199,7 @@ function verifyHeaders(headers) { throw new Error('Improperly formatted armor header: ' + headers[i]); } if (!/^(Version|Comment|MessageID|Hash|Charset): .+$/.test(headers[i])) { - util.printDebugError(new Error('Unknown header: ' + headers[i])); + util.print_debug_error(new Error('Unknown header: ' + headers[i])); } } } @@ -233,7 +233,7 @@ function splitChecksum(text) { * @async * @static */ -export function unarmor(input) { +function dearmor(input) { return new Promise(async (resolve, reject) => { try { const reSplit = /^-----[^-]+-----$/m; @@ -331,8 +331,8 @@ export function unarmor(input) { }); const writer = stream.getWriter(writable); try { - const checksumVerifiedString = (await checksumVerified).replace('\n', ''); - if (checksum !== checksumVerifiedString && (checksum || config.checksumRequired)) { + const checksumVerifiedString = (await checksumVerified).replace('\r\n', ''); + if (checksum !== checksumVerifiedString && (checksum || config.checksum_required)) { throw new Error("Ascii armor integrity check on message failed: '" + checksum + "' should be '" + checksumVerifiedString + "'"); } @@ -359,7 +359,7 @@ export function unarmor(input) { * @returns {String | ReadableStream} Armored text * @static */ -export function armor(messagetype, body, partindex, parttotal, customComment) { +function armor(messagetype, body, partindex, parttotal, customComment) { let text; let hash; if (messagetype === enums.armor.signed) { @@ -370,59 +370,64 @@ export function armor(messagetype, body, partindex, parttotal, customComment) { const bodyClone = stream.passiveClone(body); const result = []; switch (messagetype) { - case enums.armor.multipartSection: - result.push("-----BEGIN PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\n"); + case enums.armor.multipart_section: + result.push("-----BEGIN PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\r\n"); result.push(addheader(customComment)); result.push(base64.encode(body)); result.push("=", getCheckSum(bodyClone)); - result.push("-----END PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\n"); + result.push("-----END PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\r\n"); break; - case enums.armor.multipartLast: - result.push("-----BEGIN PGP MESSAGE, PART " + partindex + "-----\n"); + case enums.armor.multipart_last: + result.push("-----BEGIN PGP MESSAGE, PART " + partindex + "-----\r\n"); result.push(addheader(customComment)); result.push(base64.encode(body)); result.push("=", getCheckSum(bodyClone)); - result.push("-----END PGP MESSAGE, PART " + partindex + "-----\n"); + result.push("-----END PGP MESSAGE, PART " + partindex + "-----\r\n"); break; case enums.armor.signed: - result.push("\n-----BEGIN PGP SIGNED MESSAGE-----\n"); - result.push("Hash: " + hash + "\n\n"); + result.push("\r\n-----BEGIN PGP SIGNED MESSAGE-----\r\n"); + result.push("Hash: " + hash + "\r\n\r\n"); result.push(text.replace(/^-/mg, "- -")); - result.push("\n-----BEGIN PGP SIGNATURE-----\n"); + result.push("\r\n-----BEGIN PGP SIGNATURE-----\r\n"); result.push(addheader(customComment)); result.push(base64.encode(body)); result.push("=", getCheckSum(bodyClone)); - result.push("-----END PGP SIGNATURE-----\n"); + result.push("-----END PGP SIGNATURE-----\r\n"); break; case enums.armor.message: - result.push("-----BEGIN PGP MESSAGE-----\n"); + result.push("-----BEGIN PGP MESSAGE-----\r\n"); result.push(addheader(customComment)); result.push(base64.encode(body)); result.push("=", getCheckSum(bodyClone)); - result.push("-----END PGP MESSAGE-----\n"); + result.push("-----END PGP MESSAGE-----\r\n"); break; - case enums.armor.publicKey: - result.push("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"); + case enums.armor.public_key: + result.push("-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n"); result.push(addheader(customComment)); result.push(base64.encode(body)); result.push("=", getCheckSum(bodyClone)); - result.push("-----END PGP PUBLIC KEY BLOCK-----\n"); + result.push("-----END PGP PUBLIC KEY BLOCK-----\r\n"); break; - case enums.armor.privateKey: - result.push("-----BEGIN PGP PRIVATE KEY BLOCK-----\n"); + case enums.armor.private_key: + result.push("-----BEGIN PGP PRIVATE KEY BLOCK-----\r\n"); result.push(addheader(customComment)); result.push(base64.encode(body)); result.push("=", getCheckSum(bodyClone)); - result.push("-----END PGP PRIVATE KEY BLOCK-----\n"); + result.push("-----END PGP PRIVATE KEY BLOCK-----\r\n"); break; case enums.armor.signature: - result.push("-----BEGIN PGP SIGNATURE-----\n"); + result.push("-----BEGIN PGP SIGNATURE-----\r\n"); result.push(addheader(customComment)); result.push(base64.encode(body)); result.push("=", getCheckSum(bodyClone)); - result.push("-----END PGP SIGNATURE-----\n"); + result.push("-----END PGP SIGNATURE-----\r\n"); break; } return util.concat(result); } + +export default { + encode: armor, + decode: dearmor +}; diff --git a/src/encoding/base64.js b/src/encoding/base64.js index da56729a..6471ce68 100644 --- a/src/encoding/base64.js +++ b/src/encoding/base64.js @@ -31,8 +31,8 @@ if (Buffer) { return new Uint8Array(b.buffer, b.byteOffset, b.byteLength); }; } else { - encodeChunk = buf => btoa(util.uint8ArrayToStr(buf)); - decodeChunk = str => util.strToUint8Array(atob(str)); + encodeChunk = buf => btoa(util.Uint8Array_to_str(buf)); + decodeChunk = str => util.str_to_Uint8Array(atob(str)); } /** @@ -41,7 +41,7 @@ if (Buffer) { * @returns {String | ReadableStream} radix-64 version of input string * @static */ -export function encode(data) { +function encode(data) { let buf = new Uint8Array(); return stream.transform(data, value => { buf = util.concatUint8Array([buf, value]); @@ -52,11 +52,11 @@ export function encode(data) { const encoded = encodeChunk(buf.subarray(0, bytes)); for (let i = 0; i < lines; i++) { r.push(encoded.substr(i * 60, 60)); - r.push('\n'); + r.push('\r\n'); } buf = buf.subarray(bytes); return r.join(''); - }, () => (buf.length ? encodeChunk(buf) + '\n' : '')); + }, () => (buf.length ? encodeChunk(buf) + '\r\n' : '')); } /** @@ -65,7 +65,7 @@ export function encode(data) { * @returns {Uint8Array | ReadableStream} binary array version of input string * @static */ -export function decode(data) { +function decode(data) { let buf = ''; return stream.transform(data, value => { buf += value; @@ -93,27 +93,4 @@ export function decode(data) { }, () => decodeChunk(buf)); } -/** - * Convert a Base-64 encoded string an array of 8-bit integer - * - * Note: accepts both Radix-64 and URL-safe strings - * @param {String} base64 Base-64 encoded string to convert - * @returns {Uint8Array} An array of 8-bit integers - */ -export function b64ToUint8Array(base64) { - return decode(base64.replace(/-/g, '+').replace(/_/g, '/')); -} - -/** - * Convert an array of 8-bit integer to a Base-64 encoded string - * @param {Uint8Array} bytes An array of 8-bit integers to convert - * @param {bool} url If true, output is URL-safe - * @returns {String} Base-64 encoded string - */ -export function uint8ArrayToB64(bytes, url) { - let encoded = encode(bytes).replace(/[\r\n]/g, ''); - if (url) { - encoded = encoded.replace(/[+]/g, '-').replace(/[/]/g, '_').replace(/[=]/g, ''); - } - return encoded; -} +export default { encode, decode }; diff --git a/src/enums.js b/src/enums.js index 88fe6ba1..3762ee34 100644 --- a/src/enums.js +++ b/src/enums.js @@ -96,11 +96,11 @@ export default { */ publicKey: { /** RSA (Encrypt or Sign) [HAC] */ - rsaEncryptSign: 1, + rsa_encrypt_sign: 1, /** RSA (Encrypt only) [HAC] */ - rsaEncrypt: 2, + rsa_encrypt: 2, /** RSA (Sign only) [HAC] */ - rsaSign: 3, + rsa_sign: 3, /** Elgamal (Encrypt only) [ELGAMAL] [HAC] */ elgamal: 16, /** DSA (Sign only) [FIPS186] [HAC] */ @@ -126,6 +126,7 @@ export default { plaintext: 0, /** Not implemented! */ idea: 1, + '3des': 2, tripledes: 2, cast5: 3, blowfish: 4, @@ -180,7 +181,7 @@ export default { aead: { eax: 1, ocb: 2, - experimentalGcm: 100 // Private algorithm + experimental_gcm: 100 // Private algorithm }, /** A list of packet types and numeric tags associated with them. @@ -195,17 +196,17 @@ export default { secretKey: 5, publicKey: 6, secretSubkey: 7, - compressedData: 8, - symmetricallyEncryptedData: 9, + compressed: 8, + symmetricallyEncrypted: 9, marker: 10, - literalData: 11, + literal: 11, trust: 12, - userID: 13, + userid: 13, publicSubkey: 14, userAttribute: 17, - symEncryptedIntegrityProtectedData: 18, + symEncryptedIntegrityProtected: 18, modificationDetectionCode: 19, - AEADEncryptedData: 20 // see IETF draft: https://tools.ietf.org/html/draft-ford-openpgp-format-00#section-2.1 + symEncryptedAEADProtected: 20 // see IETF draft: https://tools.ietf.org/html/draft-ford-openpgp-format-00#section-2.1 }, /** Data types in the literal packet @@ -247,17 +248,17 @@ export default { * The issuer of this certification does not make any particular * assertion as to how well the certifier has checked that the owner * of the key is in fact the person described by the User ID. */ - certGeneric: 16, + cert_generic: 16, /** 0x11: Persona certification of a User ID and Public-Key packet. * * The issuer of this certification has not done any verification of * the claim that the owner of this key is the User ID specified. */ - certPersona: 17, + cert_persona: 17, /** 0x12: Casual certification of a User ID and Public-Key packet. * * The issuer of this certification has done some casual * verification of the claim of identity. */ - certCasual: 18, + cert_casual: 18, /** 0x13: Positive certification of a User ID and Public-Key packet. * * The issuer of this certification has done substantial @@ -266,7 +267,7 @@ export default { * Most OpenPGP implementations make their "key signatures" as 0x10 * certifications. Some implementations can issue 0x11-0x13 * certifications, but few differentiate between the types. */ - certPositive: 19, + cert_positive: 19, /** 0x30: Certification revocation signature * * This signature revokes an earlier User ID certification signature @@ -276,7 +277,7 @@ export default { * is computed over the same data as the certificate that it * revokes, and should have a later creation date than that * certificate. */ - certRevocation: 48, + cert_revocation: 48, /** 0x18: Subkey Binding Signature * * This signature is a statement by the top-level signing key that @@ -286,7 +287,7 @@ export default { * an Embedded Signature subpacket in this binding signature that * contains a 0x19 signature made by the signing subkey on the * primary key and subkey. */ - subkeyBinding: 24, + subkey_binding: 24, /** 0x19: Primary Key Binding Signature * * This signature is a statement by a signing subkey, indicating @@ -301,7 +302,7 @@ export default { * (type 0x18) or primary key binding signature (type 0x19) then hashes * the subkey using the same format as the main key (also using 0x99 as * the first octet). */ - keyBinding: 25, + key_binding: 25, /** 0x1F: Signature directly on a key * * This signature is calculated directly on a key. It binds the @@ -318,7 +319,7 @@ export default { * revoked key is not to be used. Only revocation signatures by the * key being revoked, or by an authorized revocation key, should be * considered valid revocation signatures.a */ - keyRevocation: 32, + key_revocation: 32, /** 0x28: Subkey revocation signature * * The signature is calculated directly on the subkey being revoked. @@ -329,7 +330,7 @@ export default { * * Key revocation signatures (types 0x20 and 0x28) * hash only the key being revoked. */ - subkeyRevocation: 40, + subkey_revocation: 40, /** 0x40: Timestamp signature. * This signature is only meaningful for the timestamp contained in * it. */ @@ -343,7 +344,7 @@ export default { * mean SHOULD. There are plausible uses for this (such as a blind * party that only sees the signature, not the key or source * document) that cannot include a target subpacket. */ - thirdParty: 80 + third_party: 80 }, /** Signature subpacket type @@ -351,32 +352,32 @@ export default { * @readonly */ signatureSubpacket: { - signatureCreationTime: 2, - signatureExpirationTime: 3, - exportableCertification: 4, - trustSignature: 5, - regularExpression: 6, + signature_creation_time: 2, + signature_expiration_time: 3, + exportable_certification: 4, + trust_signature: 5, + regular_expression: 6, revocable: 7, - keyExpirationTime: 9, - placeholderBackwardsCompatibility: 10, - preferredSymmetricAlgorithms: 11, - revocationKey: 12, + key_expiration_time: 9, + placeholder_backwards_compatibility: 10, + preferred_symmetric_algorithms: 11, + revocation_key: 12, issuer: 16, - notationData: 20, - preferredHashAlgorithms: 21, - preferredCompressionAlgorithms: 22, - keyServerPreferences: 23, - preferredKeyServer: 24, - primaryUserId: 25, - policyUri: 26, - keyFlags: 27, - signersUserId: 28, - reasonForRevocation: 29, + notation_data: 20, + preferred_hash_algorithms: 21, + preferred_compression_algorithms: 22, + key_server_preferences: 23, + preferred_key_server: 24, + primary_user_id: 25, + policy_uri: 26, + key_flags: 27, + signers_user_id: 28, + reason_for_revocation: 29, features: 30, - signatureTarget: 31, - embeddedSignature: 32, - issuerFingerprint: 33, - preferredAeadAlgorithms: 34 + signature_target: 31, + embedded_signature: 32, + issuer_fingerprint: 33, + preferred_aead_algorithms: 34 }, /** Key flags @@ -385,21 +386,21 @@ export default { */ keyFlags: { /** 0x01 - This key may be used to certify other keys. */ - certifyKeys: 1, + certify_keys: 1, /** 0x02 - This key may be used to sign data. */ - signData: 2, + sign_data: 2, /** 0x04 - This key may be used to encrypt communications. */ - encryptCommunication: 4, + encrypt_communication: 4, /** 0x08 - This key may be used to encrypt storage. */ - encryptStorage: 8, + encrypt_storage: 8, /** 0x10 - The private component of this key may have been split * by a secret-sharing mechanism. */ - splitPrivateKey: 16, + split_private_key: 16, /** 0x20 - This key may be used for authentication. */ authentication: 32, /** 0x80 - The private component of this key may be in the * possession of more than one person. */ - sharedPrivateKey: 128 + shared_private_key: 128 }, /** Armor type @@ -407,12 +408,12 @@ export default { * @readonly */ armor: { - multipartSection: 0, - multipartLast: 1, + multipart_section: 0, + multipart_last: 1, signed: 2, message: 3, - publicKey: 4, - privateKey: 5, + public_key: 4, + private_key: 5, signature: 6 }, @@ -422,15 +423,15 @@ export default { */ reasonForRevocation: { /** No reason specified (key revocations or cert revocations) */ - noReason: 0, + no_reason: 0, /** Key is superseded (key revocations) */ - keySuperseded: 1, + key_superseded: 1, /** Key material has been compromised (key revocations) */ - keyCompromised: 2, + key_compromised: 2, /** Key is retired and no longer used (key revocations) */ - keyRetired: 3, + key_retired: 3, /** User ID information is no longer valid (cert revocations) */ - userIdInvalid: 32 + userid_invalid: 32 }, /** {@link https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-04#section-5.2.3.25|RFC4880bis-04, section 5.2.3.25} @@ -439,13 +440,13 @@ export default { */ features: { /** 0x01 - Modification Detection (packets 18 and 19) */ - modificationDetection: 1, + modification_detection: 1, /** 0x02 - AEAD Encrypted Data Packet (packet 20) and version 5 * Symmetric-Key Encrypted Session Key Packets (packet 3) */ aead: 2, /** 0x04 - Version 5 Public-Key Packet format and corresponding new * fingerprint format */ - v5Keys: 4 + v5_keys: 4 }, /** Asserts validity and converts from string/integer to integer. */ diff --git a/src/hkp.js b/src/hkp.js index 084bfe3d..f2b1ff05 100644 --- a/src/hkp.js +++ b/src/hkp.js @@ -23,68 +23,67 @@ import config from './config'; -class HKP { - /** - * Initialize the HKP client and configure it with the key server url and fetch function. - * @param {String} keyServerBaseUrl (optional) The HKP key server base url including - * the protocol to use, e.g. 'https://pgp.mit.edu'; defaults to - * openpgp.config.keyserver (https://keyserver.ubuntu.com) - */ - constructor(keyServerBaseUrl) { - this._baseUrl = keyServerBaseUrl || config.keyserver; - this._fetch = typeof globalThis.fetch === 'function' ? globalThis.fetch : require('node-fetch'); - } - - /** - * Search for a public key on the key server either by key ID or part of the user ID. - * @param {String} options.keyId The long public key ID. - * @param {String} options.query This can be any part of the key user ID such as name - * or email address. - * @returns {Promise} The ascii armored public key. - * @async - */ - lookup(options) { - let uri = this._baseUrl + '/pks/lookup?op=get&options=mr&search='; - const fetch = this._fetch; +/** + * Initialize the HKP client and configure it with the key server url and fetch function. + * @constructor + * @param {String} keyServerBaseUrl (optional) The HKP key server base url including + * the protocol to use, e.g. 'https://pgp.mit.edu'; defaults to + * openpgp.config.keyserver (https://keyserver.ubuntu.com) + */ +function HKP(keyServerBaseUrl) { + this._baseUrl = keyServerBaseUrl || config.keyserver; + this._fetch = typeof global.fetch === 'function' ? global.fetch : require('node-fetch'); +} - if (options.keyId) { - uri += '0x' + encodeURIComponent(options.keyId); - } else if (options.query) { - uri += encodeURIComponent(options.query); - } else { - throw new Error('You must provide a query parameter!'); - } +/** + * Search for a public key on the key server either by key ID or part of the user ID. + * @param {String} options.keyId The long public key ID. + * @param {String} options.query This can be any part of the key user ID such as name + * or email address. + * @returns {Promise} The ascii armored public key. + * @async + */ +HKP.prototype.lookup = function(options) { + let uri = this._baseUrl + '/pks/lookup?op=get&options=mr&search='; + const fetch = this._fetch; - return fetch(uri).then(function(response) { - if (response.status === 200) { - return response.text(); - } - }).then(function(publicKeyArmored) { - if (!publicKeyArmored || publicKeyArmored.indexOf('-----END PGP PUBLIC KEY BLOCK-----') < 0) { - return; - } - return publicKeyArmored.trim(); - }); + if (options.keyId) { + uri += '0x' + encodeURIComponent(options.keyId); + } else if (options.query) { + uri += encodeURIComponent(options.query); + } else { + throw new Error('You must provide a query parameter!'); } - /** - * Upload a public key to the server. - * @param {String} publicKeyArmored An ascii armored public key to be uploaded. - * @returns {Promise} - * @async - */ - upload(publicKeyArmored) { - const uri = this._baseUrl + '/pks/add'; - const fetch = this._fetch; + return fetch(uri).then(function(response) { + if (response.status === 200) { + return response.text(); + } + }).then(function(publicKeyArmored) { + if (!publicKeyArmored || publicKeyArmored.indexOf('-----END PGP PUBLIC KEY BLOCK-----') < 0) { + return; + } + return publicKeyArmored.trim(); + }); +}; - return fetch(uri, { - method: 'post', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' - }, - body: 'keytext=' + encodeURIComponent(publicKeyArmored) - }); - } -} +/** + * Upload a public key to the server. + * @param {String} publicKeyArmored An ascii armored public key to be uploaded. + * @returns {Promise} + * @async + */ +HKP.prototype.upload = function(publicKeyArmored) { + const uri = this._baseUrl + '/pks/add'; + const fetch = this._fetch; + + return fetch(uri, { + method: 'post', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' + }, + body: 'keytext=' + encodeURIComponent(publicKeyArmored) + }); +}; export default HKP; diff --git a/src/index.js b/src/index.js index cf8ccb4b..393b1318 100644 --- a/src/index.js +++ b/src/index.js @@ -1,54 +1,104 @@ /* eslint-disable import/newline-after-import, import/first */ /** - * Export high level API functions. + * Export high level api as default. * Usage: * - * import { encrypt } from 'openpgp' - * encrypt({ message, publicKeys }) + * import openpgp from 'openpgp.js' + * openpgp.encryptMessage(keys, text) + */ +import * as openpgp from './openpgp'; +export default openpgp; + +/** + * Export each high level api function separately. + * Usage: + * + * import { encryptMessage } from 'openpgp.js' + * encryptMessage(keys, text) */ export { encrypt, decrypt, sign, verify, - generateKey, reformatKey, revokeKey, decryptKey, encryptKey, - generateSessionKey, encryptSessionKey, decryptSessionKeys + generateKey, reformatKey, revokeKey, decryptKey, + encryptSessionKey, decryptSessionKeys, + initWorker, getWorker, destroyWorker } from './openpgp'; /** * @see module:key * @name module:openpgp.key */ -export { - readKey, readArmoredKey, - readKeys, readArmoredKeys, - Key -} from './key'; +import * as keyMod from './key'; +export const key = keyMod; /** * @see module:signature * @name module:openpgp.signature */ -export * from './signature'; +import * as signatureMod from './signature'; +export const signature = signatureMod; /** * @see module:message * @name module:openpgp.message */ -export { - readMessage, readArmoredMessage, - Message -} from './message'; +import * as messageMod from './message'; +export const message = messageMod; /** * @see module:cleartext * @name module:openpgp.cleartext */ -export * from './cleartext'; +import * as cleartextMod from './cleartext'; +export const cleartext = cleartextMod; + +/** + * @see module:util + * @name module:openpgp.util + */ +export { default as util } from './util'; /** * @see module:packet * @name module:openpgp.packet */ -export * from './packet'; +export { default as packet } from './packet'; + +/** + * @see module:type/mpi + * @name module:openpgp.MPI + */ +export { default as MPI } from './type/mpi'; + +/** + * @see module:type/s2k + * @name module:openpgp.S2K + */ +export { default as S2K } from './type/s2k'; + +/** + * @see module:type/keyid + * @name module:openpgp.Keyid + */ +export { default as Keyid } from './type/keyid'; + +/** + * @see module:type/ecdh_symkey + * @name module:openpgp.ECDHSymmetricKey + */ +export { default as ECDHSymmetricKey } from './type/ecdh_symkey'; + +/** + * @see module:type/kdf_params + * @name module:openpgp.KDFParams + */ +export { default as KDFParams } from './type/kdf_params'; + +/** + * @see module:type/oid + * @name module:openpgp.OID + */ +export { default as OID } from './type/oid'; /** * @see streams @@ -60,7 +110,7 @@ export { default as stream } from 'web-stream-tools'; * @see module:encoding/armor * @name module:openpgp.armor */ -export * from './encoding/armor'; +export { default as armor } from './encoding/armor'; /** * @see module:enums @@ -74,12 +124,24 @@ export { default as enums } from './enums'; */ export { default as config } from './config/config'; +/** + * @see module:crypto + * @name module:openpgp.crypto + */ +export { default as crypto } from './crypto'; + /** * @see module:keyring * @name module:openpgp.Keyring */ export { default as Keyring } from './keyring'; +/** + * @see module:worker/async_proxy + * @name module:openpgp.AsyncProxy + */ +export { default as AsyncProxy } from './worker/async_proxy'; + /** * @see module:hkp * @name module:openpgp.HKP @@ -91,3 +153,9 @@ export { default as HKP } from './hkp'; * @name module:openpgp.WKD */ export { default as WKD } from './wkd'; + +/** + * @see module:lightweight + */ +import * as lightweightMod from './lightweight_helper'; +export const lightweight = lightweightMod; diff --git a/src/key/factory.js b/src/key/factory.js index 735d9a15..0cb6920e 100644 --- a/src/key/factory.js +++ b/src/key/factory.js @@ -27,26 +27,31 @@ * @module key/factory */ -import { PacketList, UserIDPacket, SignaturePacket } from '../packet'; +import packet from '../packet'; import Key from './key'; import * as helper from './helper'; import enums from '../enums'; import util from '../util'; import config from '../config'; -import { unarmor } from '../encoding/armor'; +import armor from '../encoding/armor'; /** * Generates a new OpenPGP key. Supports RSA and ECC keys. - * By default, primary and subkeys will be of same type. - * @param {ecc|rsa} options.type The primary key algorithm type: ECC or RSA - * @param {String} options.curve Elliptic curve for ECC keys - * @param {Integer} options.rsaBits Number of bits for RSA keys - * @param {Array} options.userIds User IDs as strings or objects: 'Jo Doe ' or { name:'Jo Doe', email:'info@jo.com' } - * @param {String} options.passphrase Passphrase used to encrypt the resulting private key - * @param {Number} options.keyExpirationTime (optional) Number of seconds from the key creation time after which the key expires - * @param {Date} options.date Creation date of the key and the key signatures - * @param {Array} options.subkeys (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}] - * sign parameter defaults to false, and indicates whether the subkey should sign rather than encrypt + * Primary and subkey will be of same type. + * @param {module:enums.publicKey} [options.keyType=module:enums.publicKey.rsa_encrypt_sign] + * To indicate what type of key to make. + * RSA is 1. See {@link https://tools.ietf.org/html/rfc4880#section-9.1} + * @param {Integer} options.numBits number of bits for the key creation. + * @param {String|Array} options.userIds + * Assumes already in form of "User Name " + * If array is used, the first userId is set as primary user Id + * @param {String} options.passphrase The passphrase used to encrypt the resulting private key + * @param {Number} [options.keyExpirationTime=0] + * The number of seconds after the key creation time that the key expires + * @param {String} curve (optional) elliptic curve for ECC keys + * @param {Date} date Override the creation date of the key and the key signatures + * @param {Array} subkeys (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}] + * sign parameter defaults to false, and indicates whether the subkey should sign rather than encrypt * @returns {Promise} * @async * @static @@ -63,12 +68,16 @@ export async function generate(options) { /** * Reformats and signs an OpenPGP key with a given User ID. Currently only supports RSA keys. - * @param {module:key.Key} options.privateKey The private key to reformat - * @param {Array} options.userIds User IDs as strings or objects: 'Jo Doe ' or { name:'Jo Doe', email:'info@jo.com' } - * @param {String} options.passphrase Passphrase used to encrypt the resulting private key - * @param {Number} options.keyExpirationTime Number of seconds from the key creation time after which the key expires - * @param {Date} options.date Override the creation date of the key and the key signatures - * @param {Array} options.subkeys (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}] + * @param {module:key.Key} options.privateKey The private key to reformat + * @param {module:enums.publicKey} [options.keyType=module:enums.publicKey.rsa_encrypt_sign] + * @param {String|Array} options.userIds + * Assumes already in form of "User Name " + * If array is used, the first userId is set as primary user Id + * @param {String} options.passphrase The passphrase used to encrypt the resulting private key + * @param {Number} [options.keyExpirationTime=0] + * The number of seconds after the key creation time that the key expires + * @param {Date} date Override the creation date of the key and the key signatures + * @param {Array} subkeys (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}] * * @returns {Promise} * @async @@ -77,13 +86,13 @@ export async function generate(options) { export async function reformat(options) { options = sanitize(options); - if (options.privateKey.primaryKey.isDummy()) { - throw new Error('Cannot reformat a gnu-dummy primary key'); - } - - const isDecrypted = options.privateKey.getKeys().every(({ keyPacket }) => keyPacket.isDecrypted()); - if (!isDecrypted) { - throw new Error('Key is not decrypted'); + try { + const isDecrypted = options.privateKey.getKeys().every(key => key.isDecrypted()); + if (!isDecrypted) { + await options.privateKey.decrypt(); + } + } catch (err) { + throw new Error('Key not decrypted'); } const packetlist = options.privateKey.toPacketlist(); @@ -138,7 +147,7 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options) { } })); - const packetlist = new PacketList(); + const packetlist = new packet.List(); packetlist.push(secretKeyPacket); @@ -156,32 +165,37 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options) { return algos; } - const userIdPacket = UserIDPacket.fromObject(userId); + const userIdPacket = new packet.Userid(); + userIdPacket.format(userId); + const dataToSign = {}; dataToSign.userId = userIdPacket; dataToSign.key = secretKeyPacket; - const signaturePacket = new SignaturePacket(options.date); - signaturePacket.signatureType = enums.signature.certGeneric; + const signaturePacket = new packet.Signature(options.date); + signaturePacket.signatureType = enums.signature.cert_generic; signaturePacket.publicKeyAlgorithm = secretKeyPacket.algorithm; signaturePacket.hashAlgorithm = await helper.getPreferredHashAlgo(null, secretKeyPacket); - signaturePacket.keyFlags = [enums.keyFlags.certifyKeys | enums.keyFlags.signData]; + signaturePacket.keyFlags = [enums.keyFlags.certify_keys | enums.keyFlags.sign_data]; signaturePacket.preferredSymmetricAlgorithms = createdPreferredAlgos([ // prefer aes256, aes128, then aes192 (no WebCrypto support: https://www.chromium.org/blink/webcrypto#TOC-AES-support) enums.symmetric.aes256, enums.symmetric.aes128, - enums.symmetric.aes192 - ], config.encryptionCipher); - if (config.aeadProtect) { + enums.symmetric.aes192, + enums.symmetric.cast5, + enums.symmetric.tripledes + ], config.encryption_cipher); + if (config.aead_protect) { signaturePacket.preferredAeadAlgorithms = createdPreferredAlgos([ enums.aead.eax, enums.aead.ocb - ], config.aeadMode); + ], config.aead_mode); } signaturePacket.preferredHashAlgorithms = createdPreferredAlgos([ - // prefer fast asm.js implementations (SHA-256) + // prefer fast asm.js implementations (SHA-256). SHA-1 will not be secure much longer...move to bottom of list enums.hash.sha256, - enums.hash.sha512 - ], config.preferHashAlgorithm); + enums.hash.sha512, + enums.hash.sha1 + ], config.prefer_hash_algorithm); signaturePacket.preferredCompressionAlgorithms = createdPreferredAlgos([ enums.compression.zlib, enums.compression.zip, @@ -190,17 +204,17 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options) { if (index === 0) { signaturePacket.isPrimaryUserID = true; } - if (config.integrityProtect) { + if (config.integrity_protect) { signaturePacket.features = [0]; - signaturePacket.features[0] |= enums.features.modificationDetection; + signaturePacket.features[0] |= enums.features.modification_detection; } - if (config.aeadProtect) { + if (config.aead_protect) { signaturePacket.features || (signaturePacket.features = [0]); signaturePacket.features[0] |= enums.features.aead; } - if (config.v5Keys) { + if (config.v5_keys) { signaturePacket.features || (signaturePacket.features = [0]); - signaturePacket.features[0] |= enums.features.v5Keys; + signaturePacket.features[0] |= enums.features.v5_keys; } if (options.keyExpirationTime > 0) { signaturePacket.keyExpirationTime = options.keyExpirationTime; @@ -231,8 +245,8 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options) { // This packet should be removed before returning the key. const dataToSign = { key: secretKeyPacket }; packetlist.push(await helper.createSignaturePacket(dataToSign, null, secretKeyPacket, { - signatureType: enums.signature.keyRevocation, - reasonForRevocationFlag: enums.reasonForRevocation.noReason, + signatureType: enums.signature.key_revocation, + reasonForRevocationFlag: enums.reasonForRevocation.no_reason, reasonForRevocationString: '' }, options.date)); @@ -252,67 +266,61 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options) { } /** - * Reads an unarmored OpenPGP key and returns a key object - * @param {Uint8Array} data to be parsed - * @returns {Promise} key object - * @async - * @static - */ -export async function readKey(data) { - const packetlist = new PacketList(); - await packetlist.read(data, helper.allowedKeyPackets); - return new Key(packetlist); -} - -/** - * Reads an OpenPGP armored key and returns a key object - * @param {String | ReadableStream} armoredKey text to be parsed - * @returns {Promise} key object - * @async - * @static - */ -export async function readArmoredKey(armoredKey) { - const input = await unarmor(armoredKey); - if (!(input.type === enums.armor.publicKey || input.type === enums.armor.privateKey)) { - throw new Error('Armored text not of type key'); - } - return readKey(input.data); -} - -/** - * Reads an unarmored OpenPGP key block and returns a list of key objects + * Reads an unarmored OpenPGP key list and returns one or multiple key objects * @param {Uint8Array} data to be parsed - * @returns {Promise>} key object + * @returns {Promise<{keys: Array, + * err: (Array|null)}>} result object with key and error arrays * @async * @static */ -export async function readKeys(data) { - const keys = []; - const packetlist = new PacketList(); - await packetlist.read(data, helper.allowedKeyPackets); - const keyIndex = packetlist.indexOfTag(enums.packet.publicKey, enums.packet.secretKey); - if (keyIndex.length === 0) { - throw new Error('No key packet found'); +export async function read(data) { + const result = {}; + result.keys = []; + const err = []; + try { + const packetlist = new packet.List(); + await packetlist.read(data); + const keyIndex = packetlist.indexOfTag(enums.packet.publicKey, enums.packet.secretKey); + if (keyIndex.length === 0) { + throw new Error('No key packet found'); + } + for (let i = 0; i < keyIndex.length; i++) { + const oneKeyList = packetlist.slice(keyIndex[i], keyIndex[i + 1]); + try { + const newKey = new Key(oneKeyList); + result.keys.push(newKey); + } catch (e) { + err.push(e); + } + } + } catch (e) { + err.push(e); } - for (let i = 0; i < keyIndex.length; i++) { - const oneKeyList = packetlist.slice(keyIndex[i], keyIndex[i + 1]); - const newKey = new Key(oneKeyList); - keys.push(newKey); + if (err.length) { + result.err = err; } - return keys; + return result; } + /** - * Reads an OpenPGP armored key block and returns a list of key objects - * @param {String | ReadableStream} armoredKey text to be parsed - * @returns {Promise>} key objects + * Reads an OpenPGP armored text and returns one or multiple key objects + * @param {String | ReadableStream} armoredText text to be parsed + * @returns {Promise<{keys: Array, + * err: (Array|null)}>} result object with key and error arrays * @async * @static */ -export async function readArmoredKeys(armoredKey) { - const input = await unarmor(armoredKey); - if (!(input.type === enums.armor.publicKey || input.type === enums.armor.privateKey)) { - throw new Error('Armored text not of type key'); +export async function readArmored(armoredText) { + try { + const input = await armor.decode(armoredText); + if (!(input.type === enums.armor.public_key || input.type === enums.armor.private_key)) { + throw new Error('Armored text not of type key'); + } + return read(input.data); + } catch (e) { + const result = { keys: [], err: [] }; + result.err.push(e); + return result; } - return readKeys(input.data); } diff --git a/src/key/helper.js b/src/key/helper.js index 99cc48b5..d13bc32c 100644 --- a/src/key/helper.js +++ b/src/key/helper.js @@ -7,32 +7,14 @@ * @module key/helper */ -import { - PublicKeyPacket, - PublicSubkeyPacket, - SecretKeyPacket, - SecretSubkeyPacket, - UserIDPacket, - UserAttributePacket, - SignaturePacket -} from '../packet'; +import packet from '../packet'; import enums from '../enums'; import config from '../config'; import crypto from '../crypto'; import util from '../util'; -export const allowedKeyPackets = { - PublicKeyPacket, - PublicSubkeyPacket, - SecretKeyPacket, - SecretSubkeyPacket, - UserIDPacket, - UserAttributePacket, - SignaturePacket -}; - export async function generateSecretSubkey(options) { - const secretSubkeyPacket = new SecretSubkeyPacket(options.date); + const secretSubkeyPacket = new packet.SecretSubkey(options.date); secretSubkeyPacket.packets = null; secretSubkeyPacket.algorithm = enums.read(enums.publicKey, options.algorithm); await secretSubkeyPacket.generate(options.rsaBits, options.curve); @@ -40,7 +22,7 @@ export async function generateSecretSubkey(options) { } export async function generateSecretKey(options) { - const secretKeyPacket = new SecretKeyPacket(options.date); + const secretKeyPacket = new packet.SecretKey(options.date); secretKeyPacket.packets = null; secretKeyPacket.algorithm = enums.read(enums.publicKey, options.algorithm); await secretKeyPacket.generate(options.rsaBits, options.curve); @@ -49,9 +31,9 @@ export async function generateSecretKey(options) { /** * Returns the valid and non-expired signature that has the latest creation date, while ignoring signatures created in the future. - * @param {Array} signatures List of signatures + * @param {Array} signatures List of signatures * @param {Date} date Use the given date instead of the current time - * @returns {Promise} The latest valid signature + * @returns {Promise} The latest valid signature * @async */ export async function getLatestValidSignature(signatures, primaryKey, signatureType, dataToVerify, date = new Date()) { @@ -62,10 +44,10 @@ export async function getLatestValidSignature(signatures, primaryKey, signatureT if ( (!signature || signatures[i].created >= signature.created) && // check binding signature is not expired (ie, check for V4 expiration time) - !signatures[i].isExpired(date) - ) { + !signatures[i].isExpired(date) && // check binding signature is verified - signatures[i].verified || await signatures[i].verify(primaryKey, signatureType, dataToVerify); + (signatures[i].verified || await signatures[i].verify(primaryKey, signatureType, dataToVerify)) + ) { signature = signatures[i]; } } catch (e) { @@ -75,8 +57,8 @@ export async function getLatestValidSignature(signatures, primaryKey, signatureT if (!signature) { throw util.wrapError( `Could not find valid ${enums.read(enums.signature, signatureType)} signature in key ${primaryKey.getKeyId().toHex()}` - .replace('certGeneric ', 'self-') - .replace(/([a-z])([A-Z])/g, (_, $1, $2) => $1 + ' ' + $2.toLowerCase()) + .replace('cert_generic ', 'self-') + .replace('_', ' ') , exception); } return signature; @@ -94,25 +76,25 @@ export function isDataExpired(keyPacket, signature, date = new Date()) { /** * Create Binding signature to the key according to the {@link https://tools.ietf.org/html/rfc4880#section-5.2.1} - * @param {SecretSubkeyPacket} subkey Subkey key packet - * @param {SecretKeyPacket} primaryKey Primary key packet + * @param {module:packet.SecretSubkey} subkey Subkey key packet + * @param {module:packet.SecretKey} primaryKey Primary key packet * @param {Object} options */ export async function createBindingSignature(subkey, primaryKey, options) { const dataToSign = {}; dataToSign.key = primaryKey; dataToSign.bind = subkey; - const subkeySignaturePacket = new SignaturePacket(options.date); - subkeySignaturePacket.signatureType = enums.signature.subkeyBinding; + const subkeySignaturePacket = new packet.Signature(options.date); + subkeySignaturePacket.signatureType = enums.signature.subkey_binding; subkeySignaturePacket.publicKeyAlgorithm = primaryKey.algorithm; subkeySignaturePacket.hashAlgorithm = await getPreferredHashAlgo(null, subkey); if (options.sign) { - subkeySignaturePacket.keyFlags = [enums.keyFlags.signData]; + subkeySignaturePacket.keyFlags = [enums.keyFlags.sign_data]; subkeySignaturePacket.embeddedSignature = await createSignaturePacket(dataToSign, null, subkey, { - signatureType: enums.signature.keyBinding + signatureType: enums.signature.key_binding }, options.date); } else { - subkeySignaturePacket.keyFlags = [enums.keyFlags.encryptCommunication | enums.keyFlags.encryptStorage]; + subkeySignaturePacket.keyFlags = [enums.keyFlags.encrypt_communication | enums.keyFlags.encrypt_storage]; } if (options.keyExpirationTime > 0) { subkeySignaturePacket.keyExpirationTime = options.keyExpirationTime; @@ -125,14 +107,14 @@ export async function createBindingSignature(subkey, primaryKey, options) { /** * Returns the preferred signature hash algorithm of a key * @param {module:key.Key} key (optional) the key to get preferences from - * @param {SecretKeyPacket|SecretSubkeyPacket} keyPacket key packet used for signing + * @param {module:packet.SecretKey|module:packet.SecretSubkey} keyPacket key packet used for signing * @param {Date} date (optional) use the given date for verification instead of the current time * @param {Object} userId (optional) user ID * @returns {Promise} * @async */ export async function getPreferredHashAlgo(key, keyPacket, date = new Date(), userId = {}) { - let hash_algo = config.preferHashAlgorithm; + let hash_algo = config.prefer_hash_algorithm; let pref_algo = hash_algo; if (key) { const primaryUser = await key.getPrimaryUser(date, userId); @@ -143,15 +125,15 @@ export async function getPreferredHashAlgo(key, keyPacket, date = new Date(), us } } switch (Object.getPrototypeOf(keyPacket)) { - case SecretKeyPacket.prototype: - case PublicKeyPacket.prototype: - case SecretSubkeyPacket.prototype: - case PublicSubkeyPacket.prototype: + case packet.SecretKey.prototype: + case packet.PublicKey.prototype: + case packet.SecretSubkey.prototype: + case packet.PublicSubkey.prototype: switch (keyPacket.algorithm) { case 'ecdh': case 'ecdsa': case 'eddsa': - pref_algo = crypto.publicKey.elliptic.getPreferredHashAlgo(keyPacket.publicParams.oid); + pref_algo = crypto.publicKey.elliptic.getPreferredHashAlgo(keyPacket.params[0]); } } return crypto.hash.getHashByteLength(hash_algo) <= crypto.hash.getHashByteLength(pref_algo) ? @@ -200,8 +182,8 @@ export async function getPreferredAlgo(type, keys, date = new Date(), userIds = /** * Create signature packet * @param {Object} dataToSign Contains packets to be signed - * @param {SecretKeyPacket| - * SecretSubkeyPacket} signingKeyPacket secret key packet for signing + * @param {module:packet.SecretKey| + * module:packet.SecretSubkey} signingKeyPacket secret key packet for signing * @param {Object} signatureProperties (optional) properties to write on the signature packet before signing * @param {Date} date (optional) override the creationtime of the signature * @param {Object} userId (optional) user ID @@ -210,13 +192,10 @@ export async function getPreferredAlgo(type, keys, date = new Date(), userIds = * @returns {module:packet/signature} signature packet */ export async function createSignaturePacket(dataToSign, privateKey, signingKeyPacket, signatureProperties, date, userId, detached = false, streaming = false) { - if (signingKeyPacket.isDummy()) { - throw new Error('Cannot sign with a gnu-dummy key.'); - } if (!signingKeyPacket.isDecrypted()) { throw new Error('Private key is not decrypted.'); } - const signaturePacket = new SignaturePacket(date); + const signaturePacket = new packet.Signature(date); Object.assign(signaturePacket, signatureProperties); signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; signaturePacket.hashAlgorithm = await getPreferredHashAlgo(privateKey, signingKeyPacket, date, userId); @@ -241,7 +220,7 @@ export async function mergeSignatures(source, dest, attr, checkFn) { await Promise.all(source.map(async function(sourceSig) { if (!sourceSig.isExpired() && (!checkFn || await checkFn(sourceSig)) && !dest[attr].some(function(destSig) { - return util.equalsUint8Array(destSig.write_params(), sourceSig.write_params()); + return util.equalsUint8Array(destSig.signature, sourceSig.signature); })) { dest[attr].push(sourceSig); } @@ -252,15 +231,15 @@ export async function mergeSignatures(source, dest, attr, checkFn) { /** * Checks if a given certificate or binding signature is revoked - * @param {SecretKeyPacket| - * PublicKeyPacket} primaryKey The primary key packet - * @param {Object} dataToVerify The data to check - * @param {Array} revocations The revocation signatures to check - * @param {SignaturePacket} signature The certificate or signature to check - * @param {PublicSubkeyPacket| - * SecretSubkeyPacket| - * PublicKeyPacket| - * SecretKeyPacket} key, optional The key packet to check the signature + * @param {module:packet.SecretKey| + * module:packet.PublicKey} primaryKey The primary key packet + * @param {Object} dataToVerify The data to check + * @param {Array} revocations The revocation signatures to check + * @param {module:packet.Signature} signature The certificate or signature to check + * @param {module:packet.PublicSubkey| + * module:packet.SecretSubkey| + * module:packet.PublicKey| + * module:packet.SecretKey} key, optional The key packet to check the signature * @param {Date} date Use the given date instead of the current time * @returns {Promise} True if the signature revokes the data * @async @@ -281,10 +260,9 @@ export async function isDataRevoked(primaryKey, signatureType, dataToVerify, rev // third-party key certification, which should only affect // `verifyAllCertifications`.) (!signature || revocationSignature.issuerKeyId.equals(signature.issuerKeyId)) && - !(config.revocationsExpire && revocationSignature.isExpired(normDate)) + !(config.revocations_expire && revocationSignature.isExpired(normDate)) && + (revocationSignature.verified || await revocationSignature.verify(key, signatureType, dataToVerify)) ) { - revocationSignature.verified || await revocationSignature.verify(key, signatureType, dataToVerify); - // TODO get an identifier of the revoked object instead revocationKeyIds.push(revocationSignature.issuerKeyId); } @@ -330,7 +308,6 @@ export async function isAeadSupported(keys, date = new Date(), userIds = []) { } export function sanitizeKeyOptions(options, subkeyDefaults = {}) { - options.type = options.type || subkeyDefaults.type; options.curve = options.curve || subkeyDefaults.curve; options.rsaBits = options.rsaBits || subkeyDefaults.rsaBits; options.keyExpirationTime = options.keyExpirationTime !== undefined ? options.keyExpirationTime : subkeyDefaults.keyExpirationTime; @@ -339,27 +316,24 @@ export function sanitizeKeyOptions(options, subkeyDefaults = {}) { options.sign = options.sign || false; - switch (options.type) { - case 'ecc': - try { - options.curve = enums.write(enums.curve, options.curve); - } catch (e) { - throw new Error('Invalid curve'); - } - if (options.curve === enums.curve.ed25519 || options.curve === enums.curve.curve25519) { - options.curve = options.sign ? enums.curve.ed25519 : enums.curve.curve25519; - } - if (options.sign) { - options.algorithm = options.curve === enums.curve.ed25519 ? enums.publicKey.eddsa : enums.publicKey.ecdsa; - } else { - options.algorithm = enums.publicKey.ecdh; - } - break; - case 'rsa': - options.algorithm = enums.publicKey.rsaEncryptSign; - break; - default: - throw new Error(`Unsupported key type ${options.type}`); + if (options.curve) { + try { + options.curve = enums.write(enums.curve, options.curve); + } catch (e) { + throw new Error('Not valid curve.'); + } + if (options.curve === enums.curve.ed25519 || options.curve === enums.curve.curve25519) { + options.curve = options.sign ? enums.curve.ed25519 : enums.curve.curve25519; + } + if (options.sign) { + options.algorithm = options.curve === enums.curve.ed25519 ? enums.publicKey.eddsa : enums.publicKey.ecdsa; + } else { + options.algorithm = enums.publicKey.ecdh; + } + } else if (options.rsaBits) { + options.algorithm = enums.publicKey.rsa_encrypt_sign; + } else { + throw new Error('Unrecognized key type'); } return options; } @@ -368,11 +342,11 @@ export function isValidSigningKeyPacket(keyPacket, signature) { if (!signature.verified || signature.revoked !== false) { // Sanity check throw new Error('Signature not verified'); } - return keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.rsaEncrypt) && + return keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.rsa_encrypt) && keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.elgamal) && keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.ecdh) && (!signature.keyFlags || - (signature.keyFlags[0] & enums.keyFlags.signData) !== 0); + (signature.keyFlags[0] & enums.keyFlags.sign_data) !== 0); } export function isValidEncryptionKeyPacket(keyPacket, signature) { @@ -380,12 +354,12 @@ export function isValidEncryptionKeyPacket(keyPacket, signature) { throw new Error('Signature not verified'); } return keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.dsa) && - keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.rsaSign) && + keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.rsa_sign) && keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.ecdsa) && keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.eddsa) && (!signature.keyFlags || - (signature.keyFlags[0] & enums.keyFlags.encryptCommunication) !== 0 || - (signature.keyFlags[0] & enums.keyFlags.encryptStorage) !== 0); + (signature.keyFlags[0] & enums.keyFlags.encrypt_communication) !== 0 || + (signature.keyFlags[0] & enums.keyFlags.encrypt_storage) !== 0); } export function isValidDecryptionKeyPacket(signature) { @@ -393,12 +367,12 @@ export function isValidDecryptionKeyPacket(signature) { throw new Error('Signature not verified'); } - if (config.allowInsecureDecryptionWithSigningKeys) { + if (config.allow_insecure_decryption_with_signing_keys) { // This is only relevant for RSA keys, all other signing ciphers cannot decrypt return true; } return !signature.keyFlags || - (signature.keyFlags[0] & enums.keyFlags.encryptCommunication) !== 0 || - (signature.keyFlags[0] & enums.keyFlags.encryptStorage) !== 0; + (signature.keyFlags[0] & enums.keyFlags.encrypt_communication) !== 0 || + (signature.keyFlags[0] & enums.keyFlags.encrypt_storage) !== 0; } diff --git a/src/key/index.js b/src/key/index.js index 2aee3bb7..966fdf00 100644 --- a/src/key/index.js +++ b/src/key/index.js @@ -4,9 +4,9 @@ */ import { - readKey, readArmoredKey, - readKeys, readArmoredKeys, + readArmored, generate, + read, reformat } from './factory'; @@ -20,9 +20,9 @@ import { import Key from './key.js'; export { - readKey, readArmoredKey, - readKeys, readArmoredKeys, + readArmored, generate, + read, reformat, getPreferredAlgo, isAeadSupported, diff --git a/src/key/key.js b/src/key/key.js index f79c7c7d..3bce4b78 100644 --- a/src/key/key.js +++ b/src/key/key.js @@ -25,14 +25,8 @@ * @module key/Key */ -import { armor, unarmor } from '../encoding/armor'; -import { - PacketList, - PublicKeyPacket, - PublicSubkeyPacket, - SignaturePacket -} from '../packet'; -import config from '../config'; +import armor from '../encoding/armor'; +import packet from '../packet'; import enums from '../enums'; import util from '../util'; import User from './user'; @@ -40,873 +34,855 @@ import SubKey from './subkey'; import * as helper from './helper'; /** - * Class that represents an OpenPGP key. Must contain a primary key. + * @class + * @classdesc Class that represents an OpenPGP key. Must contain a primary key. * Can contain additional subkeys, signatures, user ids, user attributes. - * @borrows PublicKeyPacket#getKeyId as Key#getKeyId - * @borrows PublicKeyPacket#getFingerprint as Key#getFingerprint - * @borrows PublicKeyPacket#hasSameFingerprintAs as Key#hasSameFingerprintAs - * @borrows PublicKeyPacket#getAlgorithmInfo as Key#getAlgorithmInfo - * @borrows PublicKeyPacket#getCreationTime as Key#getCreationTime - */ -class Key { - /** - * @param {PacketList} packetlist The packets that form this key - */ - constructor(packetlist) { - if (!(this instanceof Key)) { - return new Key(packetlist); - } - // same data as in packetlist but in structured form - this.keyPacket = null; - this.revocationSignatures = []; - this.directSignatures = []; - this.users = []; - this.subKeys = []; - this.packetlist2structure(packetlist); - if (!this.keyPacket) { - throw new Error('Invalid key: need at least key packet'); - } + * @param {module:packet.List} packetlist The packets that form this key + * @borrows module:packet.PublicKey#getKeyId as Key#getKeyId + * @borrows module:packet.PublicKey#getFingerprint as Key#getFingerprint + * @borrows module:packet.PublicKey#hasSameFingerprintAs as Key#hasSameFingerprintAs + * @borrows module:packet.PublicKey#getAlgorithmInfo as Key#getAlgorithmInfo + * @borrows module:packet.PublicKey#getCreationTime as Key#getCreationTime + * @borrows module:packet.PublicKey#isDecrypted as Key#isDecrypted + */ +export default function Key(packetlist) { + if (!(this instanceof Key)) { + return new Key(packetlist); + } + // same data as in packetlist but in structured form + this.keyPacket = null; + this.revocationSignatures = []; + this.directSignatures = []; + this.users = []; + this.subKeys = []; + this.packetlist2structure(packetlist); + if (!this.keyPacket || !this.users.length) { + throw new Error('Invalid key: need at least key and user ID packet'); } +} - get primaryKey() { +Object.defineProperty(Key.prototype, 'primaryKey', { + get() { return this.keyPacket; - } + }, + configurable: true, + enumerable: true +}); - /** - * Transforms packetlist to structured key data - * @param {PacketList} packetlist The packets that form a key - */ - packetlist2structure(packetlist) { - let user; - let primaryKeyId; - let subKey; - for (let i = 0; i < packetlist.length; i++) { - switch (packetlist[i].tag) { - case enums.packet.publicKey: - case enums.packet.secretKey: - if (this.keyPacket) { - throw new Error('Key block contains multiple keys'); - } - this.keyPacket = packetlist[i]; - primaryKeyId = this.getKeyId(); - break; - case enums.packet.userID: - case enums.packet.userAttribute: - user = new User(packetlist[i]); - this.users.push(user); - break; - case enums.packet.publicSubkey: - case enums.packet.secretSubkey: - user = null; - subKey = new SubKey(packetlist[i]); - this.subKeys.push(subKey); - break; - case enums.packet.signature: - switch (packetlist[i].signatureType) { - case enums.signature.certGeneric: - case enums.signature.certPersona: - case enums.signature.certCasual: - case enums.signature.certPositive: - if (!user) { - util.printDebug('Dropping certification signatures without preceding user packet'); - continue; - } - if (packetlist[i].issuerKeyId.equals(primaryKeyId)) { - user.selfCertifications.push(packetlist[i]); - } else { - user.otherCertifications.push(packetlist[i]); - } - break; - case enums.signature.certRevocation: - if (user) { - user.revocationSignatures.push(packetlist[i]); - } else { - this.directSignatures.push(packetlist[i]); - } - break; - case enums.signature.key: +/** + * Transforms packetlist to structured key data + * @param {module:packet.List} packetlist The packets that form a key + */ +Key.prototype.packetlist2structure = function(packetlist) { + let user; + let primaryKeyId; + let subKey; + for (let i = 0; i < packetlist.length; i++) { + switch (packetlist[i].tag) { + case enums.packet.publicKey: + case enums.packet.secretKey: + this.keyPacket = packetlist[i]; + primaryKeyId = this.getKeyId(); + break; + case enums.packet.userid: + case enums.packet.userAttribute: + user = new User(packetlist[i]); + this.users.push(user); + break; + case enums.packet.publicSubkey: + case enums.packet.secretSubkey: + user = null; + subKey = new SubKey(packetlist[i]); + this.subKeys.push(subKey); + break; + case enums.packet.signature: + switch (packetlist[i].signatureType) { + case enums.signature.cert_generic: + case enums.signature.cert_persona: + case enums.signature.cert_casual: + case enums.signature.cert_positive: + if (!user) { + util.print_debug('Dropping certification signatures without preceding user packet'); + continue; + } + if (packetlist[i].issuerKeyId.equals(primaryKeyId)) { + user.selfCertifications.push(packetlist[i]); + } else { + user.otherCertifications.push(packetlist[i]); + } + break; + case enums.signature.cert_revocation: + if (user) { + user.revocationSignatures.push(packetlist[i]); + } else { this.directSignatures.push(packetlist[i]); - break; - case enums.signature.subkeyBinding: - if (!subKey) { - util.printDebug('Dropping subkey binding signature without preceding subkey packet'); - continue; - } - subKey.bindingSignatures.push(packetlist[i]); - break; - case enums.signature.keyRevocation: - this.revocationSignatures.push(packetlist[i]); - break; - case enums.signature.subkeyRevocation: - if (!subKey) { - util.printDebug('Dropping subkey revocation signature without preceding subkey packet'); - continue; - } - subKey.revocationSignatures.push(packetlist[i]); - break; - } - break; - } + } + break; + case enums.signature.key: + this.directSignatures.push(packetlist[i]); + break; + case enums.signature.subkey_binding: + if (!subKey) { + util.print_debug('Dropping subkey binding signature without preceding subkey packet'); + continue; + } + subKey.bindingSignatures.push(packetlist[i]); + break; + case enums.signature.key_revocation: + this.revocationSignatures.push(packetlist[i]); + break; + case enums.signature.subkey_revocation: + if (!subKey) { + util.print_debug('Dropping subkey revocation signature without preceding subkey packet'); + continue; + } + subKey.revocationSignatures.push(packetlist[i]); + break; + } + break; } } +}; - /** - * Transforms structured key data to packetlist - * @returns {PacketList} The packets that form a key - */ - toPacketlist() { - const packetlist = new PacketList(); - packetlist.push(this.keyPacket); - packetlist.concat(this.revocationSignatures); - packetlist.concat(this.directSignatures); - this.users.map(user => packetlist.concat(user.toPacketlist())); - this.subKeys.map(subKey => packetlist.concat(subKey.toPacketlist())); - return packetlist; - } - - /** - * Clones the key object - * @returns {Promise} shallow clone of the key - * @async - */ - async clone() { - return new Key(this.toPacketlist()); - } - - /** - * Returns an array containing all public or private subkeys matching keyId; - * If keyId is not present, returns all subkeys. - * @param {type/keyid} keyId - * @returns {Array} - */ - getSubkeys(keyId = null) { - const subKeys = []; - this.subKeys.forEach(subKey => { - if (!keyId || subKey.getKeyId().equals(keyId, true)) { - subKeys.push(subKey); - } - }); - return subKeys; - } - - /** - * Returns an array containing all public or private keys matching keyId. - * If keyId is not present, returns all keys starting with the primary key. - * @param {type/keyid} keyId - * @returns {Array} - */ - getKeys(keyId = null) { - const keys = []; - if (!keyId || this.getKeyId().equals(keyId, true)) { - keys.push(this); - } - return keys.concat(this.getSubkeys(keyId)); - } - - /** - * Returns key IDs of all keys - * @returns {Array} - */ - getKeyIds() { - return this.getKeys().map(key => key.getKeyId()); - } - - /** - * Returns userids - * @returns {Array} array of userids - */ - getUserIds() { - return this.users.map(user => { - return user.userId ? user.userId.userid : null; - }).filter(userid => userid !== null); - } - - /** - * Returns true if this is a public key - * @returns {Boolean} - */ - isPublic() { - return this.keyPacket.tag === enums.packet.publicKey; - } - - /** - * Returns true if this is a private key - * @returns {Boolean} - */ - isPrivate() { - return this.keyPacket.tag === enums.packet.secretKey; - } - - /** - * Returns key as public key (shallow copy) - * @returns {module:key.Key} new public Key - */ - toPublic() { - const packetlist = new PacketList(); - const keyPackets = this.toPacketlist(); - let bytes; - let pubKeyPacket; - let pubSubkeyPacket; - for (let i = 0; i < keyPackets.length; i++) { - switch (keyPackets[i].tag) { - case enums.packet.secretKey: - bytes = keyPackets[i].writePublicKey(); - pubKeyPacket = new PublicKeyPacket(); - pubKeyPacket.read(bytes); - packetlist.push(pubKeyPacket); - break; - case enums.packet.secretSubkey: - bytes = keyPackets[i].writePublicKey(); - pubSubkeyPacket = new PublicSubkeyPacket(); - pubSubkeyPacket.read(bytes); - packetlist.push(pubSubkeyPacket); - break; - default: - packetlist.push(keyPackets[i]); - } +/** + * Transforms structured key data to packetlist + * @returns {module:packet.List} The packets that form a key + */ +Key.prototype.toPacketlist = function() { + const packetlist = new packet.List(); + packetlist.push(this.keyPacket); + packetlist.concat(this.revocationSignatures); + packetlist.concat(this.directSignatures); + this.users.map(user => packetlist.concat(user.toPacketlist())); + this.subKeys.map(subKey => packetlist.concat(subKey.toPacketlist())); + return packetlist; +}; + +/** + * Returns an array containing all public or private subkeys matching keyId; + * If keyId is not present, returns all subkeys. + * @param {type/keyid} keyId + * @returns {Array} + */ +Key.prototype.getSubkeys = function(keyId = null) { + const subKeys = []; + this.subKeys.forEach(subKey => { + if (!keyId || subKey.getKeyId().equals(keyId, true)) { + subKeys.push(subKey); } - return new Key(packetlist); + }); + return subKeys; +}; + +/** + * Returns an array containing all public or private keys matching keyId. + * If keyId is not present, returns all keys starting with the primary key. + * @param {type/keyid} keyId + * @returns {Array} + */ +Key.prototype.getKeys = function(keyId = null) { + const keys = []; + if (!keyId || this.getKeyId().equals(keyId, true)) { + keys.push(this); } + return keys.concat(this.getSubkeys(keyId)); +}; + +/** + * Returns key IDs of all keys + * @returns {Array} + */ +Key.prototype.getKeyIds = function() { + return this.getKeys().map(key => key.getKeyId()); +}; + +/** + * Returns userids + * @returns {Array} array of userids + */ +Key.prototype.getUserIds = function() { + return this.users.map(user => { + return user.userId ? user.userId.userid : null; + }).filter(userid => userid !== null); +}; + +/** + * Returns true if this is a public key + * @returns {Boolean} + */ +Key.prototype.isPublic = function() { + return this.keyPacket.tag === enums.packet.publicKey; +}; - /** - * Returns ASCII armored text of key - * @returns {ReadableStream} ASCII armor - */ - armor() { - const type = this.isPublic() ? enums.armor.publicKey : enums.armor.privateKey; - return armor(type, this.toPacketlist().write()); - } - - /** - * Returns last created key or key by given keyId that is available for signing and verification - * @param {module:type/keyid} keyId, optional - * @param {Date} date (optional) use the given date for verification instead of the current time - * @param {Object} userId, optional user ID - * @returns {Promise} key or null if no signing key has been found - * @async - */ - async getSigningKey(keyId = null, date = new Date(), userId = {}) { - await this.verifyPrimaryKey(date, userId); - const primaryKey = this.keyPacket; - const subKeys = this.subKeys.slice().sort((a, b) => b.keyPacket.created - a.keyPacket.created); - let exception; - for (let i = 0; i < subKeys.length; i++) { - if (!keyId || subKeys[i].getKeyId().equals(keyId)) { - try { - await subKeys[i].verify(primaryKey, date); - const dataToVerify = { key: primaryKey, bind: subKeys[i].keyPacket }; - const bindingSignature = await helper.getLatestValidSignature(subKeys[i].bindingSignatures, primaryKey, enums.signature.subkeyBinding, dataToVerify, date); - if ( - bindingSignature && - bindingSignature.embeddedSignature && - helper.isValidSigningKeyPacket(subKeys[i].keyPacket, bindingSignature) && - await helper.getLatestValidSignature([bindingSignature.embeddedSignature], subKeys[i].keyPacket, enums.signature.keyBinding, dataToVerify, date) - ) { - return subKeys[i]; - } - } catch (e) { - exception = e; +/** + * Returns true if this is a private key + * @returns {Boolean} + */ +Key.prototype.isPrivate = function() { + return this.keyPacket.tag === enums.packet.secretKey; +}; + +/** + * Returns key as public key (shallow copy) + * @returns {module:key.Key} new public Key + */ +Key.prototype.toPublic = function() { + const packetlist = new packet.List(); + const keyPackets = this.toPacketlist(); + let bytes; + let pubKeyPacket; + let pubSubkeyPacket; + for (let i = 0; i < keyPackets.length; i++) { + switch (keyPackets[i].tag) { + case enums.packet.secretKey: + bytes = keyPackets[i].writePublicKey(); + pubKeyPacket = new packet.PublicKey(); + pubKeyPacket.read(bytes); + packetlist.push(pubKeyPacket); + break; + case enums.packet.secretSubkey: + bytes = keyPackets[i].writePublicKey(); + pubSubkeyPacket = new packet.PublicSubkey(); + pubSubkeyPacket.read(bytes); + packetlist.push(pubSubkeyPacket); + break; + default: + packetlist.push(keyPackets[i]); + } + } + return new Key(packetlist); +}; + +/** + * Returns ASCII armored text of key + * @returns {ReadableStream} ASCII armor + */ +Key.prototype.armor = function() { + const type = this.isPublic() ? enums.armor.public_key : enums.armor.private_key; + return armor.encode(type, this.toPacketlist().write()); +}; + +/** + * Returns last created key or key by given keyId that is available for signing and verification + * @param {module:type/keyid} keyId, optional + * @param {Date} date (optional) use the given date for verification instead of the current time + * @param {Object} userId, optional user ID + * @returns {Promise} key or null if no signing key has been found + * @async + */ +Key.prototype.getSigningKey = async function (keyId = null, date = new Date(), userId = {}) { + await this.verifyPrimaryKey(date, userId); + const primaryKey = this.keyPacket; + const subKeys = this.subKeys.slice().sort((a, b) => b.keyPacket.created - a.keyPacket.created); + let exception; + for (let i = 0; i < subKeys.length; i++) { + if (!keyId || subKeys[i].getKeyId().equals(keyId)) { + try { + await subKeys[i].verify(primaryKey, date); + const dataToVerify = { key: primaryKey, bind: subKeys[i].keyPacket }; + const bindingSignature = await helper.getLatestValidSignature(subKeys[i].bindingSignatures, primaryKey, enums.signature.subkey_binding, dataToVerify, date); + if ( + bindingSignature && + bindingSignature.embeddedSignature && + helper.isValidSigningKeyPacket(subKeys[i].keyPacket, bindingSignature) && + await helper.getLatestValidSignature([bindingSignature.embeddedSignature], subKeys[i].keyPacket, enums.signature.key_binding, dataToVerify, date) + ) { + return subKeys[i]; } + } catch (e) { + exception = e; } } - const primaryUser = await this.getPrimaryUser(date, userId); - if ((!keyId || primaryKey.getKeyId().equals(keyId)) && - helper.isValidSigningKeyPacket(primaryKey, primaryUser.selfCertification)) { - return this; - } - throw util.wrapError('Could not find valid signing key packet in key ' + this.getKeyId().toHex(), exception); - } - - /** - * Returns last created key or key by given keyId that is available for encryption or decryption - * @param {module:type/keyid} keyId, optional - * @param {Date} date, optional - * @param {String} userId, optional - * @returns {Promise} key or null if no encryption key has been found - * @async - */ - async getEncryptionKey(keyId, date = new Date(), userId = {}) { - await this.verifyPrimaryKey(date, userId); - const primaryKey = this.keyPacket; - // V4: by convention subkeys are preferred for encryption service - const subKeys = this.subKeys.slice().sort((a, b) => b.keyPacket.created - a.keyPacket.created); - let exception; - for (let i = 0; i < subKeys.length; i++) { - if (!keyId || subKeys[i].getKeyId().equals(keyId)) { - try { - await subKeys[i].verify(primaryKey, date); - const dataToVerify = { key: primaryKey, bind: subKeys[i].keyPacket }; - const bindingSignature = await helper.getLatestValidSignature(subKeys[i].bindingSignatures, primaryKey, enums.signature.subkeyBinding, dataToVerify, date); - if (bindingSignature && helper.isValidEncryptionKeyPacket(subKeys[i].keyPacket, bindingSignature)) { - return subKeys[i]; - } - } catch (e) { - exception = e; + } + const primaryUser = await this.getPrimaryUser(date, userId); + if ((!keyId || primaryKey.getKeyId().equals(keyId)) && + helper.isValidSigningKeyPacket(primaryKey, primaryUser.selfCertification)) { + return this; + } + throw util.wrapError('Could not find valid signing key packet in key ' + this.getKeyId().toHex(), exception); +}; + +/** + * Returns last created key or key by given keyId that is available for encryption or decryption + * @param {module:type/keyid} keyId, optional + * @param {Date} date, optional + * @param {String} userId, optional + * @returns {Promise} key or null if no encryption key has been found + * @async + */ +Key.prototype.getEncryptionKey = async function(keyId, date = new Date(), userId = {}) { + await this.verifyPrimaryKey(date, userId); + const primaryKey = this.keyPacket; + // V4: by convention subkeys are preferred for encryption service + const subKeys = this.subKeys.slice().sort((a, b) => b.keyPacket.created - a.keyPacket.created); + let exception; + for (let i = 0; i < subKeys.length; i++) { + if (!keyId || subKeys[i].getKeyId().equals(keyId)) { + try { + await subKeys[i].verify(primaryKey, date); + const dataToVerify = { key: primaryKey, bind: subKeys[i].keyPacket }; + const bindingSignature = await helper.getLatestValidSignature(subKeys[i].bindingSignatures, primaryKey, enums.signature.subkey_binding, dataToVerify, date); + if (bindingSignature && helper.isValidEncryptionKeyPacket(subKeys[i].keyPacket, bindingSignature)) { + return subKeys[i]; } + } catch (e) { + exception = e; } } - // if no valid subkey for encryption, evaluate primary key - const primaryUser = await this.getPrimaryUser(date, userId); - if ((!keyId || primaryKey.getKeyId().equals(keyId)) && - helper.isValidEncryptionKeyPacket(primaryKey, primaryUser.selfCertification)) { - return this; - } - throw util.wrapError('Could not find valid encryption key packet in key ' + this.getKeyId().toHex(), exception); - } - - /** - * Returns all keys that are available for decryption, matching the keyId when given - * This is useful to retrieve keys for session key decryption - * @param {module:type/keyid} keyId, optional - * @param {Date} date, optional - * @param {String} userId, optional - * @returns {Promise>} array of decryption keys - * @async - */ - async getDecryptionKeys(keyId, date = new Date(), userId = {}) { - const primaryKey = this.keyPacket; - const keys = []; - for (let i = 0; i < this.subKeys.length; i++) { - if (!keyId || this.subKeys[i].getKeyId().equals(keyId, true)) { - try { - const dataToVerify = { key: primaryKey, bind: this.subKeys[i].keyPacket }; - const bindingSignature = await helper.getLatestValidSignature(this.subKeys[i].bindingSignatures, primaryKey, enums.signature.subkeyBinding, dataToVerify, date); - if (bindingSignature && helper.isValidDecryptionKeyPacket(bindingSignature)) { - keys.push(this.subKeys[i]); - } - } catch (e) {} - } - } + } + // if no valid subkey for encryption, evaluate primary key + const primaryUser = await this.getPrimaryUser(date, userId); + if ((!keyId || primaryKey.getKeyId().equals(keyId)) && + helper.isValidEncryptionKeyPacket(primaryKey, primaryUser.selfCertification)) { + return this; + } + throw util.wrapError('Could not find valid encryption key packet in key ' + this.getKeyId().toHex(), exception); +}; - // evaluate primary key - const primaryUser = await this.getPrimaryUser(date, userId); - if ((!keyId || primaryKey.getKeyId().equals(keyId, true)) && - helper.isValidDecryptionKeyPacket(primaryUser.selfCertification)) { - keys.push(this); +/** + * Returns all keys that are available for decryption, matching the keyId when given + * This is useful to retrieve keys for session key decryption + * @param {module:type/keyid} keyId, optional + * @param {Date} date, optional + * @param {String} userId, optional + * @returns {Promise>} array of decryption keys + * @async + */ +Key.prototype.getDecryptionKeys = async function(keyId, date = new Date(), userId = {}) { + const primaryKey = this.keyPacket; + const keys = []; + for (let i = 0; i < this.subKeys.length; i++) { + if (!keyId || this.subKeys[i].getKeyId().equals(keyId, true)) { + try { + const dataToVerify = { key: primaryKey, bind: this.subKeys[i].keyPacket }; + const bindingSignature = await helper.getLatestValidSignature(this.subKeys[i].bindingSignatures, primaryKey, enums.signature.subkey_binding, dataToVerify, date); + if (bindingSignature && helper.isValidDecryptionKeyPacket(bindingSignature)) { + keys.push(this.subKeys[i]); + } + } catch (e) {} } + } - return keys; + // evaluate primary key + const primaryUser = await this.getPrimaryUser(date, userId); + if ((!keyId || primaryKey.getKeyId().equals(keyId, true)) && + helper.isValidDecryptionKeyPacket(primaryUser.selfCertification)) { + keys.push(this); } - /** - * Encrypts all secret key and subkey packets matching keyId - * @param {String|Array} passphrases - if multiple passphrases, then should be in same order as packets each should encrypt - * @param {module:type/keyid} keyId - * @throws {Error} if encryption failed for any key or subkey - * @async - */ - async encrypt(passphrases, keyId = null) { - if (!this.isPrivate()) { - throw new Error("Nothing to encrypt in a public key"); - } + return keys; +}; - const keys = this.getKeys(keyId); - passphrases = util.isArray(passphrases) ? passphrases : new Array(keys.length).fill(passphrases); - if (passphrases.length !== keys.length) { - throw new Error("Invalid number of passphrases for key"); - } +/** + * Encrypts all secret key and subkey packets matching keyId + * @param {String|Array} passphrases - if multiple passphrases, then should be in same order as packets each should encrypt + * @param {module:type/keyid} keyId + * @returns {Promise>} + * @async + */ +Key.prototype.encrypt = async function(passphrases, keyId = null) { + if (!this.isPrivate()) { + throw new Error("Nothing to encrypt in a public key"); + } - await Promise.all(keys.map(async function(key, i) { - const { keyPacket } = key; - await keyPacket.encrypt(passphrases[i]); - keyPacket.clearPrivateParams(); - })); + const keys = this.getKeys(keyId); + passphrases = util.isArray(passphrases) ? passphrases : new Array(keys.length).fill(passphrases); + if (passphrases.length !== keys.length) { + throw new Error("Invalid number of passphrases for key"); } - /** - * Decrypts all secret key and subkey packets matching keyId - * @param {String|Array} passphrases - * @param {module:type/keyid} keyId - * @throws {Error} if any matching key or subkey packets did not decrypt successfully - * @async - */ - async decrypt(passphrases, keyId = null) { - if (!this.isPrivate()) { - throw new Error("Nothing to decrypt in a public key"); - } - passphrases = util.isArray(passphrases) ? passphrases : [passphrases]; - - await Promise.all(this.getKeys(keyId).map(async function(key) { - let decrypted = false; - let error = null; - await Promise.all(passphrases.map(async function(passphrase) { - try { - await key.keyPacket.decrypt(passphrase); - // If we are decrypting a single key packet, we also validate it directly - if (keyId) await key.keyPacket.validate(); - decrypted = true; - } catch (e) { - error = e; - } - })); - if (!decrypted) { - throw error; - } - })); + return Promise.all(keys.map(async function(key, i) { + const { keyPacket } = key; + await keyPacket.encrypt(passphrases[i]); + keyPacket.clearPrivateParams(); + return keyPacket; + })); +}; - if (!keyId) { - // The full key should be decrypted and we can validate it all - await this.validate(); - } +/** + * Decrypts all secret key and subkey packets matching keyId + * @param {String|Array} passphrases + * @param {module:type/keyid} keyId + * @returns {Promise} true if all matching key and subkey packets decrypted successfully + * @throws {Error} if any matching key or subkey packets did not decrypt successfully + * @async + */ +Key.prototype.decrypt = async function(passphrases, keyId = null) { + if (!this.isPrivate()) { + throw new Error("Nothing to decrypt in a public key"); } + passphrases = util.isArray(passphrases) ? passphrases : [passphrases]; - /** - * Returns true if the primary key or any subkey is decrypted. - * A dummy key is considered encrypted. - */ - isDecrypted() { - return this.getKeys().some(({ keyPacket }) => keyPacket.isDecrypted()); - } - - /** - * Check whether the private and public primary key parameters correspond - * Together with verification of binding signatures, this guarantees key integrity - * In case of gnu-dummy primary key, it is enough to validate any signing subkeys - * otherwise all encryption subkeys are validated - * If only gnu-dummy keys are found, we cannot properly validate so we throw an error - * @throws {Error} if validation was not successful and the key cannot be trusted - * @async - */ - async validate() { - if (!this.isPrivate()) { - throw new Error("Cannot validate a public key"); - } - - let signingKeyPacket; - if (!this.primaryKey.isDummy()) { - signingKeyPacket = this.primaryKey; - } else { - /** - * It is enough to validate any signing keys - * since its binding signatures are also checked - */ - const signingKey = await this.getSigningKey(null, null); - // This could again be a dummy key - if (signingKey && !signingKey.keyPacket.isDummy()) { - signingKeyPacket = signingKey.keyPacket; + const results = await Promise.all(this.getKeys(keyId).map(async function(key) { + let decrypted = false; + let error = null; + await Promise.all(passphrases.map(async function(passphrase) { + try { + await key.keyPacket.decrypt(passphrase); + // If we are decrypting a single key packet, we also validate it directly + if (keyId) await key.keyPacket.validate(); + decrypted = true; + } catch (e) { + error = e; } + })); + if (!decrypted) { + throw error; } + return decrypted; + })); - if (signingKeyPacket) { - return signingKeyPacket.validate(); - } else { - const keys = this.getKeys(); - const allDummies = keys.map(key => key.keyPacket.isDummy()).every(Boolean); - if (allDummies) { - throw new Error("Cannot validate an all-gnu-dummy key"); - } + if (!keyId) { + // The full key should be decrypted and we can validate it all + await this.validate(); + } - return Promise.all(keys.map(async key => key.keyPacket.validate())); - } + return results.every(result => result === true); +}; + +/** + * Check whether the private and public primary key parameters correspond + * Together with verification of binding signatures, this guarantees key integrity + * In case of gnu-dummy primary key, it is enough to validate any signing subkeys + * otherwise all encryption subkeys are validated + * If only gnu-dummy keys are found, we cannot properly validate so we throw an error + * @throws {Error} if validation was not successful and the key cannot be trusted + * @async + */ +Key.prototype.validate = async function() { + if (!this.isPrivate()) { + throw new Error("Cannot validate a public key"); } - /** - * Clear private key parameters - */ - clearPrivateParams() { - if (!this.isPrivate()) { - throw new Error("Can't clear private parameters of a public key"); + let signingKeyPacket; + if (!this.primaryKey.isDummy()) { + signingKeyPacket = this.primaryKey; + } else { + /** + * It is enough to validate any signing keys + * since its binding signatures are also checked + */ + const signingKey = await this.getSigningKey(null, null); + // This could again be a dummy key + if (signingKey && !signingKey.keyPacket.isDummy()) { + signingKeyPacket = signingKey.keyPacket; } - this.getKeys().forEach(({ keyPacket }) => { - if (keyPacket.isDecrypted()) { - keyPacket.clearPrivateParams(); - } - }); } - /** - * Checks if a signature on a key is revoked - * @param {SignaturePacket} signature The signature to verify - * @param {PublicSubkeyPacket| - * SecretSubkeyPacket| - * PublicKeyPacket| - * SecretKeyPacket} key, optional The key to verify the signature - * @param {Date} date Use the given date instead of the current time - * @returns {Promise} True if the certificate is revoked - * @async - */ - async isRevoked(signature, key, date = new Date()) { - return helper.isDataRevoked( - this.keyPacket, enums.signature.keyRevocation, { key: this.keyPacket }, this.revocationSignatures, signature, key, date - ); - } - - /** - * Verify primary key. Checks for revocation signatures, expiration time - * and valid self signature. Throws if the primary key is invalid. - * @param {Date} date (optional) use the given date for verification instead of the current time - * @param {Object} userId (optional) user ID - * @throws {Error} If key verification failed - * @async - */ - async verifyPrimaryKey(date = new Date(), userId = {}) { - const primaryKey = this.keyPacket; - // check for key revocation signatures - if (await this.isRevoked(null, null, date)) { - throw new Error('Primary key is revoked'); - } - // check for valid, unrevoked, unexpired self signature - const { selfCertification } = await this.getPrimaryUser(date, userId); - // check for expiration time - if (helper.isDataExpired(primaryKey, selfCertification, date)) { - throw new Error('Primary key is expired'); + if (signingKeyPacket) { + return signingKeyPacket.validate(); + } else { + const keys = this.getKeys(); + const allDummies = keys.map(key => key.keyPacket.isDummy()).every(Boolean); + if (allDummies) { + throw new Error("Cannot validate an all-gnu-dummy key"); } + + return Promise.all(keys.map(async key => key.keyPacket.validate())); } +}; - /** - * Returns the latest date when the key can be used for encrypting, signing, or both, depending on the `capabilities` paramater. - * When `capabilities` is null, defaults to returning the expiry date of the primary key. - * Returns null if `capabilities` is passed and the key does not have the specified capabilities or is revoked or invalid. - * Returns Infinity if the key doesn't expire. - * @param {encrypt|sign|encrypt_sign} capabilities, optional - * @param {module:type/keyid} keyId, optional - * @param {Object} userId, optional user ID - * @returns {Promise} - * @async - */ - async getExpirationTime(capabilities, keyId, userId) { - const primaryUser = await this.getPrimaryUser(null, userId); - const selfCert = primaryUser.selfCertification; - const keyExpiry = helper.getExpirationTime(this.keyPacket, selfCert); - const sigExpiry = selfCert.getExpirationTime(); - let expiry = keyExpiry < sigExpiry ? keyExpiry : sigExpiry; - if (capabilities === 'encrypt' || capabilities === 'encrypt_sign') { - const encryptKey = - await this.getEncryptionKey(keyId, expiry, userId).catch(() => {}) || - await this.getEncryptionKey(keyId, null, userId).catch(() => {}); - if (!encryptKey) return null; - const encryptExpiry = await encryptKey.getExpirationTime(this.keyPacket); - if (encryptExpiry < expiry) expiry = encryptExpiry; - } - if (capabilities === 'sign' || capabilities === 'encrypt_sign') { - const signKey = - await this.getSigningKey(keyId, expiry, userId).catch(() => {}) || - await this.getSigningKey(keyId, null, userId).catch(() => {}); - if (!signKey) return null; - const signExpiry = await signKey.getExpirationTime(this.keyPacket); - if (signExpiry < expiry) expiry = signExpiry; +/** + * Clear private key parameters + */ +Key.prototype.clearPrivateParams = function () { + if (!this.isPrivate()) { + throw new Error("Can't clear private parameters of a public key"); + } + this.getKeys().forEach(({ keyPacket }) => { + if (keyPacket.isDecrypted()) { + keyPacket.clearPrivateParams(); } - return expiry; - } - - /** - * Returns primary user and most significant (latest valid) self signature - * - if multiple primary users exist, returns the one with the latest self signature - * - otherwise, returns the user with the latest self signature - * @param {Date} date (optional) use the given date for verification instead of the current time - * @param {Object} userId (optional) user ID to get instead of the primary user, if it exists - * @returns {Promise<{user: module:key.User, - * selfCertification: SignaturePacket}>} The primary user and the self signature - * @async - */ - async getPrimaryUser(date = new Date(), userId = {}) { - const primaryKey = this.keyPacket; - const users = []; - let exception; - for (let i = 0; i < this.users.length; i++) { - try { - const user = this.users[i]; - if (!user.userId) { - continue; - } - if ( - (userId.name !== undefined && user.userId.name !== userId.name) || - (userId.email !== undefined && user.userId.email !== userId.email) || - (userId.comment !== undefined && user.userId.comment !== userId.comment) - ) { - throw new Error('Could not find user that matches that user ID'); - } - const dataToVerify = { userId: user.userId, key: primaryKey }; - const selfCertification = await helper.getLatestValidSignature(user.selfCertifications, primaryKey, enums.signature.certGeneric, dataToVerify, date); - users.push({ index: i, user, selfCertification }); - } catch (e) { - exception = e; + }); +}; + +/** + * Checks if a signature on a key is revoked + * @param {module:packet.SecretKey| + * @param {module:packet.Signature} signature The signature to verify + * @param {module:packet.PublicSubkey| + * module:packet.SecretSubkey| + * module:packet.PublicKey| + * module:packet.SecretKey} key, optional The key to verify the signature + * @param {Date} date Use the given date instead of the current time + * @returns {Promise} True if the certificate is revoked + * @async + */ +Key.prototype.isRevoked = async function(signature, key, date = new Date()) { + return helper.isDataRevoked( + this.keyPacket, enums.signature.key_revocation, { key: this.keyPacket }, this.revocationSignatures, signature, key, date + ); +}; + +/** + * Verify primary key. Checks for revocation signatures, expiration time + * and valid self signature. Throws if the primary key is invalid. + * @param {Date} date (optional) use the given date for verification instead of the current time + * @param {Object} userId (optional) user ID + * @returns {Promise} The status of the primary key + * @async + */ +Key.prototype.verifyPrimaryKey = async function(date = new Date(), userId = {}) { + const primaryKey = this.keyPacket; + // check for key revocation signatures + if (await this.isRevoked(null, null, date)) { + throw new Error('Primary key is revoked'); + } + // check for at least one self signature. Self signature of user ID not mandatory + // See {@link https://tools.ietf.org/html/rfc4880#section-11.1} + if (!this.users.some(user => user.userId && user.selfCertifications.length)) { + throw new Error('No self-certifications'); + } + // check for valid, unrevoked, unexpired self signature + const { selfCertification } = await this.getPrimaryUser(date, userId); + // check for expiration time + if (helper.isDataExpired(primaryKey, selfCertification, date)) { + throw new Error('Primary key is expired'); + } +}; + +/** + * Returns the latest date when the key can be used for encrypting, signing, or both, depending on the `capabilities` paramater. + * When `capabilities` is null, defaults to returning the expiry date of the primary key. + * Returns null if `capabilities` is passed and the key does not have the specified capabilities or is revoked or invalid. + * Returns Infinity if the key doesn't expire. + * @param {encrypt|sign|encrypt_sign} capabilities, optional + * @param {module:type/keyid} keyId, optional + * @param {Object} userId, optional user ID + * @returns {Promise} + * @async + */ +Key.prototype.getExpirationTime = async function(capabilities, keyId, userId) { + const primaryUser = await this.getPrimaryUser(null, userId); + const selfCert = primaryUser.selfCertification; + const keyExpiry = helper.getExpirationTime(this.keyPacket, selfCert); + const sigExpiry = selfCert.getExpirationTime(); + let expiry = keyExpiry < sigExpiry ? keyExpiry : sigExpiry; + if (capabilities === 'encrypt' || capabilities === 'encrypt_sign') { + const encryptKey = + await this.getEncryptionKey(keyId, expiry, userId).catch(() => {}) || + await this.getEncryptionKey(keyId, null, userId).catch(() => {}); + if (!encryptKey) return null; + const encryptExpiry = await encryptKey.getExpirationTime(this.keyPacket); + if (encryptExpiry < expiry) expiry = encryptExpiry; + } + if (capabilities === 'sign' || capabilities === 'encrypt_sign') { + const signKey = + await this.getSigningKey(keyId, expiry, userId).catch(() => {}) || + await this.getSigningKey(keyId, null, userId).catch(() => {}); + if (!signKey) return null; + const signExpiry = await signKey.getExpirationTime(this.keyPacket); + if (signExpiry < expiry) expiry = signExpiry; + } + return expiry; +}; + +/** + * Returns primary user and most significant (latest valid) self signature + * - if multiple primary users exist, returns the one with the latest self signature + * - otherwise, returns the user with the latest self signature + * @param {Date} date (optional) use the given date for verification instead of the current time + * @param {Object} userId (optional) user ID to get instead of the primary user, if it exists + * @returns {Promise<{user: module:key.User, + * selfCertification: module:packet.Signature}>} The primary user and the self signature + * @async + */ +Key.prototype.getPrimaryUser = async function(date = new Date(), userId = {}) { + const primaryKey = this.keyPacket; + const users = []; + let exception; + for (let i = 0; i < this.users.length; i++) { + try { + const user = this.users[i]; + if (!user.userId) { + continue; } - } - if (!users.length) { - throw exception || new Error('Could not find primary user'); - } - await Promise.all(users.map(async function (a) { - return a.user.revoked || a.user.isRevoked(primaryKey, a.selfCertification, null, date); - })); - // sort by primary user flag and signature creation time - const primaryUser = users.sort(function(a, b) { - const A = a.selfCertification; - const B = b.selfCertification; - return B.revoked - A.revoked || A.isPrimaryUserID - B.isPrimaryUserID || A.created - B.created; - }).pop(); - const { user, selfCertification: cert } = primaryUser; - if (cert.revoked || await user.isRevoked(primaryKey, cert, null, date)) { - throw new Error('Primary user is revoked'); - } - return primaryUser; - } - - /** - * Update key with new components from specified key with same key ID: - * users, subkeys, certificates are merged into the destination key, - * duplicates and expired signatures are ignored. - * - * If the specified key is a private key and the destination key is public, - * the destination key is transformed to a private key. - * @param {module:key.Key} key Source key to merge - * @returns {Promise} - * @async - */ - async update(key) { - if (!this.hasSameFingerprintAs(key)) { - throw new Error('Key update method: fingerprints of keys not equal'); - } - if (this.isPublic() && key.isPrivate()) { - // check for equal subkey packets - const equal = (this.subKeys.length === key.subKeys.length) && - (this.subKeys.every(destSubKey => { - return key.subKeys.some(srcSubKey => { - return destSubKey.hasSameFingerprintAs(srcSubKey); - }); - })); - if (!equal) { - throw new Error('Cannot update public key with private key if subkey mismatch'); + if ( + (userId.name !== undefined && user.userId.name !== userId.name) || + (userId.email !== undefined && user.userId.email !== userId.email) || + (userId.comment !== undefined && user.userId.comment !== userId.comment) + ) { + throw new Error('Could not find user that matches that user ID'); } - this.keyPacket = key.keyPacket; + const dataToVerify = { userId: user.userId, key: primaryKey }; + const selfCertification = await helper.getLatestValidSignature(user.selfCertifications, primaryKey, enums.signature.cert_generic, dataToVerify, date); + users.push({ index: i, user, selfCertification }); + } catch (e) { + exception = e; } - // revocation signatures - await helper.mergeSignatures(key, this, 'revocationSignatures', srcRevSig => { - return helper.isDataRevoked(this.keyPacket, enums.signature.keyRevocation, this, [srcRevSig], null, key.keyPacket); - }); - // direct signatures - await helper.mergeSignatures(key, this, 'directSignatures'); - // TODO replace when Promise.some or Promise.any are implemented - // users - await Promise.all(key.users.map(async srcUser => { - let found = false; - await Promise.all(this.users.map(async dstUser => { - if ((srcUser.userId && dstUser.userId && - (srcUser.userId.userid === dstUser.userId.userid)) || - (srcUser.userAttribute && (srcUser.userAttribute.equals(dstUser.userAttribute)))) { - await dstUser.update(srcUser, this.keyPacket); - found = true; - } - })); - if (!found) { - this.users.push(srcUser); + } + if (!users.length) { + throw exception || new Error('Could not find primary user'); + } + await Promise.all(users.map(async function (a) { + return a.user.revoked || a.user.isRevoked(primaryKey, a.selfCertification, null, date); + })); + // sort by primary user flag and signature creation time + const primaryUser = users.sort(function(a, b) { + const A = a.selfCertification; + const B = b.selfCertification; + return B.revoked - A.revoked || A.isPrimaryUserID - B.isPrimaryUserID || A.created - B.created; + }).pop(); + const { user, selfCertification: cert } = primaryUser; + if (cert.revoked || await user.isRevoked(primaryKey, cert, null, date)) { + throw new Error('Primary user is revoked'); + } + return primaryUser; +}; + +/** + * Update key with new components from specified key with same key ID: + * users, subkeys, certificates are merged into the destination key, + * duplicates and expired signatures are ignored. + * + * If the specified key is a private key and the destination key is public, + * the destination key is transformed to a private key. + * @param {module:key.Key} key Source key to merge + * @returns {Promise} + * @async + */ +Key.prototype.update = async function(key) { + if (!this.hasSameFingerprintAs(key)) { + throw new Error('Key update method: fingerprints of keys not equal'); + } + if (this.isPublic() && key.isPrivate()) { + // check for equal subkey packets + const equal = (this.subKeys.length === key.subKeys.length) && + (this.subKeys.every(destSubKey => { + return key.subKeys.some(srcSubKey => { + return destSubKey.hasSameFingerprintAs(srcSubKey); + }); + })); + if (!equal) { + throw new Error('Cannot update public key with private key if subkey mismatch'); + } + this.keyPacket = key.keyPacket; + } + // revocation signatures + await helper.mergeSignatures(key, this, 'revocationSignatures', srcRevSig => { + return helper.isDataRevoked(this.keyPacket, enums.signature.key_revocation, this, [srcRevSig], null, key.keyPacket); + }); + // direct signatures + await helper.mergeSignatures(key, this, 'directSignatures'); + // TODO replace when Promise.some or Promise.any are implemented + // users + await Promise.all(key.users.map(async srcUser => { + let found = false; + await Promise.all(this.users.map(async dstUser => { + if ((srcUser.userId && dstUser.userId && + (srcUser.userId.userid === dstUser.userId.userid)) || + (srcUser.userAttribute && (srcUser.userAttribute.equals(dstUser.userAttribute)))) { + await dstUser.update(srcUser, this.keyPacket); + found = true; } })); - // TODO replace when Promise.some or Promise.any are implemented - // subkeys - await Promise.all(key.subKeys.map(async srcSubKey => { - let found = false; - await Promise.all(this.subKeys.map(async dstSubKey => { - if (dstSubKey.hasSameFingerprintAs(srcSubKey)) { - await dstSubKey.update(srcSubKey, this.keyPacket); - found = true; - } - })); - if (!found) { - this.subKeys.push(srcSubKey); + if (!found) { + this.users.push(srcUser); + } + })); + // TODO replace when Promise.some or Promise.any are implemented + // subkeys + await Promise.all(key.subKeys.map(async srcSubKey => { + let found = false; + await Promise.all(this.subKeys.map(async dstSubKey => { + if (dstSubKey.hasSameFingerprintAs(srcSubKey)) { + await dstSubKey.update(srcSubKey, this.keyPacket); + found = true; } })); - } - - /** - * Revokes the key - * @param {Object} reasonForRevocation optional, object indicating the reason for revocation - * @param {module:enums.reasonForRevocation} reasonForRevocation.flag optional, flag indicating the reason for revocation - * @param {String} reasonForRevocation.string optional, string explaining the reason for revocation - * @param {Date} date optional, override the creationtime of the revocation signature - * @returns {Promise} new key with revocation signature - * @async - */ - async revoke( - { - flag: reasonForRevocationFlag = enums.reasonForRevocation.noReason, - string: reasonForRevocationString = '' - } = {}, - date = new Date() - ) { - if (this.isPublic()) { - throw new Error('Need private key for revoking'); - } - const dataToSign = { key: this.keyPacket }; - const key = await this.clone(); - key.revocationSignatures.push(await helper.createSignaturePacket(dataToSign, null, this.keyPacket, { - signatureType: enums.signature.keyRevocation, - reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag), - reasonForRevocationString - }, date)); - return key; - } - - /** - * Get revocation certificate from a revoked key. - * (To get a revocation certificate for an unrevoked key, call revoke() first.) - * @param {Date} date Use the given date instead of the current time - * @returns {Promise} armored revocation certificate - * @async - */ - async getRevocationCertificate(date = new Date()) { - const dataToVerify = { key: this.keyPacket }; - const revocationSignature = await helper.getLatestValidSignature(this.revocationSignatures, this.keyPacket, enums.signature.keyRevocation, dataToVerify, date); - const packetlist = new PacketList(); - packetlist.push(revocationSignature); - return armor(enums.armor.publicKey, packetlist.write(), null, null, 'This is a revocation certificate'); - } - - /** - * Applies a revocation certificate to a key - * This adds the first signature packet in the armored text to the key, - * if it is a valid revocation signature. - * @param {String} revocationCertificate armored revocation certificate - * @returns {Promise} new revoked key - * @async - */ - async applyRevocationCertificate(revocationCertificate) { - const input = await unarmor(revocationCertificate); - const packetlist = new PacketList(); - await packetlist.read(input.data, { SignaturePacket }); - const revocationSignature = packetlist.findPacket(enums.packet.signature); - if (!revocationSignature || revocationSignature.signatureType !== enums.signature.keyRevocation) { - throw new Error('Could not find revocation signature packet'); + if (!found) { + this.subKeys.push(srcSubKey); } - if (!revocationSignature.issuerKeyId.equals(this.getKeyId())) { - throw new Error('Revocation signature does not match key'); - } - if (revocationSignature.isExpired()) { - throw new Error('Revocation signature is expired'); - } - try { - await revocationSignature.verify(this.keyPacket, enums.signature.keyRevocation, { key: this.keyPacket }); - } catch (e) { - throw util.wrapError('Could not verify revocation signature', e); - } - const key = await this.clone(); - key.revocationSignatures.push(revocationSignature); - return key; - } - - /** - * Signs primary user of key - * @param {Array} privateKeys decrypted private keys for signing - * @param {Date} date (optional) use the given date for verification instead of the current time - * @param {Object} userId (optional) user ID to get instead of the primary user, if it exists - * @returns {Promise} new public key with new certificate signature - * @async - */ - async signPrimaryUser(privateKeys, date, userId) { - const { index, user } = await this.getPrimaryUser(date, userId); - const userSign = await user.sign(this.keyPacket, privateKeys); - const key = await this.clone(); - key.users[index] = userSign; - return key; - } - - /** - * Signs all users of key - * @param {Array} privateKeys decrypted private keys for signing - * @returns {Promise} new public key with new certificate signature - * @async - */ - async signAllUsers(privateKeys) { - const that = this; - const key = await this.clone(); - key.users = await Promise.all(this.users.map(function(user) { - return user.sign(that.keyPacket, privateKeys); - })); - return key; - } - - /** - * Verifies primary user of key - * - if no arguments are given, verifies the self certificates; - * - otherwise, verifies all certificates signed with given keys. - * @param {Array} keys array of keys to verify certificate signatures - * @param {Date} date (optional) use the given date for verification instead of the current time - * @param {Object} userId (optional) user ID to get instead of the primary user, if it exists - * @returns {Promise>} List of signer's keyid and validity of signature - * @async - */ - async verifyPrimaryUser(keys, date, userId) { - const primaryKey = this.keyPacket; - const { user } = await this.getPrimaryUser(date, userId); - const results = keys ? await user.verifyAllCertifications(primaryKey, keys) : + })); +}; + +/** + * Revokes the key + * @param {Object} reasonForRevocation optional, object indicating the reason for revocation + * @param {module:enums.reasonForRevocation} reasonForRevocation.flag optional, flag indicating the reason for revocation + * @param {String} reasonForRevocation.string optional, string explaining the reason for revocation + * @param {Date} date optional, override the creationtime of the revocation signature + * @returns {Promise} new key with revocation signature + * @async + */ +Key.prototype.revoke = async function({ + flag: reasonForRevocationFlag = enums.reasonForRevocation.no_reason, + string: reasonForRevocationString = '' +} = {}, date = new Date()) { + if (this.isPublic()) { + throw new Error('Need private key for revoking'); + } + const dataToSign = { key: this.keyPacket }; + const key = new Key(this.toPacketlist()); + key.revocationSignatures.push(await helper.createSignaturePacket(dataToSign, null, this.keyPacket, { + signatureType: enums.signature.key_revocation, + reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag), + reasonForRevocationString + }, date)); + return key; +}; + +/** + * Get revocation certificate from a revoked key. + * (To get a revocation certificate for an unrevoked key, call revoke() first.) + * @param {Date} date Use the given date instead of the current time + * @returns {Promise} armored revocation certificate + * @async + */ +Key.prototype.getRevocationCertificate = async function(date = new Date()) { + const dataToVerify = { key: this.keyPacket }; + const revocationSignature = await helper.getLatestValidSignature(this.revocationSignatures, this.keyPacket, enums.signature.key_revocation, dataToVerify, date); + const packetlist = new packet.List(); + packetlist.push(revocationSignature); + return armor.encode(enums.armor.public_key, packetlist.write(), null, null, 'This is a revocation certificate'); +}; + +/** + * Applies a revocation certificate to a key + * This adds the first signature packet in the armored text to the key, + * if it is a valid revocation signature. + * @param {String} revocationCertificate armored revocation certificate + * @returns {Promise} new revoked key + * @async + */ +Key.prototype.applyRevocationCertificate = async function(revocationCertificate) { + const input = await armor.decode(revocationCertificate); + const packetlist = new packet.List(); + await packetlist.read(input.data); + const revocationSignature = packetlist.findPacket(enums.packet.signature); + if (!revocationSignature || revocationSignature.signatureType !== enums.signature.key_revocation) { + throw new Error('Could not find revocation signature packet'); + } + if (!revocationSignature.issuerKeyId.equals(this.getKeyId())) { + throw new Error('Revocation signature does not match key'); + } + if (revocationSignature.isExpired()) { + throw new Error('Revocation signature is expired'); + } + try { + await revocationSignature.verify(this.keyPacket, enums.signature.key_revocation, { key: this.keyPacket }); + } catch (e) { + throw util.wrapError('Could not verify revocation signature', e); + } + const key = new Key(this.toPacketlist()); + key.revocationSignatures.push(revocationSignature); + return key; +}; + +/** + * Signs primary user of key + * @param {Array} privateKey decrypted private keys for signing + * @param {Date} date (optional) use the given date for verification instead of the current time + * @param {Object} userId (optional) user ID to get instead of the primary user, if it exists + * @returns {Promise} new public key with new certificate signature + * @async + */ +Key.prototype.signPrimaryUser = async function(privateKeys, date, userId) { + const { index, user } = await this.getPrimaryUser(date, userId); + const userSign = await user.sign(this.keyPacket, privateKeys); + const key = new Key(this.toPacketlist()); + key.users[index] = userSign; + return key; +}; + +/** + * Signs all users of key + * @param {Array} privateKeys decrypted private keys for signing + * @returns {Promise} new public key with new certificate signature + * @async + */ +Key.prototype.signAllUsers = async function(privateKeys) { + const that = this; + const key = new Key(this.toPacketlist()); + key.users = await Promise.all(this.users.map(function(user) { + return user.sign(that.keyPacket, privateKeys); + })); + return key; +}; + +/** + * Verifies primary user of key + * - if no arguments are given, verifies the self certificates; + * - otherwise, verifies all certificates signed with given keys. + * @param {Array} keys array of keys to verify certificate signatures + * @param {Date} date (optional) use the given date for verification instead of the current time + * @param {Object} userId (optional) user ID to get instead of the primary user, if it exists + * @returns {Promise>} List of signer's keyid and validity of signature + * @async + */ +Key.prototype.verifyPrimaryUser = async function(keys, date, userId) { + const primaryKey = this.keyPacket; + const { user } = await this.getPrimaryUser(date, userId); + const results = keys ? await user.verifyAllCertifications(primaryKey, keys) : + [{ keyid: primaryKey.keyid, valid: await user.verify(primaryKey).catch(() => false) }]; + return results; +}; + +/** + * Verifies all users of key + * - if no arguments are given, verifies the self certificates; + * - otherwise, verifies all certificates signed with given keys. + * @param {Array} keys array of keys to verify certificate signatures + * @returns {Promise>} list of userid, signer's keyid and validity of signature + * @async + */ +Key.prototype.verifyAllUsers = async function(keys) { + const results = []; + const primaryKey = this.keyPacket; + await Promise.all(this.users.map(async function(user) { + const signatures = keys ? await user.verifyAllCertifications(primaryKey, keys) : [{ keyid: primaryKey.keyid, valid: await user.verify(primaryKey).catch(() => false) }]; - return results; - } - - /** - * Verifies all users of key - * - if no arguments are given, verifies the self certificates; - * - otherwise, verifies all certificates signed with given keys. - * @param {Array} keys array of keys to verify certificate signatures - * @returns {Promise>} list of userid, signer's keyid and validity of signature - * @async - */ - async verifyAllUsers(keys) { - const results = []; - const primaryKey = this.keyPacket; - await Promise.all(this.users.map(async function(user) { - const signatures = keys ? await user.verifyAllCertifications(primaryKey, keys) : - [{ keyid: primaryKey.keyid, valid: await user.verify(primaryKey).catch(() => false) }]; - signatures.forEach(signature => { - results.push({ - userid: user.userId.userid, - keyid: signature.keyid, - valid: signature.valid - }); + signatures.forEach(signature => { + results.push({ + userid: user.userId.userid, + keyid: signature.keyid, + valid: signature.valid }); - })); - return results; - } - - /** - * Generates a new OpenPGP subkey, and returns a clone of the Key object with the new subkey added. - * Supports RSA and ECC keys. Defaults to the algorithm and bit size/curve of the primary key. DSA primary keys default to RSA subkeys. - * @param {ecc|rsa} options.type The subkey algorithm: ECC or RSA - * @param {String} options.curve (optional) Elliptic curve for ECC keys - * @param {Integer} options.rsaBits (optional) Number of bits for RSA subkeys - * @param {Number} options.keyExpirationTime (optional) Number of seconds from the key creation time after which the key expires - * @param {Date} options.date (optional) Override the creation date of the key and the key signatures - * @param {Boolean} options.sign (optional) Indicates whether the subkey should sign rather than encrypt. Defaults to false - * @returns {Promise} - * @async - */ - async addSubkey(options = {}) { - if (!this.isPrivate()) { - throw new Error("Cannot add a subkey to a public key"); - } - if (options.passphrase) { - throw new Error("Subkey could not be encrypted here, please encrypt whole key"); - } - if (options.rsaBits < config.minRsaBits) { - throw new Error(`rsaBits should be at least ${config.minRsaBits}, got: ${options.rsaBits}`); - } - const secretKeyPacket = this.primaryKey; - if (secretKeyPacket.isDummy()) { - throw new Error("Cannot add subkey to gnu-dummy primary key"); - } - if (!secretKeyPacket.isDecrypted()) { - throw new Error("Key is not decrypted"); - } - const defaultOptions = secretKeyPacket.getAlgorithmInfo(); - defaultOptions.type = defaultOptions.curve ? 'ecc' : 'rsa'; // DSA keys default to RSA - defaultOptions.rsaBits = defaultOptions.bits || 4096; - defaultOptions.curve = defaultOptions.curve || 'curve25519'; - options = helper.sanitizeKeyOptions(options, defaultOptions); - const keyPacket = await helper.generateSecretSubkey(options); - const bindingSignature = await helper.createBindingSignature(keyPacket, secretKeyPacket, options); - const packetList = this.toPacketlist(); - packetList.push(keyPacket); - packetList.push(bindingSignature); - return new Key(packetList); - } -} + }); + })); + return results; +}; -['getKeyId', 'getFingerprint', 'getAlgorithmInfo', 'getCreationTime', 'hasSameFingerprintAs'].forEach(name => { +/** + * Generates a new OpenPGP subkey, and returns a clone of the Key object with the new subkey added. + * Supports RSA and ECC keys. Defaults to the algorithm and bit size/curve of the primary key. + * @param {Integer} options.rsaBits number of bits for the key creation. + * @param {Number} [options.keyExpirationTime=0] + * The number of seconds after the key creation time that the key expires + * @param {String} curve (optional) Elliptic curve for ECC keys + * @param {Date} date (optional) Override the creation date of the key and the key signatures + * @param {Boolean} sign (optional) Indicates whether the subkey should sign rather than encrypt. Defaults to false + * @returns {Promise} + * @async + */ +Key.prototype.addSubkey = async function(options = {}) { + if (!this.isPrivate()) { + throw new Error("Cannot add a subkey to a public key"); + } + if (options.passphrase) { + throw new Error("Subkey could not be encrypted here, please encrypt whole key"); + } + if (util.getWebCryptoAll() && options.rsaBits < 2048) { + throw new Error('When using webCrypto rsaBits should be 2048 or 4096, found: ' + options.rsaBits); + } + const secretKeyPacket = this.primaryKey; + if (!secretKeyPacket.isDecrypted()) { + throw new Error("Key is not decrypted"); + } + const defaultOptions = secretKeyPacket.getAlgorithmInfo(); + options = helper.sanitizeKeyOptions(options, defaultOptions); + const keyPacket = await helper.generateSecretSubkey(options); + const bindingSignature = await helper.createBindingSignature(keyPacket, secretKeyPacket, options); + const packetList = this.toPacketlist(); + packetList.push(keyPacket); + packetList.push(bindingSignature); + return new Key(packetList); +}; + +['getKeyId', 'getFingerprint', 'getAlgorithmInfo', 'getCreationTime', 'isDecrypted', 'hasSameFingerprintAs'].forEach(name => { Key.prototype[name] = SubKey.prototype[name]; }); - -export default Key; diff --git a/src/key/subkey.js b/src/key/subkey.js index 3ef62fa2..5defb3fd 100644 --- a/src/key/subkey.js +++ b/src/key/subkey.js @@ -7,181 +7,172 @@ import enums from '../enums'; import * as helper from './helper'; -import { PacketList } from '../packet'; +import packet from '../packet'; /** - * Class that represents a subkey packet and the relevant signatures. - * @borrows PublicSubkeyPacket#getKeyId as SubKey#getKeyId - * @borrows PublicSubkeyPacket#getFingerprint as SubKey#getFingerprint - * @borrows PublicSubkeyPacket#hasSameFingerprintAs as SubKey#hasSameFingerprintAs - * @borrows PublicSubkeyPacket#getAlgorithmInfo as SubKey#getAlgorithmInfo - * @borrows PublicSubkeyPacket#getCreationTime as SubKey#getCreationTime - * @borrows PublicSubkeyPacket#isDecrypted as SubKey#isDecrypted + * @class + * @classdesc Class that represents a subkey packet and the relevant signatures. + * @borrows module:packet.PublicSubkey#getKeyId as SubKey#getKeyId + * @borrows module:packet.PublicSubkey#getFingerprint as SubKey#getFingerprint + * @borrows module:packet.PublicSubkey#hasSameFingerprintAs as SubKey#hasSameFingerprintAs + * @borrows module:packet.PublicSubkey#getAlgorithmInfo as SubKey#getAlgorithmInfo + * @borrows module:packet.PublicSubkey#getCreationTime as SubKey#getCreationTime + * @borrows module:packet.PublicSubkey#isDecrypted as SubKey#isDecrypted */ -class SubKey { - constructor(subKeyPacket) { - if (!(this instanceof SubKey)) { - return new SubKey(subKeyPacket); - } - this.keyPacket = subKeyPacket; - this.bindingSignatures = []; - this.revocationSignatures = []; +export default function SubKey(subKeyPacket) { + if (!(this instanceof SubKey)) { + return new SubKey(subKeyPacket); } + this.keyPacket = subKeyPacket; + this.bindingSignatures = []; + this.revocationSignatures = []; +} - /** - * Transforms structured subkey data to packetlist - * @returns {PacketList} - */ - toPacketlist() { - const packetlist = new PacketList(); - packetlist.push(this.keyPacket); - packetlist.concat(this.revocationSignatures); - packetlist.concat(this.bindingSignatures); - return packetlist; - } +/** + * Transforms structured subkey data to packetlist + * @returns {module:packet.List} + */ +SubKey.prototype.toPacketlist = function() { + const packetlist = new packet.List(); + packetlist.push(this.keyPacket); + packetlist.concat(this.revocationSignatures); + packetlist.concat(this.bindingSignatures); + return packetlist; +}; - /** - * Checks if a binding signature of a subkey is revoked - * @param {SecretKeyPacket| - * PublicKeyPacket} primaryKey The primary key packet - * @param {SignaturePacket} signature The binding signature to verify - * @param {PublicSubkeyPacket| - * SecretSubkeyPacket| - * PublicKeyPacket| - * SecretKeyPacket} key, optional The key to verify the signature - * @param {Date} date Use the given date instead of the current time - * @returns {Promise} True if the binding signature is revoked - * @async - */ - async isRevoked(primaryKey, signature, key, date = new Date()) { - return helper.isDataRevoked( - primaryKey, enums.signature.subkeyRevocation, { - key: primaryKey, - bind: this.keyPacket - }, this.revocationSignatures, signature, key, date - ); - } +/** + * Checks if a binding signature of a subkey is revoked + * @param {module:packet.SecretKey| + * module:packet.PublicKey} primaryKey The primary key packet + * @param {module:packet.Signature} signature The binding signature to verify + * @param {module:packet.PublicSubkey| + * module:packet.SecretSubkey| + * module:packet.PublicKey| + * module:packet.SecretKey} key, optional The key to verify the signature + * @param {Date} date Use the given date instead of the current time + * @returns {Promise} True if the binding signature is revoked + * @async + */ +SubKey.prototype.isRevoked = async function(primaryKey, signature, key, date = new Date()) { + return helper.isDataRevoked( + primaryKey, enums.signature.subkey_revocation, { + key: primaryKey, + bind: this.keyPacket + }, this.revocationSignatures, signature, key, date + ); +}; - /** - * Verify subkey. Checks for revocation signatures, expiration time - * and valid binding signature. - * @param {SecretKeyPacket| - * PublicKeyPacket} primaryKey The primary key packet - * @param {Date} date Use the given date instead of the current time - * @throws {Error} if the subkey is invalid. - * @async - */ - async verify(primaryKey, date = new Date()) { - const dataToVerify = { key: primaryKey, bind: this.keyPacket }; - // check subkey binding signatures - const bindingSignature = await helper.getLatestValidSignature(this.bindingSignatures, primaryKey, enums.signature.subkeyBinding, dataToVerify, date); - // check binding signature is not revoked - if (bindingSignature.revoked || await this.isRevoked(primaryKey, bindingSignature, null, date)) { - throw new Error('Subkey is revoked'); - } - // check for expiration time - if (helper.isDataExpired(this.keyPacket, bindingSignature, date)) { - throw new Error('Subkey is expired'); - } + +/** + * Verify subkey. Checks for revocation signatures, expiration time + * and valid binding signature. Throws if the subkey is invalid. + * @param {module:packet.SecretKey| + * module:packet.PublicKey} primaryKey The primary key packet + * @param {Date} date Use the given date instead of the current time + * @returns {Promise} + * @async + */ +SubKey.prototype.verify = async function(primaryKey, date = new Date()) { + const dataToVerify = { key: primaryKey, bind: this.keyPacket }; + // check subkey binding signatures + const bindingSignature = await helper.getLatestValidSignature(this.bindingSignatures, primaryKey, enums.signature.subkey_binding, dataToVerify, date); + // check binding signature is not revoked + if (bindingSignature.revoked || await this.isRevoked(primaryKey, bindingSignature, null, date)) { + throw new Error('Subkey is revoked'); } + // check for expiration time + if (helper.isDataExpired(this.keyPacket, bindingSignature, date)) { + throw new Error('Subkey is expired'); + } +}; - /** - * Returns the expiration time of the subkey or Infinity if key does not expire - * Returns null if the subkey is invalid. - * @param {SecretKeyPacket| - * PublicKeyPacket} primaryKey The primary key packet - * @param {Date} date Use the given date instead of the current time - * @returns {Promise} - * @async - */ - async getExpirationTime(primaryKey, date = new Date()) { - const dataToVerify = { key: primaryKey, bind: this.keyPacket }; - let bindingSignature; - try { - bindingSignature = await helper.getLatestValidSignature(this.bindingSignatures, primaryKey, enums.signature.subkeyBinding, dataToVerify, date); - } catch (e) { - return null; - } - const keyExpiry = helper.getExpirationTime(this.keyPacket, bindingSignature); - const sigExpiry = bindingSignature.getExpirationTime(); - return keyExpiry < sigExpiry ? keyExpiry : sigExpiry; +/** + * Returns the expiration time of the subkey or Infinity if key does not expire + * Returns null if the subkey is invalid. + * @param {module:packet.SecretKey| + * module:packet.PublicKey} primaryKey The primary key packet + * @param {Date} date Use the given date instead of the current time + * @returns {Promise} + * @async + */ +SubKey.prototype.getExpirationTime = async function(primaryKey, date = new Date()) { + const dataToVerify = { key: primaryKey, bind: this.keyPacket }; + let bindingSignature; + try { + bindingSignature = await helper.getLatestValidSignature(this.bindingSignatures, primaryKey, enums.signature.subkey_binding, dataToVerify, date); + } catch (e) { + return null; } + const keyExpiry = helper.getExpirationTime(this.keyPacket, bindingSignature); + const sigExpiry = bindingSignature.getExpirationTime(); + return keyExpiry < sigExpiry ? keyExpiry : sigExpiry; +}; - /** - * Update subkey with new components from specified subkey - * @param {module:key~SubKey} subKey Source subkey to merge - * @param {SecretKeyPacket| - SecretSubkeyPacket} primaryKey primary key used for validation - * @throws {Error} if update failed - * @async - */ - async update(subKey, primaryKey) { - if (!this.hasSameFingerprintAs(subKey)) { - throw new Error('SubKey update method: fingerprints of subkeys not equal'); - } - // key packet - if (this.keyPacket.tag === enums.packet.publicSubkey && - subKey.keyPacket.tag === enums.packet.secretSubkey) { - this.keyPacket = subKey.keyPacket; - } - // update missing binding signatures - const that = this; - const dataToVerify = { key: primaryKey, bind: that.keyPacket }; - await helper.mergeSignatures(subKey, this, 'bindingSignatures', async function(srcBindSig) { - for (let i = 0; i < that.bindingSignatures.length; i++) { - if (that.bindingSignatures[i].issuerKeyId.equals(srcBindSig.issuerKeyId)) { - if (srcBindSig.created > that.bindingSignatures[i].created) { - that.bindingSignatures[i] = srcBindSig; - } - return false; +/** + * Update subkey with new components from specified subkey + * @param {module:key~SubKey} subKey Source subkey to merge + * @param {module:packet.SecretKey| + module:packet.SecretSubkey} primaryKey primary key used for validation + * @returns {Promise} + * @async + */ +SubKey.prototype.update = async function(subKey, primaryKey) { + if (!this.hasSameFingerprintAs(subKey)) { + throw new Error('SubKey update method: fingerprints of subkeys not equal'); + } + // key packet + if (this.keyPacket.tag === enums.packet.publicSubkey && + subKey.keyPacket.tag === enums.packet.secretSubkey) { + this.keyPacket = subKey.keyPacket; + } + // update missing binding signatures + const that = this; + const dataToVerify = { key: primaryKey, bind: that.keyPacket }; + await helper.mergeSignatures(subKey, this, 'bindingSignatures', async function(srcBindSig) { + for (let i = 0; i < that.bindingSignatures.length; i++) { + if (that.bindingSignatures[i].issuerKeyId.equals(srcBindSig.issuerKeyId)) { + if (srcBindSig.created > that.bindingSignatures[i].created) { + that.bindingSignatures[i] = srcBindSig; } - } - try { - srcBindSig.verified || await srcBindSig.verify(primaryKey, enums.signature.subkeyBinding, dataToVerify); - return true; - } catch (e) { return false; } - }); - // revocation signatures - await helper.mergeSignatures(subKey, this, 'revocationSignatures', function(srcRevSig) { - return helper.isDataRevoked(primaryKey, enums.signature.subkeyRevocation, dataToVerify, [srcRevSig]); - }); - } - - /** - * Revokes the subkey - * @param {SecretKeyPacket} primaryKey decrypted private primary key for revocation - * @param {Object} reasonForRevocation optional, object indicating the reason for revocation - * @param {module:enums.reasonForRevocation} reasonForRevocation.flag optional, flag indicating the reason for revocation - * @param {String} reasonForRevocation.string optional, string explaining the reason for revocation - * @param {Date} date optional, override the creationtime of the revocation signature - * @returns {Promise} new subkey with revocation signature - * @async - */ - async revoke( - primaryKey, - { - flag: reasonForRevocationFlag = enums.reasonForRevocation.noReason, - string: reasonForRevocationString = '' - } = {}, - date = new Date() - ) { - const dataToSign = { key: primaryKey, bind: this.keyPacket }; - const subKey = new SubKey(this.keyPacket); - subKey.revocationSignatures.push(await helper.createSignaturePacket(dataToSign, null, primaryKey, { - signatureType: enums.signature.subkeyRevocation, - reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag), - reasonForRevocationString - }, date)); - await subKey.update(this, primaryKey); - return subKey; - } + } + try { + return srcBindSig.verified || await srcBindSig.verify(primaryKey, enums.signature.subkey_binding, dataToVerify); + } catch (e) { + return false; + } + }); + // revocation signatures + await helper.mergeSignatures(subKey, this, 'revocationSignatures', function(srcRevSig) { + return helper.isDataRevoked(primaryKey, enums.signature.subkey_revocation, dataToVerify, [srcRevSig]); + }); +}; - hasSameFingerprintAs(other) { - return this.keyPacket.hasSameFingerprintAs(other.keyPacket || other); - } -} +/** + * Revokes the subkey + * @param {module:packet.SecretKey} primaryKey decrypted private primary key for revocation + * @param {Object} reasonForRevocation optional, object indicating the reason for revocation + * @param {module:enums.reasonForRevocation} reasonForRevocation.flag optional, flag indicating the reason for revocation + * @param {String} reasonForRevocation.string optional, string explaining the reason for revocation + * @param {Date} date optional, override the creationtime of the revocation signature + * @returns {Promise} new subkey with revocation signature + * @async + */ +SubKey.prototype.revoke = async function(primaryKey, { + flag: reasonForRevocationFlag = enums.reasonForRevocation.no_reason, + string: reasonForRevocationString = '' +} = {}, date = new Date()) { + const dataToSign = { key: primaryKey, bind: this.keyPacket }; + const subKey = new SubKey(this.keyPacket); + subKey.revocationSignatures.push(await helper.createSignaturePacket(dataToSign, null, primaryKey, { + signatureType: enums.signature.subkey_revocation, + reasonForRevocationFlag: enums.write(enums.reasonForRevocation, reasonForRevocationFlag), + reasonForRevocationString + }, date)); + await subKey.update(this, primaryKey); + return subKey; +}; ['getKeyId', 'getFingerprint', 'getAlgorithmInfo', 'getCreationTime', 'isDecrypted'].forEach(name => { SubKey.prototype[name] = @@ -190,4 +181,7 @@ class SubKey { }; }); -export default SubKey; +SubKey.prototype.hasSameFingerprintAs = + function(other) { + return this.keyPacket.hasSameFingerprintAs(other.keyPacket || other); + }; diff --git a/src/key/user.js b/src/key/user.js index f85ebe43..84d5320a 100644 --- a/src/key/user.js +++ b/src/key/user.js @@ -8,227 +8,223 @@ import enums from '../enums'; import util from '../util'; -import { PacketList } from '../packet'; +import packet from '../packet'; import { mergeSignatures, isDataRevoked, createSignaturePacket } from './helper'; /** - * Class that represents an user ID or attribute packet and the relevant signatures. + * @class + * @classdesc Class that represents an user ID or attribute packet and the relevant signatures. */ -class User { - constructor(userPacket) { - if (!(this instanceof User)) { - return new User(userPacket); - } - this.userId = userPacket.tag === enums.packet.userID ? userPacket : null; - this.userAttribute = userPacket.tag === enums.packet.userAttribute ? userPacket : null; - this.selfCertifications = []; - this.otherCertifications = []; - this.revocationSignatures = []; - } - - /** - * Transforms structured user data to packetlist - * @returns {PacketList} - */ - toPacketlist() { - const packetlist = new PacketList(); - packetlist.push(this.userId || this.userAttribute); - packetlist.concat(this.revocationSignatures); - packetlist.concat(this.selfCertifications); - packetlist.concat(this.otherCertifications); - return packetlist; +export default function User(userPacket) { + if (!(this instanceof User)) { + return new User(userPacket); } + this.userId = userPacket.tag === enums.packet.userid ? userPacket : null; + this.userAttribute = userPacket.tag === enums.packet.userAttribute ? userPacket : null; + this.selfCertifications = []; + this.otherCertifications = []; + this.revocationSignatures = []; +} - /** - * Signs user - * @param {SecretKeyPacket| - * PublicKeyPacket} primaryKey The primary key packet - * @param {Array} privateKeys Decrypted private keys for signing - * @returns {Promise} New user with new certificate signatures - * @async - */ - async sign(primaryKey, privateKeys) { - const dataToSign = { - userId: this.userId, - userAttribute: this.userAttribute, - key: primaryKey - }; - const user = new User(dataToSign.userId || dataToSign.userAttribute); - user.otherCertifications = await Promise.all(privateKeys.map(async function(privateKey) { - if (privateKey.isPublic()) { - throw new Error('Need private key for signing'); - } - if (privateKey.hasSameFingerprintAs(primaryKey)) { - throw new Error('Not implemented for self signing'); - } - const signingKey = await privateKey.getSigningKey(); - return createSignaturePacket(dataToSign, privateKey, signingKey.keyPacket, { - // Most OpenPGP implementations use generic certification (0x10) - signatureType: enums.signature.certGeneric, - keyFlags: [enums.keyFlags.certifyKeys | enums.keyFlags.signData] - }); - })); - await user.update(this, primaryKey); - return user; - } +/** + * Transforms structured user data to packetlist + * @returns {module:packet.List} + */ +User.prototype.toPacketlist = function() { + const packetlist = new packet.List(); + packetlist.push(this.userId || this.userAttribute); + packetlist.concat(this.revocationSignatures); + packetlist.concat(this.selfCertifications); + packetlist.concat(this.otherCertifications); + return packetlist; +}; - /** - * Checks if a given certificate of the user is revoked - * @param {SecretKeyPacket| - * PublicKeyPacket} primaryKey The primary key packet - * @param {SignaturePacket} certificate The certificate to verify - * @param {PublicSubkeyPacket| - * SecretSubkeyPacket| - * PublicKeyPacket| - * SecretKeyPacket} key, optional The key to verify the signature - * @param {Date} date Use the given date instead of the current time - * @returns {Promise} True if the certificate is revoked - * @async - */ - async isRevoked(primaryKey, certificate, key, date = new Date()) { - return isDataRevoked( - primaryKey, enums.signature.certRevocation, { - key: primaryKey, - userId: this.userId, - userAttribute: this.userAttribute - }, this.revocationSignatures, certificate, key, date - ); - } +/** + * Signs user + * @param {module:packet.SecretKey| + * module:packet.PublicKey} primaryKey The primary key packet + * @param {Array} privateKeys Decrypted private keys for signing + * @returns {Promise} New user with new certificate signatures + * @async + */ +User.prototype.sign = async function(primaryKey, privateKeys) { + const dataToSign = { + userId: this.userId, + userAttribute: this.userAttribute, + key: primaryKey + }; + const user = new User(dataToSign.userId || dataToSign.userAttribute); + user.otherCertifications = await Promise.all(privateKeys.map(async function(privateKey) { + if (privateKey.isPublic()) { + throw new Error('Need private key for signing'); + } + if (privateKey.hasSameFingerprintAs(primaryKey)) { + throw new Error('Not implemented for self signing'); + } + const signingKey = await privateKey.getSigningKey(); + return createSignaturePacket(dataToSign, privateKey, signingKey.keyPacket, { + // Most OpenPGP implementations use generic certification (0x10) + signatureType: enums.signature.cert_generic, + keyFlags: [enums.keyFlags.certify_keys | enums.keyFlags.sign_data] + }); + })); + await user.update(this, primaryKey); + return user; +}; - /** - * Verifies the user certificate. Throws if the user certificate is invalid. - * @param {SecretKeyPacket| - * PublicKeyPacket} primaryKey The primary key packet - * @param {SignaturePacket} certificate A certificate of this user - * @param {Array} keys Array of keys to verify certificate signatures - * @param {Date} date Use the given date instead of the current time - * @returns {Promise} status of the certificate - * @async - */ - async verifyCertificate(primaryKey, certificate, keys, date = new Date()) { - const that = this; - const keyid = certificate.issuerKeyId; - const dataToVerify = { +/** + * Checks if a given certificate of the user is revoked + * @param {module:packet.SecretKey| + * module:packet.PublicKey} primaryKey The primary key packet + * @param {module:packet.Signature} certificate The certificate to verify + * @param {module:packet.PublicSubkey| + * module:packet.SecretSubkey| + * module:packet.PublicKey| + * module:packet.SecretKey} key, optional The key to verify the signature + * @param {Date} date Use the given date instead of the current time + * @returns {Promise} True if the certificate is revoked + * @async + */ +User.prototype.isRevoked = async function(primaryKey, certificate, key, date = new Date()) { + return isDataRevoked( + primaryKey, enums.signature.cert_revocation, { + key: primaryKey, userId: this.userId, - userAttribute: this.userAttribute, - key: primaryKey - }; - const results = await Promise.all(keys.map(async function(key) { - if (!key.getKeyIds().some(id => id.equals(keyid))) { - return null; - } - const signingKey = await key.getSigningKey(keyid, date); - if (certificate.revoked || await that.isRevoked(primaryKey, certificate, signingKey.keyPacket, date)) { - throw new Error('User certificate is revoked'); - } - try { - certificate.verified || await certificate.verify(signingKey.keyPacket, enums.signature.certGeneric, dataToVerify); - } catch (e) { - throw util.wrapError('User certificate is invalid', e); - } - if (certificate.isExpired(date)) { - throw new Error('User certificate is expired'); - } - return true; - })); - return results.find(result => result !== null) || null; - } + userAttribute: this.userAttribute + }, this.revocationSignatures, certificate, key, date + ); +}; - /** - * Verifies all user certificates - * @param {SecretKeyPacket| - * PublicKeyPacket} primaryKey The primary key packet - * @param {Array} keys Array of keys to verify certificate signatures - * @param {Date} date Use the given date instead of the current time - * @returns {Promise>} List of signer's keyid and validity of signature - * @async - */ - async verifyAllCertifications(primaryKey, keys, date = new Date()) { - const that = this; - const certifications = this.selfCertifications.concat(this.otherCertifications); - return Promise.all(certifications.map(async function(certification) { - return { - keyid: certification.issuerKeyId, - valid: await that.verifyCertificate(primaryKey, certification, keys, date).catch(() => false) - }; - })); - } - /** - * Verify User. Checks for existence of self signatures, revocation signatures - * and validity of self signature. - * @param {SecretKeyPacket| - * PublicKeyPacket} primaryKey The primary key packet - * @param {Date} date Use the given date instead of the current time - * @returns {Promise} Status of user - * @throws {Error} if there are no valid self signatures. - * @async - */ - async verify(primaryKey, date = new Date()) { - if (!this.selfCertifications.length) { - throw new Error('No self-certifications'); +/** + * Verifies the user certificate. Throws if the user certificate is invalid. + * @param {module:packet.SecretKey| + * module:packet.PublicKey} primaryKey The primary key packet + * @param {module:packet.Signature} certificate A certificate of this user + * @param {Array} keys Array of keys to verify certificate signatures + * @param {Date} date Use the given date instead of the current time + * @returns {Promise} status of the certificate + * @async + */ +User.prototype.verifyCertificate = async function(primaryKey, certificate, keys, date = new Date()) { + const that = this; + const keyid = certificate.issuerKeyId; + const dataToVerify = { + userId: this.userId, + userAttribute: this.userAttribute, + key: primaryKey + }; + const results = await Promise.all(keys.map(async function(key) { + if (!key.getKeyIds().some(id => id.equals(keyid))) { + return null; } - const that = this; - const dataToVerify = { - userId: this.userId, - userAttribute: this.userAttribute, - key: primaryKey - }; - // TODO replace when Promise.some or Promise.any are implemented - let exception; - for (let i = this.selfCertifications.length - 1; i >= 0; i--) { - try { - const selfCertification = this.selfCertifications[i]; - if (selfCertification.revoked || await that.isRevoked(primaryKey, selfCertification, undefined, date)) { - throw new Error('Self-certification is revoked'); - } - try { - selfCertification.verified || await selfCertification.verify(primaryKey, enums.signature.certGeneric, dataToVerify); - } catch (e) { - throw util.wrapError('Self-certification is invalid', e); - } - if (selfCertification.isExpired(date)) { - throw new Error('Self-certification is expired'); - } - return true; - } catch (e) { - exception = e; - } + const signingKey = await key.getSigningKey(keyid, date); + if (certificate.revoked || await that.isRevoked(primaryKey, certificate, signingKey.keyPacket, date)) { + throw new Error('User certificate is revoked'); } - throw exception; - } + try { + certificate.verified || await certificate.verify(signingKey.keyPacket, enums.signature.cert_generic, dataToVerify); + } catch (e) { + throw util.wrapError('User certificate is invalid', e); + } + if (certificate.isExpired(date)) { + throw new Error('User certificate is expired'); + } + return true; + })); + return results.find(result => result !== null) || null; +}; - /** - * Update user with new components from specified user - * @param {module:key.User} user Source user to merge - * @param {SecretKeyPacket| - * SecretSubkeyPacket} primaryKey primary key used for validation - * @returns {Promise} - * @async - */ - async update(user, primaryKey) { - const dataToVerify = { - userId: this.userId, - userAttribute: this.userAttribute, - key: primaryKey +/** + * Verifies all user certificates + * @param {module:packet.SecretKey| + * module:packet.PublicKey} primaryKey The primary key packet + * @param {Array} keys Array of keys to verify certificate signatures + * @param {Date} date Use the given date instead of the current time + * @returns {Promise>} List of signer's keyid and validity of signature + * @async + */ +User.prototype.verifyAllCertifications = async function(primaryKey, keys, date = new Date()) { + const that = this; + const certifications = this.selfCertifications.concat(this.otherCertifications); + return Promise.all(certifications.map(async function(certification) { + return { + keyid: certification.issuerKeyId, + valid: await that.verifyCertificate(primaryKey, certification, keys, date).catch(() => false) }; - // self signatures - await mergeSignatures(user, this, 'selfCertifications', async function(srcSelfSig) { + })); +}; + +/** + * Verify User. Checks for existence of self signatures, revocation signatures + * and validity of self signature. Throws when there are no valid self signatures. + * @param {module:packet.SecretKey| + * module:packet.PublicKey} primaryKey The primary key packet + * @param {Date} date Use the given date instead of the current time + * @returns {Promise} Status of user + * @async + */ +User.prototype.verify = async function(primaryKey, date = new Date()) { + if (!this.selfCertifications.length) { + throw new Error('No self-certifications'); + } + const that = this; + const dataToVerify = { + userId: this.userId, + userAttribute: this.userAttribute, + key: primaryKey + }; + // TODO replace when Promise.some or Promise.any are implemented + let exception; + for (let i = this.selfCertifications.length - 1; i >= 0; i--) { + try { + const selfCertification = this.selfCertifications[i]; + if (selfCertification.revoked || await that.isRevoked(primaryKey, selfCertification, undefined, date)) { + throw new Error('Self-certification is revoked'); + } try { - srcSelfSig.verified || await srcSelfSig.verify(primaryKey, enums.signature.certGeneric, dataToVerify); - return true; + selfCertification.verified || await selfCertification.verify(primaryKey, enums.signature.cert_generic, dataToVerify); } catch (e) { - return false; + throw util.wrapError('Self-certification is invalid', e); } - }); - // other signatures - await mergeSignatures(user, this, 'otherCertifications'); - // revocation signatures - await mergeSignatures(user, this, 'revocationSignatures', function(srcRevSig) { - return isDataRevoked(primaryKey, enums.signature.certRevocation, dataToVerify, [srcRevSig]); - }); + if (selfCertification.isExpired(date)) { + throw new Error('Self-certification is expired'); + } + return true; + } catch (e) { + exception = e; + } } -} + throw exception; +}; -export default User; +/** + * Update user with new components from specified user + * @param {module:key.User} user Source user to merge + * @param {module:packet.SecretKey| + * module:packet.SecretSubkey} primaryKey primary key used for validation + * @returns {Promise} + * @async + */ +User.prototype.update = async function(user, primaryKey) { + const dataToVerify = { + userId: this.userId, + userAttribute: this.userAttribute, + key: primaryKey + }; + // self signatures + await mergeSignatures(user, this, 'selfCertifications', async function(srcSelfSig) { + try { + return srcSelfSig.verified || srcSelfSig.verify(primaryKey, enums.signature.cert_generic, dataToVerify); + } catch (e) { + return false; + } + }); + // other signatures + await mergeSignatures(user, this, 'otherCertifications'); + // revocation signatures + await mergeSignatures(user, this, 'revocationSignatures', function(srcRevSig) { + return isDataRevoked(primaryKey, enums.signature.cert_revocation, dataToVerify, [srcRevSig]); + }); +}; diff --git a/src/keyring/keyring.js b/src/keyring/keyring.js index 448b1ef4..9a6dd09d 100644 --- a/src/keyring/keyring.js +++ b/src/keyring/keyring.js @@ -22,176 +22,104 @@ * @module keyring/keyring */ -import { readArmoredKeys } from '../key'; +import { readArmored } from '../key'; import LocalStore from './localstore'; /** - * Array of keys + * Initialization routine for the keyring. + * @constructor + * @param {keyring/localstore} [storeHandler] class implementing loadPublic(), loadPrivate(), storePublic(), and storePrivate() methods */ -class KeyArray { - /** - * @param {Array} keys The keys to store in this array - */ - constructor(keys) { - this.keys = keys; - } - - /** - * Searches all keys in the KeyArray matching the address or address part of the user ids - * @param {String} email email address to search for - * @returns {Array} The public keys associated with provided email address. - */ - getForAddress(email) { - const results = []; - for (let i = 0; i < this.keys.length; i++) { - if (emailCheck(email, this.keys[i])) { - results.push(this.keys[i]); - } - } - return results; - } - - /** - * Searches the KeyArray for a key having the specified key id - * @param {String} keyId provided as string of lowercase hex number - * withouth 0x prefix (can be 16-character key ID or fingerprint) - * @param {Boolean} deep if true search also in subkeys - * @returns {module:key.Key|null} key found or null - */ - getForId(keyId, deep) { - for (let i = 0; i < this.keys.length; i++) { - if (keyIdCheck(keyId, this.keys[i])) { - return this.keys[i]; - } - if (deep && this.keys[i].subKeys.length) { - for (let j = 0; j < this.keys[i].subKeys.length; j++) { - if (keyIdCheck(keyId, this.keys[i].subKeys[j])) { - return this.keys[i]; - } - } - } - } - return null; - } - - /** - * Imports a key from an ascii armored message - * @param {String} armored message to read the keys/key from - * @async - */ - async importKey(armored) { - const imported = await readArmoredKeys(armored); - for (let i = 0; i < imported.length; i++) { - const key = imported[i]; - // check if key already in key array - const keyidHex = key.getKeyId().toHex(); - const keyFound = this.getForId(keyidHex); - if (keyFound) { - await keyFound.update(key); - } else { - this.push(key); - } - } - } - - /** - * Add key to KeyArray - * @param {module:key.Key} key The key that will be added to the keyring - * @returns {Number} The new length of the KeyArray - */ - push(key) { - return this.keys.push(key); - } - - /** - * Removes a key with the specified keyid from the keyring - * @param {String} keyId provided as string of lowercase hex number - * withouth 0x prefix (can be 16-character key ID or fingerprint) - * @returns {module:key.Key|null} The key object which has been removed or null - */ - removeForId(keyId) { - for (let i = 0; i < this.keys.length; i++) { - if (keyIdCheck(keyId, this.keys[i])) { - return this.keys.splice(i, 1)[0]; - } - } - return null; - } +function Keyring(storeHandler) { + this.storeHandler = storeHandler || new LocalStore(); } -class Keyring { - /** - * Initialization routine for the keyring. - * @param {keyring/localstore} [storeHandler] class implementing loadPublic(), loadPrivate(), storePublic(), and storePrivate() methods - */ - constructor(storeHandler) { - this.storeHandler = storeHandler || new LocalStore(); - } +/** + * Calls the storeHandler to load the keys + * @async + */ +Keyring.prototype.load = async function () { + this.publicKeys = new KeyArray(await this.storeHandler.loadPublic()); + this.privateKeys = new KeyArray(await this.storeHandler.loadPrivate()); +}; - /** - * Calls the storeHandler to load the keys - * @async - */ - async load() { - this.publicKeys = new KeyArray(await this.storeHandler.loadPublic()); - this.privateKeys = new KeyArray(await this.storeHandler.loadPrivate()); - } +/** + * Calls the storeHandler to save the keys + * @async + */ +Keyring.prototype.store = async function () { + await Promise.all([ + this.storeHandler.storePublic(this.publicKeys.keys), + this.storeHandler.storePrivate(this.privateKeys.keys) + ]); +}; - /** - * Calls the storeHandler to save the keys - * @async - */ - async store() { - await Promise.all([ - this.storeHandler.storePublic(this.publicKeys.keys), - this.storeHandler.storePrivate(this.privateKeys.keys) - ]); - } +/** + * Clear the keyring - erase all the keys + */ +Keyring.prototype.clear = function() { + this.publicKeys.keys = []; + this.privateKeys.keys = []; +}; - /** - * Clear the keyring - erase all the keys - */ - clear() { - this.publicKeys.keys = []; - this.privateKeys.keys = []; - } +/** + * Searches the keyring for keys having the specified key id + * @param {String} keyId provided as string of lowercase hex number + * withouth 0x prefix (can be 16-character key ID or fingerprint) + * @param {Boolean} deep if true search also in subkeys + * @returns {Array|null} keys found or null + */ +Keyring.prototype.getKeysForId = function (keyId, deep) { + let result = []; + result = result.concat(this.publicKeys.getForId(keyId, deep) || []); + result = result.concat(this.privateKeys.getForId(keyId, deep) || []); + return result.length ? result : null; +}; - /** - * Searches the keyring for keys having the specified key id - * @param {String} keyId provided as string of lowercase hex number - * withouth 0x prefix (can be 16-character key ID or fingerprint) - * @param {Boolean} deep if true search also in subkeys - * @returns {Array|null} keys found or null - */ - getKeysForId(keyId, deep) { - let result = []; - result = result.concat(this.publicKeys.getForId(keyId, deep) || []); - result = result.concat(this.privateKeys.getForId(keyId, deep) || []); - return result.length ? result : null; - } +/** + * Removes keys having the specified key id from the keyring + * @param {String} keyId provided as string of lowercase hex number + * withouth 0x prefix (can be 16-character key ID or fingerprint) + * @returns {Array|null} keys found or null + */ +Keyring.prototype.removeKeysForId = function (keyId) { + let result = []; + result = result.concat(this.publicKeys.removeForId(keyId) || []); + result = result.concat(this.privateKeys.removeForId(keyId) || []); + return result.length ? result : null; +}; - /** - * Removes keys having the specified key id from the keyring - * @param {String} keyId provided as string of lowercase hex number - * withouth 0x prefix (can be 16-character key ID or fingerprint) - * @returns {Array|null} keys found or null - */ - removeKeysForId(keyId) { - let result = []; - result = result.concat(this.publicKeys.removeForId(keyId) || []); - result = result.concat(this.privateKeys.removeForId(keyId) || []); - return result.length ? result : null; - } +/** + * Get all public and private keys + * @returns {Array} all keys + */ +Keyring.prototype.getAllKeys = function () { + return this.publicKeys.keys.concat(this.privateKeys.keys); +}; - /** - * Get all public and private keys - * @returns {Array} all keys - */ - getAllKeys() { - return this.publicKeys.keys.concat(this.privateKeys.keys); - } +/** + * Array of keys + * @param {Array} keys The keys to store in this array + */ +function KeyArray(keys) { + this.keys = keys; } +/** + * Searches all keys in the KeyArray matching the address or address part of the user ids + * @param {String} email email address to search for + * @returns {Array} The public keys associated with provided email address. + */ +KeyArray.prototype.getForAddress = function(email) { + const results = []; + for (let i = 0; i < this.keys.length; i++) { + if (emailCheck(email, this.keys[i])) { + results.push(this.keys[i]); + } + } + return results; +}; + /** * Checks a key to see if it matches the specified email address * @private @@ -229,4 +157,73 @@ function keyIdCheck(keyId, key) { return keyId === key.getFingerprint(); } +/** + * Searches the KeyArray for a key having the specified key id + * @param {String} keyId provided as string of lowercase hex number + * withouth 0x prefix (can be 16-character key ID or fingerprint) + * @param {Boolean} deep if true search also in subkeys + * @returns {module:key.Key|null} key found or null + */ +KeyArray.prototype.getForId = function (keyId, deep) { + for (let i = 0; i < this.keys.length; i++) { + if (keyIdCheck(keyId, this.keys[i])) { + return this.keys[i]; + } + if (deep && this.keys[i].subKeys.length) { + for (let j = 0; j < this.keys[i].subKeys.length; j++) { + if (keyIdCheck(keyId, this.keys[i].subKeys[j])) { + return this.keys[i]; + } + } + } + } + return null; +}; + +/** + * Imports a key from an ascii armored message + * @param {String} armored message to read the keys/key from + * @returns {Promise|null>} array of error objects or null + * @async + */ +KeyArray.prototype.importKey = async function (armored) { + const imported = await readArmored(armored); + for (let i = 0; i < imported.keys.length; i++) { + const key = imported.keys[i]; + // check if key already in key array + const keyidHex = key.getKeyId().toHex(); + const keyFound = this.getForId(keyidHex); + if (keyFound) { + await keyFound.update(key); + } else { + this.push(key); + } + } + return imported.err ? imported.err : null; +}; + +/** + * Add key to KeyArray + * @param {module:key.Key} key The key that will be added to the keyring + * @returns {Number} The new length of the KeyArray + */ +KeyArray.prototype.push = function (key) { + return this.keys.push(key); +}; + +/** + * Removes a key with the specified keyid from the keyring + * @param {String} keyId provided as string of lowercase hex number + * withouth 0x prefix (can be 16-character key ID or fingerprint) + * @returns {module:key.Key|null} The key object which has been removed or null + */ +KeyArray.prototype.removeForId = function (keyId) { + for (let i = 0; i < this.keys.length; i++) { + if (keyIdCheck(keyId, this.keys[i])) { + return this.keys.splice(i, 1)[0]; + } + } + return null; +}; + export default Keyring; diff --git a/src/keyring/localstore.js b/src/keyring/localstore.js index 0ae711f4..26ca4cd7 100644 --- a/src/keyring/localstore.js +++ b/src/keyring/localstore.js @@ -20,68 +20,29 @@ * @requires web-stream-tools * @requires config * @requires key + * @requires util * @module keyring/localstore */ import stream from 'web-stream-tools'; import config from '../config'; -import { readArmoredKey } from '../key'; +import { readArmored } from '../key'; +import util from '../util'; /** * The class that deals with storage of the keyring. * Currently the only option is to use HTML5 local storage. + * @constructor + * @param {String} prefix prefix for itemnames in localstore */ -class LocalStore { - /** - * @param {String} prefix prefix for itemnames in localstore - */ - constructor(prefix) { - prefix = prefix || 'openpgp-'; - this.publicKeysItem = prefix + this.publicKeysItem; - this.privateKeysItem = prefix + this.privateKeysItem; - if (typeof globalThis !== 'undefined' && globalThis.localStorage) { - this.storage = globalThis.localStorage; - } else { - this.storage = new (require('node-localstorage').LocalStorage)(config.nodeStore); - } - } - - /** - * Load the public keys from HTML5 local storage. - * @returns {Array} array of keys retrieved from localstore - * @async - */ - async loadPublic() { - return loadKeys(this.storage, this.publicKeysItem); - } - - /** - * Load the private keys from HTML5 local storage. - * @returns {Array} array of keys retrieved from localstore - * @async - */ - async loadPrivate() { - return loadKeys(this.storage, this.privateKeysItem); - } - - /** - * Saves the current state of the public keys to HTML5 local storage. - * The key array gets stringified using JSON - * @param {Array} keys array of keys to save in localstore - * @async - */ - async storePublic(keys) { - await storeKeys(this.storage, this.publicKeysItem, keys); - } - - /** - * Saves the current state of the private keys to HTML5 local storage. - * The key array gets stringified using JSON - * @param {Array} keys array of keys to save in localstore - * @async - */ - async storePrivate(keys) { - await storeKeys(this.storage, this.privateKeysItem, keys); +function LocalStore(prefix) { + prefix = prefix || 'openpgp-'; + this.publicKeysItem = prefix + this.publicKeysItem; + this.privateKeysItem = prefix + this.privateKeysItem; + if (typeof global !== 'undefined' && global.localStorage) { + this.storage = global.localStorage; + } else { + this.storage = new (require('node-localstorage').LocalStorage)(config.node_store); } } @@ -91,19 +52,61 @@ class LocalStore { LocalStore.prototype.publicKeysItem = 'public-keys'; LocalStore.prototype.privateKeysItem = 'private-keys'; +/** + * Load the public keys from HTML5 local storage. + * @returns {Array} array of keys retrieved from localstore + * @async + */ +LocalStore.prototype.loadPublic = async function () { + return loadKeys(this.storage, this.publicKeysItem); +}; + +/** + * Load the private keys from HTML5 local storage. + * @returns {Array} array of keys retrieved from localstore + * @async + */ +LocalStore.prototype.loadPrivate = async function () { + return loadKeys(this.storage, this.privateKeysItem); +}; + async function loadKeys(storage, itemname) { const armoredKeys = JSON.parse(storage.getItem(itemname)); const keys = []; if (armoredKeys !== null && armoredKeys.length !== 0) { let key; for (let i = 0; i < armoredKeys.length; i++) { - key = await readArmoredKey(armoredKeys[i]); - keys.push(key); + key = await readArmored(armoredKeys[i]); + if (!key.err) { + keys.push(key.keys[0]); + } else { + util.print_debug("Error reading armored key from keyring index: " + i); + } } } return keys; } +/** + * Saves the current state of the public keys to HTML5 local storage. + * The key array gets stringified using JSON + * @param {Array} keys array of keys to save in localstore + * @async + */ +LocalStore.prototype.storePublic = async function (keys) { + await storeKeys(this.storage, this.publicKeysItem, keys); +}; + +/** + * Saves the current state of the private keys to HTML5 local storage. + * The key array gets stringified using JSON + * @param {Array} keys array of keys to save in localstore + * @async + */ +LocalStore.prototype.storePrivate = async function (keys) { + await storeKeys(this.storage, this.privateKeysItem, keys); +}; + async function storeKeys(storage, itemname, keys) { if (keys.length) { const armoredKeys = await Promise.all(keys.map(key => stream.readToEnd(key.armor()))); diff --git a/src/lightweight_helper.js b/src/lightweight_helper.js new file mode 100644 index 00000000..076c0c7d --- /dev/null +++ b/src/lightweight_helper.js @@ -0,0 +1,26 @@ +/** + * Load script from path + * @param {String} path + */ +export const loadScript = path => { + if (typeof importScripts !== 'undefined') { + return importScripts(path); + } + return new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.src = path; + script.onload = () => resolve(); + script.onerror = e => reject(new Error(e.message)); + document.head.appendChild(script); + }); +}; + +/** + * Download script from path + * @param {String} path fetch path + * @param {Object} options fetch options + */ +export const dl = async function(path, options) { + const response = await fetch(path, options); + return response.arrayBuffer(); +}; diff --git a/src/message.js b/src/message.js index 4c09391e..f14738e2 100644 --- a/src/message.js +++ b/src/message.js @@ -30,674 +30,503 @@ */ import stream from 'web-stream-tools'; -import { armor, unarmor } from './encoding/armor'; +import armor from './encoding/armor'; import type_keyid from './type/keyid'; import config from './config'; import crypto from './crypto'; import enums from './enums'; import util from './util'; -import { - PacketList, - LiteralDataPacket, - CompressedDataPacket, - AEADEncryptedDataPacket, - SymEncryptedIntegrityProtectedDataPacket, - SymmetricallyEncryptedDataPacket, - PublicKeyEncryptedSessionKeyPacket, - SymEncryptedSessionKeyPacket, - OnePassSignaturePacket, - SignaturePacket -} from './packet'; +import packet from './packet'; import { Signature } from './signature'; import { getPreferredHashAlgo, getPreferredAlgo, isAeadSupported, createSignaturePacket } from './key'; /** - * Class that represents an OpenPGP message. + * @class + * @classdesc Class that represents an OpenPGP message. * Can be an encrypted message, signed message, compressed message or literal message + * @param {module:packet.List} packetlist The packets that form this message * See {@link https://tools.ietf.org/html/rfc4880#section-11.3} */ -export class Message { - /** - * @param {module:PacketList} packetlist The packets that form this message - */ - constructor(packetlist) { - this.packets = packetlist || new PacketList(); - } - /** - * Returns the key IDs of the keys to which the session key is encrypted - * @returns {Array} array of keyid objects - */ - getEncryptionKeyIds() { - const keyIds = []; - const pkESKeyPacketlist = this.packets.filterByTag(enums.packet.publicKeyEncryptedSessionKey); - pkESKeyPacketlist.forEach(function(packet) { - keyIds.push(packet.publicKeyId); - }); - return keyIds; +export function Message(packetlist) { + if (!(this instanceof Message)) { + return new Message(packetlist); } + this.packets = packetlist || new packet.List(); +} + +/** + * Returns the key IDs of the keys to which the session key is encrypted + * @returns {Array} array of keyid objects + */ +Message.prototype.getEncryptionKeyIds = function() { + const keyIds = []; + const pkESKeyPacketlist = this.packets.filterByTag(enums.packet.publicKeyEncryptedSessionKey); + pkESKeyPacketlist.forEach(function(packet) { + keyIds.push(packet.publicKeyId); + }); + return keyIds; +}; - /** - * Returns the key IDs of the keys that signed the message - * @returns {Array} array of keyid objects - */ - getSigningKeyIds() { - const keyIds = []; - const msg = this.unwrapCompressed(); - // search for one pass signatures - const onePassSigList = msg.packets.filterByTag(enums.packet.onePassSignature); - onePassSigList.forEach(function(packet) { +/** + * Returns the key IDs of the keys that signed the message + * @returns {Array} array of keyid objects + */ +Message.prototype.getSigningKeyIds = function() { + const keyIds = []; + const msg = this.unwrapCompressed(); + // search for one pass signatures + const onePassSigList = msg.packets.filterByTag(enums.packet.onePassSignature); + onePassSigList.forEach(function(packet) { + keyIds.push(packet.issuerKeyId); + }); + // if nothing found look for signature packets + if (!keyIds.length) { + const signatureList = msg.packets.filterByTag(enums.packet.signature); + signatureList.forEach(function(packet) { keyIds.push(packet.issuerKeyId); }); - // if nothing found look for signature packets - if (!keyIds.length) { - const signatureList = msg.packets.filterByTag(enums.packet.signature); - signatureList.forEach(function(packet) { - keyIds.push(packet.issuerKeyId); - }); - } - return keyIds; } + return keyIds; +}; - /** - * Decrypt the message. Either a private key, a session key, or a password must be specified. - * @param {Array} privateKeys (optional) private keys with decrypted secret data - * @param {Array} passwords (optional) passwords used to decrypt - * @param {Array} sessionKeys (optional) session keys in the form: { data:Uint8Array, algorithm:String, [aeadAlgorithm:String] } - * @param {Boolean} streaming (optional) whether to process data as a stream - * @returns {Promise} new message with decrypted content - * @async - */ - async decrypt(privateKeys, passwords, sessionKeys, streaming) { - const keyObjs = sessionKeys || await this.decryptSessionKeys(privateKeys, passwords); - - const symEncryptedPacketlist = this.packets.filterByTag( - enums.packet.symmetricallyEncryptedData, - enums.packet.symEncryptedIntegrityProtectedData, - enums.packet.AEADEncryptedData - ); - - if (symEncryptedPacketlist.length === 0) { - return this; - } +/** + * Decrypt the message. Either a private key, a session key, or a password must be specified. + * @param {Array} privateKeys (optional) private keys with decrypted secret data + * @param {Array} passwords (optional) passwords used to decrypt + * @param {Array} sessionKeys (optional) session keys in the form: { data:Uint8Array, algorithm:String, [aeadAlgorithm:String] } + * @param {Boolean} streaming (optional) whether to process data as a stream + * @returns {Promise} new message with decrypted content + * @async + */ +Message.prototype.decrypt = async function(privateKeys, passwords, sessionKeys, streaming) { + const keyObjs = sessionKeys || await this.decryptSessionKeys(privateKeys, passwords); - const symEncryptedPacket = symEncryptedPacketlist[0]; - let exception = null; - const decryptedPromise = Promise.all(keyObjs.map(async keyObj => { - 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 + ); - try { - await symEncryptedPacket.decrypt(keyObj.algorithm, keyObj.data, streaming); - } catch (e) { - util.printDebugError(e); - exception = e; - } - })); - // We don't await stream.cancel here because it only returns when the other copy is canceled too. - stream.cancel(symEncryptedPacket.encrypted); // Don't keep copy of encrypted data in memory. - symEncryptedPacket.encrypted = null; - await decryptedPromise; + if (symEncryptedPacketlist.length === 0) { + return this; + } - if (!symEncryptedPacket.packets || !symEncryptedPacket.packets.length) { - throw exception || new Error('Decryption failed.'); + const symEncryptedPacket = symEncryptedPacketlist[0]; + let exception = null; + const decryptedPromise = Promise.all(keyObjs.map(async keyObj => { + if (!keyObj || !util.isUint8Array(keyObj.data) || !util.isString(keyObj.algorithm)) { + throw new Error('Invalid session key for decryption.'); } - const resultMsg = new Message(symEncryptedPacket.packets); - symEncryptedPacket.packets = new PacketList(); // remove packets after decryption + try { + await symEncryptedPacket.decrypt(keyObj.algorithm, keyObj.data, streaming); + } catch (e) { + util.print_debug_error(e); + exception = e; + } + })); + // We don't await stream.cancel here because it only returns when the other copy is canceled too. + stream.cancel(symEncryptedPacket.encrypted); // Don't keep copy of encrypted data in memory. + symEncryptedPacket.encrypted = null; + await decryptedPromise; - return resultMsg; + if (!symEncryptedPacket.packets || !symEncryptedPacket.packets.length) { + throw exception || new Error('Decryption failed.'); } - /** - * Decrypt encrypted session keys either with private keys or passwords. - * @param {Array} privateKeys (optional) private keys with decrypted secret data - * @param {Array} passwords (optional) passwords used to decrypt - * @returns {Promise>} array of object with potential sessionKey, algorithm pairs - * @async - */ - async decryptSessionKeys(privateKeys, passwords) { - let keyPackets = []; - - let exception; - if (passwords) { - const symESKeyPacketlist = this.packets.filterByTag(enums.packet.symEncryptedSessionKey); - if (!symESKeyPacketlist) { - throw new Error('No symmetrically encrypted session key packet found.'); + const resultMsg = new Message(symEncryptedPacket.packets); + symEncryptedPacket.packets = new packet.List(); // remove packets after decryption + + return resultMsg; +}; + +/** + * Decrypt encrypted session keys either with private keys or passwords. + * @param {Array} privateKeys (optional) private keys with decrypted secret data + * @param {Array} passwords (optional) passwords used to decrypt + * @returns {Promise>} array of object with potential sessionKey, algorithm pairs + * @async + */ +Message.prototype.decryptSessionKeys = async function(privateKeys, passwords) { + let keyPackets = []; + + let exception; + if (passwords) { + const symESKeyPacketlist = this.packets.filterByTag(enums.packet.symEncryptedSessionKey); + if (!symESKeyPacketlist) { + throw new Error('No symmetrically encrypted session key packet found.'); + } + await Promise.all(passwords.map(async function(password, i) { + let packets; + if (i) { + packets = new packet.List(); + await packets.read(symESKeyPacketlist.write()); + } else { + packets = symESKeyPacketlist; } - await Promise.all(passwords.map(async function(password, i) { - let packets; - if (i) { - packets = new PacketList(); - await packets.read(symESKeyPacketlist.write(), { SymEncryptedSessionKeyPacket }); - } else { - packets = symESKeyPacketlist; + await Promise.all(packets.map(async function(keyPacket) { + try { + await keyPacket.decrypt(password); + keyPackets.push(keyPacket); + } catch (err) { + util.print_debug_error(err); } - await Promise.all(packets.map(async function(keyPacket) { + })); + })); + } else if (privateKeys) { + const pkESKeyPacketlist = this.packets.filterByTag(enums.packet.publicKeyEncryptedSessionKey); + if (!pkESKeyPacketlist) { + throw new Error('No public key encrypted session key packet found.'); + } + await Promise.all(pkESKeyPacketlist.map(async function(keyPacket) { + await Promise.all(privateKeys.map(async function(privateKey) { + let algos = [ + enums.symmetric.aes256, // Old OpenPGP.js default fallback + enums.symmetric.aes128, // RFC4880bis fallback + enums.symmetric.tripledes, // RFC4880 fallback + enums.symmetric.cast5 // Golang OpenPGP fallback + ]; + try { + const primaryUser = await privateKey.getPrimaryUser(); // TODO: Pass userId from somewhere. + if (primaryUser.selfCertification.preferredSymmetricAlgorithms) { + algos = algos.concat(primaryUser.selfCertification.preferredSymmetricAlgorithms); + } + } catch (e) {} + + // do not check key expiration to allow decryption of old messages + const privateKeyPackets = (await privateKey.getDecryptionKeys(keyPacket.publicKeyId, null)).map(key => key.keyPacket); + await Promise.all(privateKeyPackets.map(async function(privateKeyPacket) { + if (!privateKeyPacket) { + return; + } + if (!privateKeyPacket.isDecrypted()) { + throw new Error('Private key is not decrypted.'); + } try { - await keyPacket.decrypt(password); + await keyPacket.decrypt(privateKeyPacket); + if (!algos.includes(enums.write(enums.symmetric, keyPacket.sessionKeyAlgorithm))) { + throw new Error('A non-preferred symmetric algorithm was used.'); + } keyPackets.push(keyPacket); } catch (err) { - util.printDebugError(err); + util.print_debug_error(err); + exception = err; } })); })); - } else if (privateKeys) { - const pkESKeyPacketlist = this.packets.filterByTag(enums.packet.publicKeyEncryptedSessionKey); - if (!pkESKeyPacketlist) { - throw new Error('No public key encrypted session key packet found.'); - } - await Promise.all(pkESKeyPacketlist.map(async function(keyPacket) { - await Promise.all(privateKeys.map(async function(privateKey) { - let algos = [ - enums.symmetric.aes256, // Old OpenPGP.js default fallback - enums.symmetric.aes128, // RFC4880bis fallback - enums.symmetric.tripledes, // RFC4880 fallback - enums.symmetric.cast5 // Golang OpenPGP fallback - ]; - try { - const primaryUser = await privateKey.getPrimaryUser(); // TODO: Pass userId from somewhere. - if (primaryUser.selfCertification.preferredSymmetricAlgorithms) { - algos = algos.concat(primaryUser.selfCertification.preferredSymmetricAlgorithms); - } - } catch (e) {} - - // do not check key expiration to allow decryption of old messages - const privateKeyPackets = (await privateKey.getDecryptionKeys(keyPacket.publicKeyId, null)).map(key => key.keyPacket); - await Promise.all(privateKeyPackets.map(async function(privateKeyPacket) { - if (!privateKeyPacket || privateKeyPacket.isDummy()) { - return; - } - if (!privateKeyPacket.isDecrypted()) { - throw new Error('Private key is not decrypted.'); - } - try { - await keyPacket.decrypt(privateKeyPacket); - if (!algos.includes(enums.write(enums.symmetric, keyPacket.sessionKeyAlgorithm))) { - throw new Error('A non-preferred symmetric algorithm was used.'); - } - keyPackets.push(keyPacket); - } catch (err) { - util.printDebugError(err); - exception = err; - } - })); - })); - stream.cancel(keyPacket.encrypted); // Don't keep copy of encrypted data in memory. - keyPacket.encrypted = null; - })); - } else { - throw new Error('No key or password specified.'); - } - - if (keyPackets.length) { - // Return only unique session keys - if (keyPackets.length > 1) { - const seen = {}; - keyPackets = keyPackets.filter(function(item) { - const k = item.sessionKeyAlgorithm + util.uint8ArrayToStr(item.sessionKey); - if (seen.hasOwnProperty(k)) { - return false; - } - seen[k] = true; - return true; - }); - } + stream.cancel(keyPacket.encrypted); // Don't keep copy of encrypted data in memory. + keyPacket.encrypted = null; + })); + } else { + throw new Error('No key or password specified.'); + } - return keyPackets.map(packet => ({ data: packet.sessionKey, algorithm: packet.sessionKeyAlgorithm })); + if (keyPackets.length) { + // Return only unique session keys + if (keyPackets.length > 1) { + const seen = {}; + keyPackets = keyPackets.filter(function(item) { + const k = item.sessionKeyAlgorithm + util.Uint8Array_to_str(item.sessionKey); + if (seen.hasOwnProperty(k)) { + return false; + } + seen[k] = true; + return true; + }); } - throw exception || new Error('Session key decryption failed.'); - } - /** - * Get literal data that is the body of the message - * @returns {(Uint8Array|null)} literal body of the message as Uint8Array - */ - getLiteralData() { - const msg = this.unwrapCompressed(); - const literal = msg.packets.findPacket(enums.packet.literalData); - return (literal && literal.getBytes()) || null; + return keyPackets.map(packet => ({ data: packet.sessionKey, algorithm: packet.sessionKeyAlgorithm })); } + throw exception || new Error('Session key decryption failed.'); +}; - /** - * Get filename from literal data packet - * @returns {(String|null)} filename of literal data packet as string - */ - getFilename() { - const msg = this.unwrapCompressed(); - const literal = msg.packets.findPacket(enums.packet.literalData); - return (literal && literal.getFilename()) || null; - } +/** + * Get literal data that is the body of the message + * @returns {(Uint8Array|null)} literal body of the message as Uint8Array + */ +Message.prototype.getLiteralData = function() { + const msg = this.unwrapCompressed(); + const literal = msg.packets.findPacket(enums.packet.literal); + return (literal && literal.getBytes()) || null; +}; - /** - * Get literal data as text - * @returns {(String|null)} literal body of the message interpreted as text - */ - getText() { - const msg = this.unwrapCompressed(); - const literal = msg.packets.findPacket(enums.packet.literalData); - if (literal) { - return literal.getText(); - } - return null; - } +/** + * Get filename from literal data packet + * @returns {(String|null)} filename of literal data packet as string + */ +Message.prototype.getFilename = function() { + const msg = this.unwrapCompressed(); + const literal = msg.packets.findPacket(enums.packet.literal); + return (literal && literal.getFilename()) || null; +}; - /** - * Generate a new session key object, taking the algorithm preferences of the passed public keys into account, if any. - * @param {Array} keys (optional) public key(s) to select algorithm preferences for - * @param {Date} date (optional) date to select algorithm preferences at - * @param {Array} userIds (optional) user IDs to select algorithm preferences for - * @returns {Promise<{ data: Uint8Array, algorithm: String }>} object with session key data and algorithm - * @async - */ - static async generateSessionKey(keys = [], date = new Date(), userIds = []) { - const algorithm = enums.read(enums.symmetric, await getPreferredAlgo('symmetric', keys, date, userIds)); - const aeadAlgorithm = config.aeadProtect && await isAeadSupported(keys, date, userIds) ? - enums.read(enums.aead, await getPreferredAlgo('aead', keys, date, userIds)) : - undefined; - const sessionKeyData = await crypto.generateSessionKey(algorithm); - return { data: sessionKeyData, algorithm, aeadAlgorithm }; +/** + * Get literal data as text + * @returns {(String|null)} literal body of the message interpreted as text + */ +Message.prototype.getText = function() { + const msg = this.unwrapCompressed(); + const literal = msg.packets.findPacket(enums.packet.literal); + if (literal) { + return literal.getText(); } + return null; +}; - /** - * Encrypt the message either with public keys, passwords, or both at once. - * @param {Array} keys (optional) public key(s) for message encryption - * @param {Array} passwords (optional) password(s) for message encryption - * @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String, [aeadAlgorithm:String] } - * @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs - * @param {Date} date (optional) override the creation date of the literal package - * @param {Array} userIds (optional) user IDs to encrypt for, e.g. [{ name:'Robert Receiver', email:'robert@openpgp.org' }] - * @param {Boolean} streaming (optional) whether to process data as a stream - * @returns {Promise} new message with encrypted content - * @async - */ - async encrypt(keys, passwords, sessionKey, wildcard = false, date = new Date(), userIds = [], streaming) { - if (sessionKey) { - if (!util.isUint8Array(sessionKey.data) || !util.isString(sessionKey.algorithm)) { - throw new Error('Invalid session key for encryption.'); - } - } else if (keys && keys.length) { - sessionKey = await Message.generateSessionKey(keys, date, userIds); - } else if (passwords && passwords.length) { - sessionKey = await Message.generateSessionKey(); - } else { - throw new Error('No keys, passwords, or session key provided.'); +/** + * Encrypt the message either with public keys, passwords, or both at once. + * @param {Array} keys (optional) public key(s) for message encryption + * @param {Array} passwords (optional) password(s) for message encryption + * @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String, [aeadAlgorithm:String] } + * @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs + * @param {Date} date (optional) override the creation date of the literal package + * @param {Array} userIds (optional) user IDs to encrypt for, e.g. [{ name:'Robert Receiver', email:'robert@openpgp.org' }] + * @param {Boolean} streaming (optional) whether to process data as a stream + * @returns {Promise} new message with encrypted content + * @async + */ +Message.prototype.encrypt = async function(keys, passwords, sessionKey, wildcard = false, date = new Date(), userIds = [], streaming) { + let symAlgo; + let aeadAlgo; + let symEncryptedPacket; + + if (sessionKey) { + if (!util.isUint8Array(sessionKey.data) || !util.isString(sessionKey.algorithm)) { + throw new Error('Invalid session key for encryption.'); } - - const { data: sessionKeyData, algorithm, aeadAlgorithm } = sessionKey; - - const msg = await Message.encryptSessionKey(sessionKeyData, algorithm, aeadAlgorithm, keys, passwords, wildcard, date, userIds); - - let symEncryptedPacket; - if (aeadAlgorithm) { - symEncryptedPacket = new AEADEncryptedDataPacket(); - symEncryptedPacket.aeadAlgorithm = aeadAlgorithm; - } else if (config.integrityProtect) { - symEncryptedPacket = new SymEncryptedIntegrityProtectedDataPacket(); - } else { - symEncryptedPacket = new SymmetricallyEncryptedDataPacket(); + symAlgo = sessionKey.algorithm; + aeadAlgo = sessionKey.aeadAlgorithm; + sessionKey = sessionKey.data; + } else if (keys && keys.length) { + symAlgo = enums.read(enums.symmetric, await getPreferredAlgo('symmetric', keys, date, userIds)); + if (config.aead_protect && await isAeadSupported(keys, date, userIds)) { + aeadAlgo = enums.read(enums.aead, await getPreferredAlgo('aead', keys, date, userIds)); } - symEncryptedPacket.packets = this.packets; - - await symEncryptedPacket.encrypt(algorithm, sessionKeyData, streaming); - - msg.packets.push(symEncryptedPacket); - symEncryptedPacket.packets = new PacketList(); // remove packets after encryption - return msg; + } else if (passwords && passwords.length) { + symAlgo = enums.read(enums.symmetric, config.encryption_cipher); + aeadAlgo = enums.read(enums.aead, config.aead_mode); + } else { + throw new Error('No keys, passwords, or session key provided.'); } - /** - * Encrypt a session key either with public keys, passwords, or both at once. - * @param {Uint8Array} sessionKey session key for encryption - * @param {String} algorithm session key algorithm - * @param {String} aeadAlgorithm (optional) aead algorithm, e.g. 'eax' or 'ocb' - * @param {Array} publicKeys (optional) public key(s) for message encryption - * @param {Array} passwords (optional) for message encryption - * @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs - * @param {Date} date (optional) override the date - * @param {Array} userIds (optional) user IDs to encrypt for, e.g. [{ name:'Robert Receiver', email:'robert@openpgp.org' }] - * @returns {Promise} new message with encrypted content - * @async - */ - static async encryptSessionKey(sessionKey, algorithm, aeadAlgorithm, publicKeys, passwords, wildcard = false, date = new Date(), userIds = []) { - const packetlist = new PacketList(); - - if (publicKeys) { - const results = await Promise.all(publicKeys.map(async function(publicKey) { - const encryptionKey = await publicKey.getEncryptionKey(undefined, date, userIds); - const pkESKeyPacket = new PublicKeyEncryptedSessionKeyPacket(); - pkESKeyPacket.publicKeyId = wildcard ? type_keyid.wildcard() : encryptionKey.getKeyId(); - pkESKeyPacket.publicKeyAlgorithm = encryptionKey.keyPacket.algorithm; - pkESKeyPacket.sessionKey = sessionKey; - pkESKeyPacket.sessionKeyAlgorithm = algorithm; - await pkESKeyPacket.encrypt(encryptionKey.keyPacket); - delete pkESKeyPacket.sessionKey; // delete plaintext session key after encryption - return pkESKeyPacket; - })); - packetlist.concat(results); - } - if (passwords) { - const testDecrypt = async function(keyPacket, password) { - try { - await keyPacket.decrypt(password); - return 1; - } catch (e) { - return 0; - } - }; - - const sum = (accumulator, currentValue) => accumulator + currentValue; - - const encryptPassword = async function(sessionKey, algorithm, aeadAlgorithm, password) { - const symEncryptedSessionKeyPacket = new SymEncryptedSessionKeyPacket(); - symEncryptedSessionKeyPacket.sessionKey = sessionKey; - symEncryptedSessionKeyPacket.sessionKeyAlgorithm = algorithm; - if (aeadAlgorithm) { - symEncryptedSessionKeyPacket.aeadAlgorithm = aeadAlgorithm; - } - await symEncryptedSessionKeyPacket.encrypt(password); - - if (config.passwordCollisionCheck) { - const results = await Promise.all(passwords.map(pwd => testDecrypt(symEncryptedSessionKeyPacket, pwd))); - if (results.reduce(sum) !== 1) { - return encryptPassword(sessionKey, algorithm, password); - } - } + if (!sessionKey) { + sessionKey = await crypto.generateSessionKey(symAlgo); + } - delete symEncryptedSessionKeyPacket.sessionKey; // delete plaintext session key after encryption - return symEncryptedSessionKeyPacket; - }; + const msg = await encryptSessionKey(sessionKey, symAlgo, aeadAlgo, keys, passwords, wildcard, date, userIds); - const results = await Promise.all(passwords.map(pwd => encryptPassword(sessionKey, algorithm, aeadAlgorithm, pwd))); - packetlist.concat(results); + if (config.aead_protect && aeadAlgo) { + symEncryptedPacket = new packet.SymEncryptedAEADProtected(); + symEncryptedPacket.aeadAlgorithm = aeadAlgo; + } else if (config.integrity_protect) { + symEncryptedPacket = new packet.SymEncryptedIntegrityProtected(); + } else { + symEncryptedPacket = new packet.SymmetricallyEncrypted(); + } + symEncryptedPacket.packets = this.packets; + + await symEncryptedPacket.encrypt(symAlgo, sessionKey, streaming); + + msg.packets.push(symEncryptedPacket); + symEncryptedPacket.packets = new packet.List(); // remove packets after encryption + return { + message: msg, + sessionKey: { + data: sessionKey, + algorithm: symAlgo, + aeadAlgorithm: aeadAlgo } + }; +}; - return new Message(packetlist); +/** + * Encrypt a session key either with public keys, passwords, or both at once. + * @param {Uint8Array} sessionKey session key for encryption + * @param {String} symAlgo session key algorithm + * @param {String} aeadAlgo (optional) aead algorithm, e.g. 'eax' or 'ocb' + * @param {Array} publicKeys (optional) public key(s) for message encryption + * @param {Array} passwords (optional) for message encryption + * @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs + * @param {Date} date (optional) override the date + * @param {Array} userIds (optional) user IDs to encrypt for, e.g. [{ name:'Robert Receiver', email:'robert@openpgp.org' }] + * @returns {Promise} new message with encrypted content + * @async + */ +export async function encryptSessionKey(sessionKey, symAlgo, aeadAlgo, publicKeys, passwords, wildcard = false, date = new Date(), userIds = []) { + const packetlist = new packet.List(); + + if (publicKeys) { + const results = await Promise.all(publicKeys.map(async function(publicKey) { + const encryptionKey = await publicKey.getEncryptionKey(undefined, date, userIds); + const pkESKeyPacket = new packet.PublicKeyEncryptedSessionKey(); + pkESKeyPacket.publicKeyId = wildcard ? type_keyid.wildcard() : encryptionKey.getKeyId(); + pkESKeyPacket.publicKeyAlgorithm = encryptionKey.keyPacket.algorithm; + pkESKeyPacket.sessionKey = sessionKey; + pkESKeyPacket.sessionKeyAlgorithm = symAlgo; + await pkESKeyPacket.encrypt(encryptionKey.keyPacket); + delete pkESKeyPacket.sessionKey; // delete plaintext session key after encryption + return pkESKeyPacket; + })); + packetlist.concat(results); } + if (passwords) { + const testDecrypt = async function(keyPacket, password) { + try { + await keyPacket.decrypt(password); + return 1; + } catch (e) { + return 0; + } + }; - /** - * Sign the message (the literal data packet of the message) - * @param {Array} privateKeys private keys with decrypted secret key data for signing - * @param {Signature} signature (optional) any existing detached signature to add to the message - * @param {Date} date (optional) override the creation time of the signature - * @param {Array} userIds (optional) user IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }] - * @param {Boolean} streaming (optional) whether to process data as a stream - * @returns {Promise} new message with signed content - * @async - */ - async sign(privateKeys = [], signature = null, date = new Date(), userIds = [], streaming = false) { - const packetlist = new PacketList(); - - const literalDataPacket = this.packets.findPacket(enums.packet.literalData); - if (!literalDataPacket) { - throw new Error('No literal data packet to sign.'); - } + const sum = (accumulator, currentValue) => accumulator + currentValue; - let i; - let existingSigPacketlist; - // If data packet was created from Uint8Array, use binary, otherwise use text - const signatureType = literalDataPacket.text === null ? - enums.signature.binary : enums.signature.text; - - if (signature) { - existingSigPacketlist = signature.packets.filterByTag(enums.packet.signature); - for (i = existingSigPacketlist.length - 1; i >= 0; i--) { - const signaturePacket = existingSigPacketlist[i]; - const onePassSig = new OnePassSignaturePacket(); - onePassSig.signatureType = signaturePacket.signatureType; - onePassSig.hashAlgorithm = signaturePacket.hashAlgorithm; - onePassSig.publicKeyAlgorithm = signaturePacket.publicKeyAlgorithm; - onePassSig.issuerKeyId = signaturePacket.issuerKeyId; - if (!privateKeys.length && i === 0) { - onePassSig.flags = 1; - } - packetlist.push(onePassSig); + const encryptPassword = async function(sessionKey, symAlgo, aeadAlgo, password) { + const symEncryptedSessionKeyPacket = new packet.SymEncryptedSessionKey(); + symEncryptedSessionKeyPacket.sessionKey = sessionKey; + symEncryptedSessionKeyPacket.sessionKeyAlgorithm = symAlgo; + if (aeadAlgo) { + symEncryptedSessionKeyPacket.aeadAlgorithm = aeadAlgo; } - } + await symEncryptedSessionKeyPacket.encrypt(password); - await Promise.all(Array.from(privateKeys).reverse().map(async function (privateKey, i) { - if (privateKey.isPublic()) { - throw new Error('Need private key for signing'); - } - const signingKey = await privateKey.getSigningKey(undefined, date, userIds); - const onePassSig = new OnePassSignaturePacket(); - onePassSig.signatureType = signatureType; - onePassSig.hashAlgorithm = await getPreferredHashAlgo(privateKey, signingKey.keyPacket, date, userIds); - onePassSig.publicKeyAlgorithm = signingKey.keyPacket.algorithm; - onePassSig.issuerKeyId = signingKey.getKeyId(); - if (i === privateKeys.length - 1) { - onePassSig.flags = 1; + if (config.password_collision_check) { + const results = await Promise.all(passwords.map(pwd => testDecrypt(symEncryptedSessionKeyPacket, pwd))); + if (results.reduce(sum) !== 1) { + return encryptPassword(sessionKey, symAlgo, password); + } } - return onePassSig; - })).then(onePassSignatureList => { - onePassSignatureList.forEach(onePassSig => packetlist.push(onePassSig)); - }); - packetlist.push(literalDataPacket); - packetlist.concat(await createSignaturePackets(literalDataPacket, privateKeys, signature, date, userIds, false, streaming)); + delete symEncryptedSessionKeyPacket.sessionKey; // delete plaintext session key after encryption + return symEncryptedSessionKeyPacket; + }; - return new Message(packetlist); + const results = await Promise.all(passwords.map(pwd => encryptPassword(sessionKey, symAlgo, aeadAlgo, pwd))); + packetlist.concat(results); } - /** - * Compresses the message (the literal and -if signed- signature data packets of the message) - * @param {module:enums.compression} compression compression algorithm to be used - * @returns {module:message.Message} new message with compressed content - */ - compress(compression) { - if (compression === enums.compression.uncompressed) { - return this; - } - - const compressed = new CompressedDataPacket(); - compressed.packets = this.packets; - compressed.algorithm = enums.read(enums.compression, compression); + return new Message(packetlist); +} - const packetList = new PacketList(); - packetList.push(compressed); +/** + * Sign the message (the literal data packet of the message) + * @param {Array} privateKeys private keys with decrypted secret key data for signing + * @param {Signature} signature (optional) any existing detached signature to add to the message + * @param {Date} date (optional) override the creation time of the signature + * @param {Array} userIds (optional) user IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }] + * @param {Boolean} streaming (optional) whether to process data as a stream + * @returns {Promise} new message with signed content + * @async + */ +Message.prototype.sign = async function(privateKeys = [], signature = null, date = new Date(), userIds = [], streaming = false) { + const packetlist = new packet.List(); - return new Message(packetList); + const literalDataPacket = this.packets.findPacket(enums.packet.literal); + if (!literalDataPacket) { + throw new Error('No literal data packet to sign.'); } - /** - * Create a detached signature for the message (the literal data packet of the message) - * @param {Array} privateKeys private keys with decrypted secret key data for signing - * @param {Signature} signature (optional) any existing detached signature - * @param {Date} date (optional) override the creation time of the signature - * @param {Array} userIds (optional) user IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }] - * @param {Boolean} streaming (optional) whether to process data as a stream - * @returns {Promise} new detached signature of message content - * @async - */ - async signDetached(privateKeys = [], signature = null, date = new Date(), userIds = [], streaming = false) { - const literalDataPacket = this.packets.findPacket(enums.packet.literalData); - if (!literalDataPacket) { - throw new Error('No literal data packet to sign.'); - } - return new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, date, userIds, true, streaming)); - } + let i; + let existingSigPacketlist; + // If data packet was created from Uint8Array, use binary, otherwise use text + const signatureType = literalDataPacket.text === null ? + enums.signature.binary : enums.signature.text; - /** - * Verify message signatures - * @param {Array} keys array of keys to verify signatures - * @param {Date} date (optional) Verify the signature against the given date, i.e. check signature creation time < date < expiration time - * @param {Boolean} streaming (optional) whether to process data as a stream - * @returns {Promise>} list of signer's keyid and validity of signature - * @async - */ - async verify(keys, date = new Date(), streaming) { - const msg = this.unwrapCompressed(); - const literalDataList = msg.packets.filterByTag(enums.packet.literalData); - if (literalDataList.length !== 1) { - throw new Error('Can only verify message with one literal data packet.'); - } - if (!streaming) { - msg.packets.concat(await stream.readToEnd(msg.packets.stream, _ => _)); - } - const onePassSigList = msg.packets.filterByTag(enums.packet.onePassSignature).reverse(); - const signatureList = msg.packets.filterByTag(enums.packet.signature); - if (streaming && onePassSigList.length && !signatureList.length && msg.packets.stream) { - await Promise.all(onePassSigList.map(async onePassSig => { - onePassSig.correspondingSig = new Promise((resolve, reject) => { - onePassSig.correspondingSigResolve = resolve; - onePassSig.correspondingSigReject = reject; - }); - onePassSig.signatureData = stream.fromAsync(async () => (await onePassSig.correspondingSig).signatureData); - onePassSig.hashed = stream.readToEnd(await onePassSig.hash(onePassSig.signatureType, literalDataList[0], undefined, false, streaming)); - onePassSig.hashed.catch(() => {}); - })); - msg.packets.stream = stream.transformPair(msg.packets.stream, async (readable, writable) => { - const reader = stream.getReader(readable); - const writer = stream.getWriter(writable); - try { - for (let i = 0; i < onePassSigList.length; i++) { - const { value: signature } = await reader.read(); - onePassSigList[i].correspondingSigResolve(signature); - } - await reader.readToEnd(); - await writer.ready; - await writer.close(); - } catch (e) { - onePassSigList.forEach(onePassSig => { - onePassSig.correspondingSigReject(e); - }); - await writer.abort(e); - } - }); - return createVerificationObjects(onePassSigList, literalDataList, keys, date, false, streaming); + if (signature) { + existingSigPacketlist = signature.packets.filterByTag(enums.packet.signature); + for (i = existingSigPacketlist.length - 1; i >= 0; i--) { + const signaturePacket = existingSigPacketlist[i]; + const onePassSig = new packet.OnePassSignature(); + onePassSig.signatureType = signaturePacket.signatureType; + onePassSig.hashAlgorithm = signaturePacket.hashAlgorithm; + onePassSig.publicKeyAlgorithm = signaturePacket.publicKeyAlgorithm; + onePassSig.issuerKeyId = signaturePacket.issuerKeyId; + if (!privateKeys.length && i === 0) { + onePassSig.flags = 1; + } + packetlist.push(onePassSig); } - return createVerificationObjects(signatureList, literalDataList, keys, date, false, streaming); } - /** - * Verify detached message signature - * @param {Array} keys array of keys to verify signatures - * @param {Signature} signature - * @param {Date} date Verify the signature against the given date, i.e. check signature creation time < date < expiration time - * @returns {Promise>} list of signer's keyid and validity of signature - * @async - */ - verifyDetached(signature, keys, date = new Date()) { - const msg = this.unwrapCompressed(); - const literalDataList = msg.packets.filterByTag(enums.packet.literalData); - if (literalDataList.length !== 1) { - throw new Error('Can only verify message with one literal data packet.'); + await Promise.all(Array.from(privateKeys).reverse().map(async function (privateKey, i) { + if (privateKey.isPublic()) { + throw new Error('Need private key for signing'); } - const signatureList = signature.packets; - return createVerificationObjects(signatureList, literalDataList, keys, date, true); - } - - /** - * Unwrap compressed message - * @returns {module:message.Message} message Content of compressed message - */ - unwrapCompressed() { - const compressed = this.packets.filterByTag(enums.packet.compressedData); - if (compressed.length) { - return new Message(compressed[0].packets); + const signingKey = await privateKey.getSigningKey(undefined, date, userIds); + const onePassSig = new packet.OnePassSignature(); + onePassSig.signatureType = signatureType; + onePassSig.hashAlgorithm = await getPreferredHashAlgo(privateKey, signingKey.keyPacket, date, userIds); + onePassSig.publicKeyAlgorithm = signingKey.keyPacket.algorithm; + onePassSig.issuerKeyId = signingKey.getKeyId(); + if (i === privateKeys.length - 1) { + onePassSig.flags = 1; } - return this; - } + return onePassSig; + })).then(onePassSignatureList => { + onePassSignatureList.forEach(onePassSig => packetlist.push(onePassSig)); + }); - /** - * Append signature to unencrypted message object - * @param {String|Uint8Array} detachedSignature The detached ASCII-armored or Uint8Array PGP signature - */ - async appendSignature(detachedSignature) { - await this.packets.read(util.isUint8Array(detachedSignature) ? detachedSignature : (await unarmor(detachedSignature)).data, { SignaturePacket }); - } + packetlist.push(literalDataPacket); + packetlist.concat(await createSignaturePackets(literalDataPacket, privateKeys, signature, date, userIds, false, streaming)); - /** - * Returns binary encoded message - * @returns {ReadableStream} binary message - */ - write() { - return this.packets.write(); - } + return new Message(packetlist); +}; - /** - * Returns ASCII armored text of message - * @returns {ReadableStream} ASCII armor - */ - armor() { - return armor(enums.armor.message, this.write()); +/** + * Compresses the message (the literal and -if signed- signature data packets of the message) + * @param {module:enums.compression} compression compression algorithm to be used + * @returns {module:message.Message} new message with compressed content + */ +Message.prototype.compress = function(compression) { + if (compression === enums.compression.uncompressed) { + return this; } - /** - * creates new message object from text - * @param {String | ReadableStream} text - * @param {String} filename (optional) - * @param {Date} date (optional) - * @param {utf8|binary|text|mime} type (optional) data packet type - * @returns {module:message.Message} new message object - * @static - */ - static fromText(text, filename, date = new Date(), type = 'utf8') { - const streamType = util.isStream(text); - if (streamType === 'node') { - text = stream.nodeToWeb(text); - } - const literalDataPacket = new LiteralDataPacket(date); - // text will be converted to UTF8 - literalDataPacket.setText(text, type); - if (filename !== undefined) { - literalDataPacket.setFilename(filename); - } - const literalDataPacketlist = new PacketList(); - literalDataPacketlist.push(literalDataPacket); - const message = new Message(literalDataPacketlist); - message.fromStream = streamType; - return message; - } + const compressed = new packet.Compressed(); + compressed.packets = this.packets; + compressed.algorithm = enums.read(enums.compression, compression); - /** - * creates new message object from binary data - * @param {Uint8Array | ReadableStream} bytes - * @param {String} filename (optional) - * @param {Date} date (optional) - * @param {utf8|binary|text|mime} type (optional) data packet type - * @returns {module:message.Message} new message object - * @static - */ - static fromBinary(bytes, filename, date = new Date(), type = 'binary') { - const streamType = util.isStream(bytes); - if (!util.isUint8Array(bytes) && !streamType) { - throw new Error('Data must be in the form of a Uint8Array or Stream'); - } - if (streamType === 'node') { - bytes = stream.nodeToWeb(bytes); - } + const packetList = new packet.List(); + packetList.push(compressed); - const literalDataPacket = new LiteralDataPacket(date); - literalDataPacket.setBytes(bytes, type); - if (filename !== undefined) { - literalDataPacket.setFilename(filename); - } - const literalDataPacketlist = new PacketList(); - literalDataPacketlist.push(literalDataPacket); - const message = new Message(literalDataPacketlist); - message.fromStream = streamType; - return message; + return new Message(packetList); +}; + +/** + * Create a detached signature for the message (the literal data packet of the message) + * @param {Array} privateKeys private keys with decrypted secret key data for signing + * @param {Signature} signature (optional) any existing detached signature + * @param {Date} date (optional) override the creation time of the signature + * @param {Array} userIds (optional) user IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }] + * @param {Boolean} streaming (optional) whether to process data as a stream + * @returns {Promise} new detached signature of message content + * @async + */ +Message.prototype.signDetached = async function(privateKeys = [], signature = null, date = new Date(), userIds = [], streaming = false) { + const literalDataPacket = this.packets.findPacket(enums.packet.literal); + if (!literalDataPacket) { + throw new Error('No literal data packet to sign.'); } -} + return new Signature(await createSignaturePackets(literalDataPacket, privateKeys, signature, date, userIds, true, streaming)); +}; /** * Create signature packets for the message - * @param {LiteralDataPacket} literalDataPacket the literal data packet to sign + * @param {module:packet.Literal} literalDataPacket the literal data packet to sign * @param {Array} privateKeys private keys with decrypted secret key data for signing * @param {Signature} signature (optional) any existing detached signature to append * @param {Date} date (optional) override the creationtime of the signature * @param {Array} userIds (optional) user IDs to sign with, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }] * @param {Boolean} detached (optional) whether to create detached signature packets * @param {Boolean} streaming (optional) whether to process data as a stream - * @returns {Promise} list of signature packets + * @returns {Promise} list of signature packets * @async */ export async function createSignaturePackets(literalDataPacket, privateKeys, signature = null, date = new Date(), userIds = [], detached = false, streaming = false) { - const packetlist = new PacketList(); + const packetlist = new packet.List(); // If data packet was created from Uint8Array, use binary, otherwise use text const signatureType = literalDataPacket.text === null ? @@ -721,16 +550,86 @@ export async function createSignaturePackets(literalDataPacket, privateKeys, sig return packetlist; } +/** + * Verify message signatures + * @param {Array} keys array of keys to verify signatures + * @param {Date} date (optional) Verify the signature against the given date, i.e. check signature creation time < date < expiration time + * @param {Boolean} streaming (optional) whether to process data as a stream + * @returns {Promise>} list of signer's keyid and validity of signature + * @async + */ +Message.prototype.verify = async function(keys, date = new Date(), streaming) { + const msg = this.unwrapCompressed(); + const literalDataList = msg.packets.filterByTag(enums.packet.literal); + if (literalDataList.length !== 1) { + throw new Error('Can only verify message with one literal data packet.'); + } + if (!streaming) { + msg.packets.concat(await stream.readToEnd(msg.packets.stream, _ => _)); + } + const onePassSigList = msg.packets.filterByTag(enums.packet.onePassSignature).reverse(); + const signatureList = msg.packets.filterByTag(enums.packet.signature); + if (streaming && onePassSigList.length && !signatureList.length && msg.packets.stream) { + await Promise.all(onePassSigList.map(async onePassSig => { + onePassSig.correspondingSig = new Promise((resolve, reject) => { + onePassSig.correspondingSigResolve = resolve; + onePassSig.correspondingSigReject = reject; + }); + onePassSig.signatureData = stream.fromAsync(async () => (await onePassSig.correspondingSig).signatureData); + onePassSig.hashed = stream.readToEnd(await onePassSig.hash(onePassSig.signatureType, literalDataList[0], undefined, false, streaming)); + onePassSig.hashed.catch(() => {}); + })); + msg.packets.stream = stream.transformPair(msg.packets.stream, async (readable, writable) => { + const reader = stream.getReader(readable); + const writer = stream.getWriter(writable); + try { + for (let i = 0; i < onePassSigList.length; i++) { + const { value: signature } = await reader.read(); + onePassSigList[i].correspondingSigResolve(signature); + } + await reader.readToEnd(); + await writer.ready; + await writer.close(); + } catch (e) { + onePassSigList.forEach(onePassSig => { + onePassSig.correspondingSigReject(e); + }); + await writer.abort(e); + } + }); + return createVerificationObjects(onePassSigList, literalDataList, keys, date, false, streaming); + } + return createVerificationObjects(signatureList, literalDataList, keys, date, false, streaming); +}; + +/** + * Verify detached message signature + * @param {Array} keys array of keys to verify signatures + * @param {Signature} signature + * @param {Date} date Verify the signature against the given date, i.e. check signature creation time < date < expiration time + * @returns {Promise>} list of signer's keyid and validity of signature + * @async + */ +Message.prototype.verifyDetached = function(signature, keys, date = new Date()) { + const msg = this.unwrapCompressed(); + const literalDataList = msg.packets.filterByTag(enums.packet.literal); + if (literalDataList.length !== 1) { + throw new Error('Can only verify message with one literal data packet.'); + } + const signatureList = signature.packets; + return createVerificationObjects(signatureList, literalDataList, keys, date, true); +}; + /** * Create object containing signer's keyid and validity of signature - * @param {SignaturePacket} signature signature packets - * @param {Array} literalDataList array of literal data packets + * @param {module:packet.Signature} signature signature packets + * @param {Array} literalDataList array of literal data packets * @param {Array} keys array of keys to verify signatures * @param {Date} date Verify the signature against the given date, * i.e. check signature creation time < date < expiration time * @param {Boolean} detached (optional) whether to verify detached signature packets * @returns {Promise>} list of signer's keyid and validity of signature + * valid: Boolean}>>} list of signer's keyid and validity of signature * @async */ async function createVerificationObject(signature, literalDataList, keys, date = new Date(), detached = false, streaming = false) { @@ -751,7 +650,7 @@ async function createVerificationObject(signature, literalDataList, keys, date = if (!signingKey) { return null; } - await signature.verify(signingKey.keyPacket, signature.signatureType, literalDataList[0], detached, streaming); + const verified = await signature.verify(signingKey.keyPacket, signature.signatureType, literalDataList[0], detached, streaming); const sig = await signaturePacket; if (sig.isExpired(date) || !( sig.created >= signingKey.getCreationTime() && @@ -762,11 +661,11 @@ async function createVerificationObject(signature, literalDataList, keys, date = )) { throw new Error('Signature is expired'); } - return true; + return verified; })(), signature: (async () => { const sig = await signaturePacket; - const packetlist = new PacketList(); + const packetlist = new packet.List(); packetlist.push(sig); return new Signature(packetlist); })() @@ -784,8 +683,8 @@ async function createVerificationObject(signature, literalDataList, keys, date = /** * Create list of objects containing signer's keyid and validity of signature - * @param {Array} signatureList array of signature packets - * @param {Array} literalDataList array of literal data packets + * @param {Array} signatureList array of signature packets + * @param {Array} literalDataList array of literal data packets * @param {Array} keys array of keys to verify signatures * @param {Date} date Verify the signature against the given date, * i.e. check signature creation time < date < expiration time @@ -802,6 +701,34 @@ export async function createVerificationObjects(signatureList, literalDataList, })); } +/** + * Unwrap compressed message + * @returns {module:message.Message} message Content of compressed message + */ +Message.prototype.unwrapCompressed = function() { + const compressed = this.packets.filterByTag(enums.packet.compressed); + if (compressed.length) { + return new Message(compressed[0].packets); + } + return this; +}; + +/** + * Append signature to unencrypted message object + * @param {String|Uint8Array} detachedSignature The detached ASCII-armored or Uint8Array PGP signature + */ +Message.prototype.appendSignature = async function(detachedSignature) { + await this.packets.read(util.isUint8Array(detachedSignature) ? detachedSignature : (await armor.decode(detachedSignature)).data); +}; + +/** + * Returns ASCII armored text of message + * @returns {ReadableStream} ASCII armor + */ +Message.prototype.armor = function() { + return armor.encode(enums.armor.message, this.packets.write()); +}; + /** * reads an OpenPGP armored message and returns a message object * @param {String | ReadableStream} armoredText text to be parsed @@ -809,15 +736,15 @@ export async function createVerificationObjects(signatureList, literalDataList, * @async * @static */ -export async function readArmoredMessage(armoredText) { +export async function readArmored(armoredText) { //TODO how do we want to handle bad text? Exception throwing //TODO don't accept non-message armored texts const streamType = util.isStream(armoredText); if (streamType === 'node') { armoredText = stream.nodeToWeb(armoredText); } - const input = await unarmor(armoredText); - return readMessage(input.data, streamType); + const input = await armor.decode(armoredText); + return read(input.data, streamType); } /** @@ -828,24 +755,71 @@ export async function readArmoredMessage(armoredText) { * @async * @static */ -export async function readMessage(input, fromStream = util.isStream(input)) { +export async function read(input, fromStream = util.isStream(input)) { const streamType = util.isStream(input); if (streamType === 'node') { input = stream.nodeToWeb(input); } - const packetlist = new PacketList(); - await packetlist.read(input, { - LiteralDataPacket, - CompressedDataPacket, - AEADEncryptedDataPacket, - SymEncryptedIntegrityProtectedDataPacket, - SymmetricallyEncryptedDataPacket, - PublicKeyEncryptedSessionKeyPacket, - SymEncryptedSessionKeyPacket, - OnePassSignaturePacket, - SignaturePacket - }, fromStream); + const packetlist = new packet.List(); + await packetlist.read(input, fromStream); const message = new Message(packetlist); message.fromStream = fromStream; return message; } + +/** + * creates new message object from text + * @param {String | ReadableStream} text + * @param {String} filename (optional) + * @param {Date} date (optional) + * @param {utf8|binary|text|mime} type (optional) data packet type + * @returns {module:message.Message} new message object + * @static + */ +export function fromText(text, filename, date = new Date(), type = 'utf8') { + const streamType = util.isStream(text); + if (streamType === 'node') { + text = stream.nodeToWeb(text); + } + const literalDataPacket = new packet.Literal(date); + // text will be converted to UTF8 + literalDataPacket.setText(text, type); + if (filename !== undefined) { + literalDataPacket.setFilename(filename); + } + const literalDataPacketlist = new packet.List(); + literalDataPacketlist.push(literalDataPacket); + const message = new Message(literalDataPacketlist); + message.fromStream = streamType; + return message; +} + +/** + * creates new message object from binary data + * @param {Uint8Array | ReadableStream} bytes + * @param {String} filename (optional) + * @param {Date} date (optional) + * @param {utf8|binary|text|mime} type (optional) data packet type + * @returns {module:message.Message} new message object + * @static + */ +export function fromBinary(bytes, filename, date = new Date(), type = 'binary') { + const streamType = util.isStream(bytes); + if (!util.isUint8Array(bytes) && !streamType) { + throw new Error('Data must be in the form of a Uint8Array or Stream'); + } + if (streamType === 'node') { + bytes = stream.nodeToWeb(bytes); + } + + const literalDataPacket = new packet.Literal(date); + literalDataPacket.setBytes(bytes, type); + if (filename !== undefined) { + literalDataPacket.setFilename(filename); + } + const literalDataPacketlist = new packet.List(); + literalDataPacketlist.push(literalDataPacket); + const message = new Message(literalDataPacketlist); + message.fromStream = streamType; + return message; +} diff --git a/src/openpgp.js b/src/openpgp.js index a6777114..2d303eb0 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -27,6 +27,7 @@ * @requires enums * @requires util * @requires polyfills + * @requires worker/async_proxy * @module openpgp */ @@ -39,21 +40,65 @@ */ import stream from 'web-stream-tools'; -import { createReadableStreamWrapper } from '@mattiasbuelens/web-streams-adapter'; -import { Message } from './message'; +import * as messageLib from './message'; import { CleartextMessage } from './cleartext'; import { generate, reformat } from './key'; import config from './config/config'; +import enums from './enums'; import './polyfills'; import util from './util'; +import AsyncProxy from './worker/async_proxy'; -let toNativeReadable; -if (globalThis.ReadableStream) { - try { - toNativeReadable = createReadableStreamWrapper(globalThis.ReadableStream); - } catch (e) {} +////////////////////////// +// // +// Web Worker setup // +// // +////////////////////////// + + +let asyncProxy; // instance of the asyncproxy + +/** + * Set the path for the web worker script and create an instance of the async proxy + * @param {String} path relative path to the worker scripts, default: 'openpgp.worker.js' + * @param {Number} n number of workers to initialize + * @param {Array} workers alternative to path parameter: web workers initialized with 'openpgp.worker.js' + * @returns {Promise} returns a promise that resolves to true if all workers have succesfully finished loading + * @async + */ +export async function initWorker({ path = 'openpgp.worker.js', n = 1, workers = [] } = {}) { + if (workers.length || (typeof global !== 'undefined' && global.Worker && global.MessageChannel)) { + const proxy = new AsyncProxy({ path, n, workers, config }); + const loaded = await proxy.loaded(); + if (loaded) { + asyncProxy = proxy; + return true; + } + } + return false; } +/** + * Returns a reference to the async proxy if the worker was initialized with openpgp.initWorker() + * @returns {module:worker/async_proxy.AsyncProxy|null} the async proxy or null if not initialized + */ +export function getWorker() { + return asyncProxy; +} + +/** + * Cleanup the current instance of the web worker. + */ +export async function destroyWorker() { + const proxy = asyncProxy; + asyncProxy = undefined; + if (proxy) { + await proxy.clearKeyCache(); + proxy.terminate(); + } +} + + ////////////////////// // // // Key handling // @@ -62,51 +107,55 @@ if (globalThis.ReadableStream) { /** - * Generates a new OpenPGP key pair. Supports RSA and ECC keys. By default, primary and subkeys will be of same type. - * @param {ecc|rsa} type (optional) The primary key algorithm type: ECC (default) or RSA - * @param {Object|Array} userIds User IDs as objects: { name:'Jo Doe', email:'info@jo.com' } - * @param {String} passphrase (optional) The passphrase used to encrypt the resulting private key - * @param {Number} rsaBits (optional) Number of bits for RSA keys, defaults to 4096 - * @param {String} curve (optional) Elliptic curve for ECC keys: - * curve25519 (default), p256, p384, p521, secp256k1, - * brainpoolP256r1, brainpoolP384r1, or brainpoolP512r1 - * @param {Date} date (optional) Override the creation date of the key and the key signatures - * @param {Number} keyExpirationTime (optional) Number of seconds from the key creation time after which the key expires - * @param {Array} subkeys (optional) Options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}] - * sign parameter defaults to false, and indicates whether the subkey should sign rather than encrypt + * Generates a new OpenPGP key pair. Supports RSA and ECC keys. Primary and subkey will be of same type. + * @param {Array} userIds array of user IDs e.g. [{ name:'Phil Zimmermann', email:'phil@openpgp.org' }] + * @param {String} passphrase (optional) The passphrase used to encrypt the resulting private key + * @param {Number} rsaBits (optional) number of bits for RSA keys: 2048 or 4096. + * @param {Number} keyExpirationTime (optional) The number of seconds after the key creation time that the key expires + * @param {String} curve (optional) elliptic curve for ECC keys: + * curve25519, p256, p384, p521, secp256k1, + * brainpoolP256r1, brainpoolP384r1, or brainpoolP512r1. + * @param {Date} date (optional) override the creation date of the key and the key signatures + * @param {Array} subkeys (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}] + * sign parameter defaults to false, and indicates whether the subkey should sign rather than encrypt * @returns {Promise} The generated key object in the form: * { key:Key, privateKeyArmored:String, publicKeyArmored:String, revocationCertificate:String } * @async * @static */ -export function generateKey({ userIds = [], passphrase = "", type = "ecc", rsaBits = 4096, curve = "curve25519", keyExpirationTime = 0, date = new Date(), subkeys = [{}] }) { + +export function generateKey({ userIds = [], passphrase = "", numBits = 2048, rsaBits = numBits, keyExpirationTime = 0, curve = "", date = new Date(), subkeys = [{}] }) { userIds = toArray(userIds); - const options = { userIds, passphrase, type, rsaBits, curve, keyExpirationTime, date, subkeys }; - if (type === "rsa" && rsaBits < config.minRsaBits) { - throw new Error(`rsaBits should be at least ${config.minRsaBits}, got: ${rsaBits}`); + const options = { userIds, passphrase, rsaBits, keyExpirationTime, curve, date, subkeys }; + if (util.getWebCryptoAll() && rsaBits < 2048) { + throw new Error('rsaBits should be 2048 or 4096, found: ' + rsaBits); + } + + if (!util.getWebCryptoAll() && asyncProxy) { // use web worker if web crypto apis are not supported + return asyncProxy.delegate('generateKey', options); } return generate(options).then(async key => { const revocationCertificate = await key.getRevocationCertificate(date); key.revocationSignatures = []; - return { + return convertStreams({ key: key, privateKeyArmored: key.armor(), publicKeyArmored: key.toPublic().armor(), revocationCertificate: revocationCertificate - }; + }); }).catch(onError.bind(null, 'Error generating keypair')); } /** * Reformats signature packets for a key and rewraps key object. - * @param {Key} privateKey Private key to reformat - * @param {Object|Array} userIds User IDs as objects: { name:'Jo Doe', email:'info@jo.com' } - * @param {String} passphrase (optional) The passphrase used to encrypt the resulting private key - * @param {Number} keyExpirationTime (optional) Number of seconds from the key creation time after which the key expires + * @param {Key} privateKey private key to reformat + * @param {Array} userIds array of user IDs e.g. [{ name:'Phil Zimmermann', email:'phil@openpgp.org' }] + * @param {String} passphrase (optional) The passphrase used to encrypt the resulting private key + * @param {Number} keyExpirationTime (optional) The number of seconds after the key creation time that the key expires * @returns {Promise} The generated key object in the form: * { key:Key, privateKeyArmored:String, publicKeyArmored:String, revocationCertificate:String } * @async @@ -115,19 +164,22 @@ export function generateKey({ userIds = [], passphrase = "", type = "ecc", rsaBi export function reformatKey({ privateKey, userIds = [], passphrase = "", keyExpirationTime = 0, date }) { userIds = toArray(userIds); const options = { privateKey, userIds, passphrase, keyExpirationTime, date }; + if (asyncProxy) { + return asyncProxy.delegate('reformatKey', options); + } return reformat(options).then(async key => { const revocationCertificate = await key.getRevocationCertificate(date); key.revocationSignatures = []; - return { + return convertStreams({ key: key, privateKeyArmored: key.armor(), publicKeyArmored: key.toPublic().armor(), revocationCertificate: revocationCertificate - }; + }); }).catch(onError.bind(null, 'Error reformatting keypair')); } @@ -147,6 +199,14 @@ export function reformatKey({ privateKey, userIds = [], passphrase = "", keyExpi export function revokeKey({ key, revocationCertificate, reasonForRevocation } = {}) { + const options = { + key, revocationCertificate, reasonForRevocation + }; + + if (!util.getWebCryptoAll() && asyncProxy) { // use web worker if web crypto apis are not supported + return asyncProxy.delegate('revokeKey', options); + } + return Promise.resolve().then(() => { if (revocationCertificate) { return key.applyRevocationCertificate(revocationCertificate); @@ -154,6 +214,7 @@ export function revokeKey({ return key.revoke(reasonForRevocation); } }).then(async key => { + await convertStreams(key); if (key.isPrivate()) { const publicKey = key.toPublic(); return { @@ -171,62 +232,45 @@ export function revokeKey({ } /** - * Unlock a private key with the given passphrase. - * This method does not change the original key. - * @param {Key} privateKey the private key to decrypt - * @param {String|Array} passphrase the user's passphrase(s) - * @returns {Promise} the unlocked key object + * Unlock a private key with your passphrase. + * @param {Key} privateKey the private key that is to be decrypted + * @param {String|Array} passphrase the user's passphrase(s) chosen during key generation + * @returns {Promise} the unlocked key object in the form: { key:Key } * @async */ -export async function decryptKey({ privateKey, passphrase }) { - const key = await privateKey.clone(); - // shallow clone is enough since the encrypted material is not changed in place by decryption - key.getKeys().forEach(k => { - k.keyPacket = Object.create( - Object.getPrototypeOf(k.keyPacket), - Object.getOwnPropertyDescriptors(k.keyPacket) - ); - }); - try { - await key.decrypt(passphrase); - return key; - } catch (err) { - key.clearPrivateParams(); - return onError('Error decrypting private key', err); +export function decryptKey({ privateKey, passphrase }) { + if (asyncProxy) { // use web worker if available + return asyncProxy.delegate('decryptKey', { privateKey, passphrase }); } + + return Promise.resolve().then(async function() { + await privateKey.decrypt(passphrase); + + return { + key: privateKey + }; + }).catch(onError.bind(null, 'Error decrypting private key')); } /** - * Lock a private key with the given passphrase. - * This method does not change the original key. - * @param {Key} privateKey the private key to encrypt - * @param {String|Array} passphrase if multiple passphrases, they should be in the same order as the packets each should encrypt - * @returns {Promise} the locked key object + * Lock a private key with your passphrase. + * @param {Key} privateKey the private key that is to be decrypted + * @param {String|Array} passphrase the user's passphrase(s) chosen during key generation + * @returns {Promise} the locked key object in the form: { key:Key } * @async */ -export async function encryptKey({ privateKey, passphrase }) { - const key = await privateKey.clone(); - key.getKeys().forEach(k => { - // shallow clone the key packets - k.keyPacket = Object.create( - Object.getPrototypeOf(k.keyPacket), - Object.getOwnPropertyDescriptors(k.keyPacket) - ); - if (!k.keyPacket.isDecrypted()) return; - // deep clone the private params, which are cleared during encryption - const privateParams = {}; - Object.keys(k.keyPacket.privateParams).forEach(name => { - privateParams[name] = new Uint8Array(k.keyPacket.privateParams[name]); - }); - k.keyPacket.privateParams = privateParams; - }); - try { - await key.encrypt(passphrase); - return key; - } catch (err) { - key.clearPrivateParams(); - return onError('Error encrypting private key', err); +export function encryptKey({ privateKey, passphrase }) { + if (asyncProxy) { // use web worker if available + return asyncProxy.delegate('encryptKey', { privateKey, passphrase }); } + + return Promise.resolve().then(async function() { + await privateKey.encrypt(passphrase); + + return { + key: privateKey + }; + }).catch(onError.bind(null, 'Error decrypting private key')); } @@ -240,56 +284,81 @@ export async function encryptKey({ privateKey, passphrase }) { /** * Encrypts message text/data with public keys, passwords or both at once. At least either public keys or passwords * must be specified. If private keys are specified, those will be used to sign the message. - * @param {Message} message message to be encrypted as created by openpgp.Message.fromText or openpgp.Message.fromBinary - * @param {Key|Array} publicKeys (optional) array of keys or single key, used to encrypt the message - * @param {Key|Array} privateKeys (optional) private keys for signing. If omitted message will not be signed - * @param {String|Array} passwords (optional) array of passwords or a single password to encrypt the message - * @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String } - * @param {module:enums.compression} compression (optional) which compression algorithm to compress the message with, defaults to what is specified in config - * @param {Boolean} armor (optional) whether the return values should be ascii armored (true, the default) or binary (false) - * @param {'web'|'ponyfill'|'node'|false} streaming (optional) whether to return data as a stream. Defaults to the type of stream `message` was created from, if any. - * @param {Signature} signature (optional) a detached signature to add to the encrypted message - * @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs - * @param {Date} date (optional) override the creation date of the message signature - * @param {Array} fromUserIds (optional) array of user IDs to sign with, one per key in `privateKeys`, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }] - * @param {Array} toUserIds (optional) array of user IDs to encrypt for, one per key in `publicKeys`, e.g. [{ name:'Robert Receiver', email:'robert@openpgp.org' }] - * @returns {Promise|NodeStream|Uint8Array|ReadableStream|NodeStream>} (String if `armor` was true, the default; Uint8Array if `armor` was false) + * @param {Message} message message to be encrypted as created by openpgp.message.fromText or openpgp.message.fromBinary + * @param {Key|Array} publicKeys (optional) array of keys or single key, used to encrypt the message + * @param {Key|Array} privateKeys (optional) private keys for signing. If omitted message will not be signed + * @param {String|Array} passwords (optional) array of passwords or a single password to encrypt the message + * @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String } + * @param {module:enums.compression} compression (optional) which compression algorithm to compress the message with, defaults to what is specified in config + * @param {Boolean} armor (optional) if the return values should be ascii armored or the message/signature objects + * @param {'web'|'node'|false} streaming (optional) whether to return data as a stream. Defaults to the type of stream `message` was created from, if any. + * @param {Boolean} detached (optional) if the signature should be detached (if true, signature will be added to returned object) + * @param {Signature} signature (optional) a detached signature to add to the encrypted message + * @param {Boolean} returnSessionKey (optional) if the unencrypted session key should be added to returned object + * @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs + * @param {Date} date (optional) override the creation date of the message signature + * @param {Array} fromUserIds (optional) array of user IDs to sign with, one per key in `privateKeys`, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }] + * @param {Array} toUserIds (optional) array of user IDs to encrypt for, one per key in `publicKeys`, e.g. [{ name:'Robert Receiver', email:'robert@openpgp.org' }] + * @returns {Promise} Object containing encrypted (and optionally signed) message in the form: + * + * { + * data: String|ReadableStream|NodeStream, (if `armor` was true, the default) + * message: Message, (if `armor` was false) + * signature: String|ReadableStream|NodeStream, (if `detached` was true and `armor` was true) + * signature: Signature (if `detached` was true and `armor` was false) + * sessionKey: { data, algorithm, aeadAlgorithm } (if `returnSessionKey` was true) + * } * @async * @static */ -export function encrypt({ message, publicKeys, privateKeys, passwords, sessionKey, compression = config.compression, armor = true, streaming = message && message.fromStream, detached = false, signature = null, wildcard = false, date = new Date(), fromUserIds = [], toUserIds = [] }) { +export function encrypt({ message, publicKeys, privateKeys, passwords, sessionKey, compression = config.compression, armor = true, streaming = message && message.fromStream, detached = false, signature = null, returnSessionKey = false, wildcard = false, date = new Date(), fromUserIds = [], toUserIds = [] }) { checkMessage(message); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords); fromUserIds = toArray(fromUserIds); toUserIds = toArray(toUserIds); - if (detached) { - throw new Error("detached option has been removed from openpgp.encrypt. Separately call openpgp.sign instead. Don't forget to remove privateKeys option as well."); - } + if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported + return asyncProxy.delegate('encrypt', { message, publicKeys, privateKeys, passwords, sessionKey, compression, armor, streaming, detached, signature, returnSessionKey, wildcard, date, fromUserIds, toUserIds }); + } + const result = {}; return Promise.resolve().then(async function() { if (!privateKeys) { privateKeys = []; } if (privateKeys.length || signature) { // sign the message only if private keys or signature is specified - message = await message.sign(privateKeys, signature, date, fromUserIds, message.fromStream); + if (detached) { + const detachedSignature = await message.signDetached(privateKeys, signature, date, fromUserIds, message.fromStream); + result.signature = armor ? detachedSignature.armor() : detachedSignature; + } else { + message = await message.sign(privateKeys, signature, date, fromUserIds, message.fromStream); + } } message = message.compress(compression); - message = await message.encrypt(publicKeys, passwords, sessionKey, wildcard, date, toUserIds, streaming); - const data = armor ? message.armor() : message.write(); - return convertStream(data, streaming, armor ? 'utf8' : 'binary'); + return message.encrypt(publicKeys, passwords, sessionKey, wildcard, date, toUserIds, streaming); + + }).then(async encrypted => { + if (armor) { + result.data = encrypted.message.armor(); + } else { + result.message = encrypted.message; + } + if (returnSessionKey) { + result.sessionKey = encrypted.sessionKey; + } + return convertStreams(result, streaming, armor ? ['signature', 'data'] : []); }).catch(onError.bind(null, 'Error encrypting message')); } /** * Decrypts a message with the user's private key, a session key or a password. Either a private key, * a session key or a password must be specified. - * @param {Message} message the message object with the encrypted data - * @param {Key|Array} privateKeys (optional) private keys with decrypted secret key data or session key - * @param {String|Array} passwords (optional) passwords to decrypt the message - * @param {Object|Array} sessionKeys (optional) session keys in the form: { data:Uint8Array, algorithm:String } - * @param {Key|Array} publicKeys (optional) array of public keys or single key, to verify signatures - * @param {'utf8'|'binary'} format (optional) whether to return data as a string(Stream) or Uint8Array(Stream). If 'utf8' (the default), also normalize newlines. - * @param {'web'|'ponyfill'|'node'|false} streaming (optional) whether to return data as a stream. Defaults to the type of stream `message` was created from, if any. - * @param {Signature} signature (optional) detached signature for verification - * @param {Date} date (optional) use the given date for verification instead of the current time - * @returns {Promise} Object containing decrypted and verified message in the form: + * @param {Message} message the message object with the encrypted data + * @param {Key|Array} privateKeys (optional) private keys with decrypted secret key data or session key + * @param {String|Array} passwords (optional) passwords to decrypt the message + * @param {Object|Array} sessionKeys (optional) session keys in the form: { data:Uint8Array, algorithm:String } + * @param {Key|Array} publicKeys (optional) array of public keys or single key, to verify signatures + * @param {'utf8'|'binary'} format (optional) whether to return data as a string(Stream) or Uint8Array(Stream). If 'utf8' (the default), also normalize newlines. + * @param {'web'|'node'|false} streaming (optional) whether to return data as a stream. Defaults to the type of stream `message` was created from, if any. + * @param {Signature} signature (optional) detached signature for verification + * @param {Date} date (optional) use the given date for verification instead of the current time + * @returns {Promise} Object containing decrypted and verified message in the form: * * { * data: String|ReadableStream|NodeStream, (if format was 'utf8', the default) @@ -309,6 +378,10 @@ export function encrypt({ message, publicKeys, privateKeys, passwords, sessionKe export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKeys, format = 'utf8', streaming = message && message.fromStream, signature = null, date = new Date() }) { checkMessage(message); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords); sessionKeys = toArray(sessionKeys); + if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported + return asyncProxy.delegate('decrypt', { message, privateKeys, passwords, sessionKeys, publicKeys, format, streaming, signature, date }); + } + return message.decrypt(privateKeys, passwords, sessionKeys, streaming).then(async function(decrypted) { if (!publicKeys) { publicKeys = []; @@ -318,8 +391,8 @@ export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKe result.signatures = signature ? await decrypted.verifyDetached(signature, publicKeys, date, streaming) : await decrypted.verify(publicKeys, date, streaming); result.data = format === 'binary' ? decrypted.getLiteralData() : decrypted.getText(); result.filename = decrypted.getFilename(); - linkStreams(result, message); - result.data = await convertStream(result.data, streaming, format); + if (streaming) linkStreams(result, message); + result.data = await convertStream(result.data, streaming); if (!streaming) await prepareSignatures(result.signatures); return result; }).catch(onError.bind(null, 'Error decrypting message')); @@ -334,53 +407,72 @@ export function decrypt({ message, privateKeys, passwords, sessionKeys, publicKe /** - * Signs a message. - * @param {CleartextMessage|Message} message (cleartext) message to be signed - * @param {Key|Array} privateKeys array of keys or single key with decrypted secret key data to sign cleartext - * @param {Boolean} armor (optional) whether the return values should be ascii armored (true, the default) or binary (false) - * @param {'web'|'ponyfill'|'node'|false} streaming (optional) whether to return data as a stream. Defaults to the type of stream `message` was created from, if any. - * @param {Boolean} detached (optional) if the return value should contain a detached signature - * @param {Date} date (optional) override the creation date of the signature - * @param {Array} fromUserIds (optional) array of user IDs to sign with, one per key in `privateKeys`, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }] - * @returns {Promise|NodeStream|Uint8Array|ReadableStream|NodeStream>} (String if `armor` was true, the default; Uint8Array if `armor` was false) + * Signs a cleartext message. + * @param {CleartextMessage|Message} message (cleartext) message to be signed + * @param {Key|Array} privateKeys array of keys or single key with decrypted secret key data to sign cleartext + * @param {Boolean} armor (optional) if the return value should be ascii armored or the message object + * @param {'web'|'node'|false} streaming (optional) whether to return data as a stream. Defaults to the type of stream `message` was created from, if any. + * @param {Boolean} detached (optional) if the return value should contain a detached signature + * @param {Date} date (optional) override the creation date of the signature + * @param {Array} fromUserIds (optional) array of user IDs to sign with, one per key in `privateKeys`, e.g. [{ name:'Steve Sender', email:'steve@openpgp.org' }] + * @returns {Promise} Object containing signed message in the form: + * + * { + * data: String|ReadableStream|NodeStream, (if `armor` was true, the default) + * message: Message (if `armor` was false) + * } + * + * Or, if `detached` was true: + * + * { + * signature: String|ReadableStream|NodeStream, (if `armor` was true, the default) + * signature: Signature (if `armor` was false) + * } * @async * @static */ export function sign({ message, privateKeys, armor = true, streaming = message && message.fromStream, detached = false, date = new Date(), fromUserIds = [] }) { checkCleartextOrMessage(message); - if (message instanceof CleartextMessage && !armor) throw new Error("Can't sign non-armored cleartext message"); - if (message instanceof CleartextMessage && detached) throw new Error("Can't sign detached cleartext message"); privateKeys = toArray(privateKeys); fromUserIds = toArray(fromUserIds); + if (asyncProxy) { // use web worker if available + return asyncProxy.delegate('sign', { + message, privateKeys, armor, streaming, detached, date, fromUserIds + }); + } + const result = {}; return Promise.resolve().then(async function() { - let signature; if (detached) { - signature = await message.signDetached(privateKeys, undefined, date, fromUserIds, message.fromStream); + const signature = await message.signDetached(privateKeys, undefined, date, fromUserIds, message.fromStream); + result.signature = armor ? signature.armor() : signature; + if (message.packets) { + result.signature = stream.transformPair(message.packets.write(), async (readable, writable) => { + await Promise.all([ + stream.pipe(result.signature, writable), + stream.readToEnd(readable).catch(() => {}) + ]); + }); + } } else { - signature = await message.sign(privateKeys, undefined, date, fromUserIds, message.fromStream); + message = await message.sign(privateKeys, undefined, date, fromUserIds, message.fromStream); + if (armor) { + result.data = message.armor(); + } else { + result.message = message; + } } - signature = armor ? signature.armor() : signature.write(); - if (detached) { - signature = stream.transformPair(message.packets.write(), async (readable, writable) => { - await Promise.all([ - stream.pipe(signature, writable), - stream.readToEnd(readable).catch(() => {}) - ]); - }); - } - return convertStream(signature, streaming, armor ? 'utf8' : 'binary'); - }).catch(onError.bind(null, 'Error signing message')); + return convertStreams(result, streaming, armor ? ['signature', 'data'] : []); + }).catch(onError.bind(null, 'Error signing cleartext message')); } /** * Verifies signatures of cleartext signed message - * @param {Key|Array} publicKeys array of publicKeys or single key, to verify signatures - * @param {CleartextMessage|Message} message (cleartext) message object with signatures - * @param {'utf8'|'binary'} format (optional) whether to return data as a string(Stream) or Uint8Array(Stream). If 'utf8' (the default), also normalize newlines. - * @param {'web'|'ponyfill'|'node'|false} streaming (optional) whether to return data as a stream. Defaults to the type of stream `message` was created from, if any. - * @param {Signature} signature (optional) detached signature for verification - * @param {Date} date (optional) use the given date for verification instead of the current time - * @returns {Promise} Object containing verified message in the form: + * @param {Key|Array} publicKeys array of publicKeys or single key, to verify signatures + * @param {CleartextMessage|Message} message (cleartext) message object with signatures + * @param {'web'|'node'|false} streaming (optional) whether to return data as a stream. Defaults to the type of stream `message` was created from, if any. + * @param {Signature} signature (optional) detached signature for verification + * @param {Date} date (optional) use the given date for verification instead of the current time + * @returns {Promise} Object containing verified message in the form: * * { * data: String|ReadableStream|NodeStream, (if `message` was a CleartextMessage) @@ -396,20 +488,23 @@ export function sign({ message, privateKeys, armor = true, streaming = message & * @async * @static */ -export function verify({ message, publicKeys, format = 'utf8', streaming = message && message.fromStream, signature = null, date = new Date() }) { +export function verify({ message, publicKeys, streaming = message && message.fromStream, signature = null, date = new Date() }) { checkCleartextOrMessage(message); - if (message instanceof CleartextMessage && format === 'binary') throw new Error("Can't return cleartext message data as binary"); publicKeys = toArray(publicKeys); + if (asyncProxy) { // use web worker if available + return asyncProxy.delegate('verify', { message, publicKeys, streaming, signature, date }); + } + return Promise.resolve().then(async function() { const result = {}; result.signatures = signature ? await message.verifyDetached(signature, publicKeys, date, streaming) : await message.verify(publicKeys, date, streaming); - result.data = format === 'binary' ? message.getLiteralData() : message.getText(); + result.data = message instanceof CleartextMessage ? message.getText() : message.getLiteralData(); if (streaming) linkStreams(result, message); - result.data = await convertStream(result.data, streaming, format); + result.data = await convertStream(result.data, streaming); if (!streaming) await prepareSignatures(result.signatures); return result; - }).catch(onError.bind(null, 'Error verifying signed message')); + }).catch(onError.bind(null, 'Error verifying cleartext signed message')); } @@ -419,24 +514,6 @@ export function verify({ message, publicKeys, format = 'utf8', streaming = messa // // /////////////////////////////////////////////// -/** - * Generate a new session key object, taking the algorithm preferences of the passed public keys into account. - * @param {Key|Array} publicKeys array of public keys or single key used to select algorithm preferences for - * @param {Date} date (optional) date to select algorithm preferences at - * @param {Array} toUserIds (optional) user IDs to select algorithm preferences for - * @returns {Promise<{ data: Uint8Array, algorithm: String }>} object with session key data and algorithm - * @async - * @static - */ -export function generateSessionKey({ publicKeys, date = new Date(), toUserIds = [] }) { - publicKeys = toArray(publicKeys); toUserIds = toArray(toUserIds); - - return Promise.resolve().then(async function() { - - return Message.generateSessionKey(publicKeys, date, toUserIds); - - }).catch(onError.bind(null, 'Error generating session key')); -} /** * Encrypt a symmetric session key with public keys, passwords, or both at once. At least either public keys @@ -446,21 +523,23 @@ export function generateSessionKey({ publicKeys, date = new Date(), toUserIds = * @param {String} aeadAlgorithm (optional) aead algorithm, e.g. 'eax' or 'ocb' * @param {Key|Array} publicKeys (optional) array of public keys or single key, used to encrypt the key * @param {String|Array} passwords (optional) passwords for the message - * @param {Boolean} armor (optional) whether the return values should be ascii armored (true, the default) or binary (false) * @param {Boolean} wildcard (optional) use a key ID of 0 instead of the public key IDs * @param {Date} date (optional) override the date * @param {Array} toUserIds (optional) array of user IDs to encrypt for, one per key in `publicKeys`, e.g. [{ name:'Phil Zimmermann', email:'phil@openpgp.org' }] - * @returns {Promise} (String if `armor` was true, the default; Uint8Array if `armor` was false) + * @returns {Promise} the encrypted session key packets contained in a message object * @async * @static */ -export function encryptSessionKey({ data, algorithm, aeadAlgorithm, publicKeys, passwords, armor = true, wildcard = false, date = new Date(), toUserIds = [] }) { +export function encryptSessionKey({ data, algorithm, aeadAlgorithm, publicKeys, passwords, wildcard = false, date = new Date(), toUserIds = [] }) { checkBinary(data); checkString(algorithm, 'algorithm'); publicKeys = toArray(publicKeys); passwords = toArray(passwords); toUserIds = toArray(toUserIds); + if (asyncProxy) { // use web worker if available + return asyncProxy.delegate('encryptSessionKey', { data, algorithm, aeadAlgorithm, publicKeys, passwords, wildcard, date, toUserIds }); + } + return Promise.resolve().then(async function() { - const message = await Message.encryptSessionKey(data, algorithm, aeadAlgorithm, publicKeys, passwords, wildcard, date, toUserIds); - return armor ? message.armor() : message.write(); + return { message: await messageLib.encryptSessionKey(data, algorithm, aeadAlgorithm, publicKeys, passwords, wildcard, date, toUserIds) }; }).catch(onError.bind(null, 'Error encrypting session key')); } @@ -480,6 +559,10 @@ export function encryptSessionKey({ data, algorithm, aeadAlgorithm, publicKeys, export function decryptSessionKeys({ message, privateKeys, passwords }) { checkMessage(message); privateKeys = toArray(privateKeys); passwords = toArray(passwords); + if (asyncProxy) { // use web worker if available + return asyncProxy.delegate('decryptSessionKeys', { message, privateKeys, passwords }); + } + return Promise.resolve().then(async function() { return message.decryptSessionKeys(privateKeys, passwords); @@ -509,12 +592,12 @@ function checkBinary(data, name) { } } function checkMessage(message) { - if (!(message instanceof Message)) { + if (!(message instanceof messageLib.Message)) { throw new Error('Parameter [message] needs to be of type Message'); } } function checkCleartextOrMessage(message) { - if (!(message instanceof CleartextMessage) && !(message instanceof Message)) { + if (!(message instanceof CleartextMessage) && !(message instanceof messageLib.Message)) { throw new Error('Parameter [message] needs to be of type Message or CleartextMessage'); } } @@ -533,51 +616,57 @@ function toArray(param) { /** * Convert data to or from Stream - * @param {Object} data the data to convert - * @param {'web'|'ponyfill'|'node'|false} streaming (optional) whether to return a ReadableStream, and of what type - * @param {'utf8'|'binary'} encoding (optional) how to return data in Node Readable streams - * @returns {Object} the data in the respective format + * @param {Object} data the data to convert + * @param {'web'|'node'|false} streaming (optional) whether to return a ReadableStream + * @returns {Object} the data in the respective format */ -async function convertStream(data, streaming, encoding = 'utf8') { - let streamType = util.isStream(data); - if (!streaming && streamType) { +async function convertStream(data, streaming) { + if (!streaming && util.isStream(data)) { return stream.readToEnd(data); } - if (streaming && !streamType) { - data = stream.toStream(data); - streamType = util.isStream(data); + if (streaming && !util.isStream(data)) { + data = new ReadableStream({ + start(controller) { + controller.enqueue(data); + controller.close(); + } + }); } if (streaming === 'node') { data = stream.webToNode(data); - if (encoding !== 'binary') data.setEncoding(encoding); - return data; - } - if (streaming === 'web' && streamType === 'ponyfill' && toNativeReadable) { - return toNativeReadable(data); } return data; } +/** + * Convert object properties from Stream + * @param {Object} obj the data to convert + * @param {'web'|'node'|false} streaming (optional) whether to return ReadableStreams + * @param {Array} keys (optional) which keys to return as streams, if possible + * @returns {Object} the data in the respective format + */ +async function convertStreams(obj, streaming, keys = []) { + if (Object.prototype.isPrototypeOf(obj) && !Uint8Array.prototype.isPrototypeOf(obj)) { + await Promise.all(Object.entries(obj).map(async ([key, value]) => { // recursively search all children + if (util.isStream(value) || keys.includes(key)) { + obj[key] = await convertStream(value, streaming); + } else { + await convertStreams(obj[key], streaming); + } + })); + } + return obj; +} + /** * Link result.data to the message stream for cancellation. - * Also, forward errors in the message to result.data. * @param {Object} result the data to convert * @param {Message} message message object * @returns {Object} */ function linkStreams(result, message) { result.data = stream.transformPair(message.packets.stream, async (readable, writable) => { - await stream.pipe(result.data, writable, { - preventClose: true - }); - const writer = stream.getWriter(writable); - try { - // Forward errors in the message stream to result.data. - await stream.readToEnd(readable, _ => _); - await writer.close(); - } catch (e) { - await writer.abort(e); - } + await stream.pipe(result.data, writable); }); } @@ -593,7 +682,7 @@ async function prepareSignatures(signatures) { } catch (e) { signature.valid = false; signature.error = e; - util.printDebugError(e); + util.print_debug_error(e); } })); } @@ -606,7 +695,7 @@ async function prepareSignatures(signatures) { */ function onError(message, error) { // log the stack trace - util.printDebugError(error); + util.print_debug_error(error); // update error message try { @@ -615,3 +704,14 @@ function onError(message, error) { throw error; } + +/** + * Check for native AEAD support and configuration by the user. Only + * browsers that implement the current WebCrypto specification support + * native GCM. Native EAX is built on CTR and CBC, which current + * browsers support. OCB and CFB are not natively supported. + * @returns {Boolean} If authenticated encryption should be used + */ +function nativeAEAD() { + return config.aead_protect && (config.aead_mode === enums.aead.eax || config.aead_mode === enums.aead.experimental_gcm) && util.getWebCrypto(); +} diff --git a/src/packet/aead_encrypted_data.js b/src/packet/aead_encrypted_data.js deleted file mode 100644 index 4bcb785b..00000000 --- a/src/packet/aead_encrypted_data.js +++ /dev/null @@ -1,202 +0,0 @@ -// 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 - -/** - * @requires web-stream-tools - * @requires config - * @requires crypto - * @requires enums - * @requires util - * @requires packet - */ - -import stream from 'web-stream-tools'; -import config from '../config'; -import crypto from '../crypto'; -import enums from '../enums'; -import util from '../util'; -import { - LiteralDataPacket, - CompressedDataPacket, - OnePassSignaturePacket, - SignaturePacket -} from '../packet'; - -const VERSION = 1; // A one-octet version number of the data packet. - -/** - * 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 - * @memberof module:packet - */ -class AEADEncryptedDataPacket { - constructor() { - this.tag = enums.packet.AEADEncryptedData; - this.version = VERSION; - this.cipherAlgo = null; - this.aeadAlgorithm = 'eax'; - this.aeadAlgo = null; - this.chunkSizeByte = null; - this.iv = null; - this.encrypted = null; - this.packets = null; - } - - /** - * Parse an encrypted payload of bytes in the order: version, IV, ciphertext (see specification) - * @param {Uint8Array | ReadableStream} bytes - */ - async read(bytes) { - await stream.parse(bytes, async reader => { - if (await reader.readByte() !== VERSION) { // The only currently defined value is 1. - throw new Error('Invalid packet version.'); - } - this.cipherAlgo = await reader.readByte(); - this.aeadAlgo = await reader.readByte(); - this.chunkSizeByte = await reader.readByte(); - const mode = crypto[enums.read(enums.aead, this.aeadAlgo)]; - this.iv = await reader.readBytes(mode.ivLength); - this.encrypted = reader.remainder(); - }); - } - - /** - * Write the encrypted payload of bytes in the order: version, IV, ciphertext (see specification) - * @returns {Uint8Array | ReadableStream} The encrypted payload - */ - write() { - return util.concat([new Uint8Array([this.version, this.cipherAlgo, this.aeadAlgo, this.chunkSizeByte]), 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 - * @param {Boolean} streaming Whether the top-level function will return a stream - * @throws {Error} if decryption was not successful - * @async - */ - async decrypt(sessionKeyAlgorithm, key, streaming) { - await this.packets.read(await this.crypt('decrypt', key, stream.clone(this.encrypted), streaming), { - LiteralDataPacket, - CompressedDataPacket, - OnePassSignaturePacket, - SignaturePacket - }, streaming); - } - - /** - * 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 - * @param {Boolean} streaming Whether the top-level function will return a stream - * @throws {Error} if encryption was not successful - * @async - */ - async encrypt(sessionKeyAlgorithm, key, streaming) { - this.cipherAlgo = enums.write(enums.symmetric, sessionKeyAlgorithm); - this.aeadAlgo = enums.write(enums.aead, this.aeadAlgorithm); - const mode = crypto[enums.read(enums.aead, this.aeadAlgo)]; - this.iv = await crypto.random.getRandomBytes(mode.ivLength); // generate new random IV - this.chunkSizeByte = config.aeadChunkSizeByte; - const data = this.packets.write(); - this.encrypted = await this.crypt('encrypt', key, data, streaming); - } - - /** - * En/decrypt the payload. - * @param {encrypt|decrypt} fn Whether to encrypt or decrypt - * @param {Uint8Array} key The session key used to en/decrypt the payload - * @param {Uint8Array | ReadableStream} data The data to en/decrypt - * @param {Boolean} streaming Whether the top-level function will return a stream - * @returns {Uint8Array | ReadableStream} - * @async - */ - async crypt(fn, key, data, streaming) { - const cipher = enums.read(enums.symmetric, this.cipherAlgo); - const mode = crypto[enums.read(enums.aead, this.aeadAlgo)]; - const modeInstance = await mode(cipher, key); - const tagLengthIfDecrypting = fn === 'decrypt' ? mode.tagLength : 0; - const tagLengthIfEncrypting = fn === 'encrypt' ? mode.tagLength : 0; - const chunkSize = 2 ** (this.chunkSizeByte + 6) + tagLengthIfDecrypting; // ((uint64_t)1 << (c + 6)) - const adataBuffer = new ArrayBuffer(21); - const adataArray = new Uint8Array(adataBuffer, 0, 13); - const adataTagArray = new Uint8Array(adataBuffer); - const adataView = new DataView(adataBuffer); - const chunkIndexArray = new Uint8Array(adataBuffer, 5, 8); - adataArray.set([0xC0 | this.tag, this.version, this.cipherAlgo, this.aeadAlgo, this.chunkSizeByte], 0); - let chunkIndex = 0; - let latestPromise = Promise.resolve(); - let cryptedBytes = 0; - let queuedBytes = 0; - const iv = this.iv; - return stream.transformPair(data, async (readable, writable) => { - const reader = stream.getReader(readable); - const buffer = new stream.TransformStream({}, { - highWaterMark: streaming ? util.getHardwareConcurrency() * 2 ** (this.chunkSizeByte + 6) : Infinity, - size: array => array.length - }); - stream.pipe(buffer.readable, writable); - const writer = stream.getWriter(buffer.writable); - try { - while (true) { - let chunk = await reader.readBytes(chunkSize + tagLengthIfDecrypting) || new Uint8Array(); - const finalChunk = chunk.subarray(chunk.length - tagLengthIfDecrypting); - chunk = chunk.subarray(0, chunk.length - tagLengthIfDecrypting); - let cryptedPromise; - let done; - if (!chunkIndex || chunk.length) { - reader.unshift(finalChunk); - cryptedPromise = modeInstance[fn](chunk, mode.getNonce(iv, chunkIndexArray), adataArray); - queuedBytes += chunk.length - tagLengthIfDecrypting + tagLengthIfEncrypting; - } else { - // After the last chunk, we either encrypt a final, empty - // data chunk to get the final authentication tag or - // validate that final authentication tag. - adataView.setInt32(13 + 4, cryptedBytes); // Should be setInt64(13, ...) - cryptedPromise = modeInstance[fn](finalChunk, mode.getNonce(iv, chunkIndexArray), adataTagArray); - queuedBytes += tagLengthIfEncrypting; - done = true; - } - cryptedBytes += chunk.length - tagLengthIfDecrypting; - // eslint-disable-next-line no-loop-func - latestPromise = latestPromise.then(() => cryptedPromise).then(async crypted => { - await writer.ready; - await writer.write(crypted); - queuedBytes -= crypted.length; - }).catch(err => writer.abort(err)); - if (done || queuedBytes > writer.desiredSize) { - await latestPromise; // Respect backpressure - } - if (!done) { - adataView.setInt32(5 + 4, ++chunkIndex); // Should be setInt64(5, ...) - } else { - await writer.close(); - break; - } - } - } catch (e) { - await writer.abort(e); - } - }); - } -} - -export default AEADEncryptedDataPacket; diff --git a/src/packet/all_packets.js b/src/packet/all_packets.js index 1cfacb87..82e93000 100644 --- a/src/packet/all_packets.js +++ b/src/packet/all_packets.js @@ -4,73 +4,76 @@ * @module packet/all_packets */ +import enums from '../enums.js'; +import * as packets from './all_packets.js'; // re-import module to parse packets from tag + export { - /** @see CompressedDataPacket */ - default as CompressedDataPacket -} from './compressed_data.js'; + /** @see module:packet.Compressed */ + default as Compressed +} from './compressed.js'; export { - /** @see SymEncryptedIntegrityProtectedDataPacket */ - default as SymEncryptedIntegrityProtectedDataPacket -} from './sym_encrypted_integrity_protected_data.js'; + /** @see module:packet.SymEncryptedIntegrityProtected */ + default as SymEncryptedIntegrityProtected +} from './sym_encrypted_integrity_protected.js'; export { - /** @see AEADEncryptedDataPacket */ - default as AEADEncryptedDataPacket -} from './aead_encrypted_data.js'; + /** @see module:packet.SymEncryptedAEADProtected */ + default as SymEncryptedAEADProtected +} from './sym_encrypted_aead_protected.js'; export { - /** @see PublicKeyEncryptedSessionKeyPacket */ - default as PublicKeyEncryptedSessionKeyPacket + /** @see module:packet.PublicKeyEncryptedSessionKey */ + default as PublicKeyEncryptedSessionKey } from './public_key_encrypted_session_key.js'; export { - /** @see SymEncryptedSessionKeyPacket */ - default as SymEncryptedSessionKeyPacket + /** @see module:packet.SymEncryptedSessionKey */ + default as SymEncryptedSessionKey } from './sym_encrypted_session_key.js'; export { - /** @see LiteralDataPacket */ - default as LiteralDataPacket -} from './literal_data.js'; + /** @see module:packet.Literal */ + default as Literal +} from './literal.js'; export { - /** @see PublicKeyPacket */ - default as PublicKeyPacket + /** @see module:packet.PublicKey */ + default as PublicKey } from './public_key.js'; export { - /** @see SymmetricallyEncryptedDataPacket */ - default as SymmetricallyEncryptedDataPacket -} from './symmetrically_encrypted_data.js'; + /** @see module:packet.SymmetricallyEncrypted */ + default as SymmetricallyEncrypted +} from './symmetrically_encrypted.js'; export { - /** @see MarkerPacket */ - default as MarkerPacket + /** @see module:packet.Marker */ + default as Marker } from './marker.js'; export { - /** @see PublicSubkeyPacket */ - default as PublicSubkeyPacket + /** @see module:packet.PublicSubkey */ + default as PublicSubkey } from './public_subkey.js'; export { - /** @see UserAttributePacket */ - default as UserAttributePacket + /** @see module:packet.UserAttribute */ + default as UserAttribute } from './user_attribute.js'; export { - /** @see OnePassSignaturePacket */ - default as OnePassSignaturePacket + /** @see module:packet.OnePassSignature */ + default as OnePassSignature } from './one_pass_signature.js'; export { - /** @see SecretKeyPacket */ - default as SecretKeyPacket + /** @see module:packet.SecretKey */ + default as SecretKey } from './secret_key.js'; export { - /** @see UserIDPacket */ - default as UserIDPacket + /** @see module:packet.Userid */ + default as Userid } from './userid.js'; export { - /** @see SecretSubkeyPacket */ - default as SecretSubkeyPacket + /** @see module:packet.SecretSubkey */ + default as SecretSubkey } from './secret_subkey.js'; export { - /** @see SignaturePacket */ - default as SignaturePacket + /** @see module:packet.Signature */ + default as Signature } from './signature.js'; export { - /** @see TrustPacket */ - default as TrustPacket + /** @see module:packet.Trust */ + default as Trust } from './trust.js'; /** @@ -80,12 +83,26 @@ export { * @param {String} tag property name from {@link module:enums.packet} * @returns {Object} new packet object with type based on tag */ -export function newPacketFromTag(tag, allowedPackets) { - const className = packetClassFromTagName(tag); - if (!allowedPackets[className]) { - throw new Error('Packet not allowed in this context: ' + className); +export function newPacketFromTag(tag) { + return new packets[packetClassFromTagName(tag)](); +} + +/** + * Allocate a new packet from structured packet clone + * @see {@link https://w3c.github.io/html/infrastructure.html#safe-passing-of-structured-data} + * @function fromStructuredClone + * @memberof module:packet + * @param {Object} packetClone packet clone + * @returns {Object} new packet object with data from packet clone + */ +export function fromStructuredClone(packetClone) { + const tagName = enums.read(enums.packet, packetClone.tag); + const packet = newPacketFromTag(tagName); + Object.assign(packet, packetClone); + if (packet.postCloneTypeFix) { + packet.postCloneTypeFix(); } - return new allowedPackets[className](); + return packet; } /** @@ -95,5 +112,5 @@ export function newPacketFromTag(tag, allowedPackets) { * @private */ function packetClassFromTagName(tag) { - return tag.substr(0, 1).toUpperCase() + tag.substr(1) + 'Packet'; + return tag.substr(0, 1).toUpperCase() + tag.substr(1); } diff --git a/src/packet/clone.js b/src/packet/clone.js new file mode 100644 index 00000000..a9f0333c --- /dev/null +++ b/src/packet/clone.js @@ -0,0 +1,189 @@ +// OpenPGP.js - An OpenPGP implementation in javascript +// Copyright (C) 2015 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 implements packet list cloning required to + * pass certain object types between the web worker and main thread using + * the structured cloning algorithm. + * @module packet/clone + */ + +import stream from 'web-stream-tools'; +import { Key } from '../key'; +import { Message } from '../message'; +import { CleartextMessage } from '../cleartext'; +import { Signature } from '../signature'; +import List from './packetlist'; +import type_keyid from '../type/keyid'; +import util from '../util'; + + +////////////////////////////// +// // +// List --> Clone // +// // +////////////////////////////// + + +/** + * Create a packetlist from the correspoding object types. + * @param {Object} options the object passed to and from the web worker + * @returns {Object} a mutated version of the options optject + */ +export function clonePackets(options) { + if (options.publicKeys) { + options.publicKeys = options.publicKeys.map(key => key.toPacketlist()); + } + if (options.privateKeys) { + options.privateKeys = options.privateKeys.map(key => key.toPacketlist()); + } + if (options.publicKey) { + options.publicKey = options.publicKey.toPacketlist(); + } + if (options.privateKey) { + options.privateKey = options.privateKey.toPacketlist(); + } + if (options.key) { + options.key = options.key.toPacketlist(); + } + if (options.message) { + //could be either a Message or CleartextMessage object + if (options.message instanceof Message) { + options.message = { packets: options.message.packets, fromStream: options.message.fromStream }; + } else if (options.message instanceof CleartextMessage) { + options.message = { text: options.message.text, signature: options.message.signature.packets }; + } + } + if (options.signature && (options.signature instanceof Signature)) { + options.signature = options.signature.packets; + } + if (options.signatures) { + options.signatures.forEach(verificationObjectToClone); + } + return options; +} + +function verificationObjectToClone(verObject) { + const verified = verObject.verified; + verObject.verified = stream.fromAsync(() => verified); + if (verObject.signature instanceof Promise) { + const signature = verObject.signature; + verObject.signature = stream.fromAsync(async () => { + const packets = (await signature).packets; + try { + await verified; + } catch (e) {} + if (packets && packets[0]) { + delete packets[0].signature; + delete packets[0].hashed; + } + return packets; + }); + } else { + verObject.signature = verObject.signature.packets; + } + if (verObject.error) { + verObject.error = verObject.error.message; + } + return verObject; +} + +////////////////////////////// +// // +// Clone --> List // +// // +////////////////////////////// + + +/** + * Creates an object with the correct prototype from a corresponding packetlist. + * @param {Object} options the object passed to and from the web worker + * @param {String} method the public api function name to be delegated to the worker + * @returns {Object} a mutated version of the options optject + */ +export function parseClonedPackets(options) { + if (options.publicKeys) { + options.publicKeys = options.publicKeys.map(packetlistCloneToKey); + } + if (options.privateKeys) { + options.privateKeys = options.privateKeys.map(packetlistCloneToKey); + } + if (options.publicKey) { + options.publicKey = packetlistCloneToKey(options.publicKey); + } + if (options.privateKey) { + options.privateKey = packetlistCloneToKey(options.privateKey); + } + if (options.key) { + options.key = packetlistCloneToKey(options.key); + } + if (options.message && options.message.signature) { + options.message = packetlistCloneToCleartextMessage(options.message); + } else if (options.message) { + options.message = packetlistCloneToMessage(options.message); + } + if (options.signatures) { + options.signatures = options.signatures.map(packetlistCloneToSignatures); + } + if (options.signature) { + options.signature = packetlistCloneToSignature(options.signature); + } + return options; +} + +function packetlistCloneToKey(clone) { + const packetlist = List.fromStructuredClone(clone); + return new Key(packetlist); +} + +function packetlistCloneToMessage(clone) { + const packetlist = List.fromStructuredClone(clone.packets); + const message = new Message(packetlist); + message.fromStream = clone.fromStream; + return message; +} + +function packetlistCloneToCleartextMessage(clone) { + const packetlist = List.fromStructuredClone(clone.signature); + return new CleartextMessage(clone.text, new Signature(packetlist)); +} + +//verification objects +function packetlistCloneToSignatures(clone) { + clone.keyid = type_keyid.fromClone(clone.keyid); + if (util.isStream(clone.signature)) { + clone.signature = stream.readToEnd(clone.signature, ([signature]) => new Signature(List.fromStructuredClone(signature))); + clone.signature.catch(() => {}); + } else { + clone.signature = new Signature(List.fromStructuredClone(clone.signature)); + } + clone.verified = stream.readToEnd(clone.verified, ([verified]) => verified); + clone.verified.catch(() => {}); + if (clone.error) { + clone.error = new Error(clone.error); + } + return clone; +} + +function packetlistCloneToSignature(clone) { + if (util.isString(clone) || util.isStream(clone)) { + //signature is armored + return clone; + } + const packetlist = List.fromStructuredClone(clone); + return new Signature(packetlist); +} diff --git a/src/packet/compressed.js b/src/packet/compressed.js new file mode 100644 index 00000000..5d646fce --- /dev/null +++ b/src/packet/compressed.js @@ -0,0 +1,194 @@ +// GPG4Browsers - An OpenPGP implementation in javascript +// Copyright (C) 2011 Recurity Labs GmbH +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 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 + +/** + * @requires web-stream-tools + * @requires pako + * @requires config + * @requires enums + * @requires util + * @requires compression/bzip2 + */ + +import pako from 'pako'; +import Bunzip from 'seek-bzip'; +import stream from 'web-stream-tools'; +import config from '../config'; +import enums from '../enums'; +import util from '../util'; + +/** + * Implementation of the Compressed Data Packet (Tag 8) + * + * {@link https://tools.ietf.org/html/rfc4880#section-5.6|RFC4880 5.6}: + * The Compressed Data packet contains compressed data. Typically, + * this packet is found as the contents of an encrypted packet, or following + * a Signature or One-Pass Signature packet, and contains a literal data packet. + * @memberof module:packet + * @constructor + */ +function Compressed() { + /** + * Packet type + * @type {module:enums.packet} + */ + this.tag = enums.packet.compressed; + /** + * List of packets + * @type {module:packet.List} + */ + this.packets = null; + /** + * Compression algorithm + * @type {compression} + */ + this.algorithm = 'zip'; + + /** + * Compressed packet data + * @type {Uint8Array | ReadableStream} + */ + this.compressed = null; +} + +/** + * Parsing function for the packet. + * @param {Uint8Array | ReadableStream} bytes Payload of a tag 8 packet + */ +Compressed.prototype.read = async function (bytes, streaming) { + await stream.parse(bytes, async reader => { + + // One octet that gives the algorithm used to compress the packet. + this.algorithm = enums.read(enums.compression, await reader.readByte()); + + // Compressed data, which makes up the remainder of the packet. + this.compressed = reader.remainder(); + + await this.decompress(streaming); + }); +}; + + +/** + * Return the compressed packet. + * @returns {Uint8Array | ReadableStream} binary compressed packet + */ +Compressed.prototype.write = function () { + if (this.compressed === null) { + this.compress(); + } + + return util.concat([new Uint8Array([enums.write(enums.compression, this.algorithm)]), this.compressed]); +}; + + +/** + * Decompression method for decompressing the compressed data + * read by read_packet + */ +Compressed.prototype.decompress = async function (streaming) { + + if (!decompress_fns[this.algorithm]) { + throw new Error(this.algorithm + ' decompression not supported'); + } + + await this.packets.read(decompress_fns[this.algorithm](this.compressed), streaming); +}; + +/** + * Compress the packet data (member decompressedData) + */ +Compressed.prototype.compress = function () { + + if (!compress_fns[this.algorithm]) { + throw new Error(this.algorithm + ' compression not supported'); + } + + this.compressed = compress_fns[this.algorithm](this.packets.write()); +}; + +export default Compressed; + +////////////////////////// +// // +// Helper functions // +// // +////////////////////////// + + +const nodeZlib = util.getNodeZlib(); + +function uncompressed(data) { + return data; +} + +function node_zlib(func, options = {}) { + return function (data) { + return stream.nodeToWeb(stream.webToNode(data).pipe(func(options))); + }; +} + +function pako_zlib(constructor, options = {}) { + return function(data) { + const obj = new constructor(options); + return stream.transform(data, value => { + if (value.length) { + obj.push(value, pako.Z_SYNC_FLUSH); + return obj.result; + } + }, () => { + if (constructor === pako.Deflate) { + obj.push([], pako.Z_FINISH); + return obj.result; + } + }); + }; +} + +function bzip2(func) { + return function(data) { + return stream.fromAsync(async () => func(await stream.readToEnd(data))); + }; +} + +let compress_fns; +let decompress_fns; +if (nodeZlib) { // Use Node native zlib for DEFLATE compression/decompression + compress_fns = { + zip: node_zlib(nodeZlib.createDeflateRaw, { level: config.deflate_level }), + zlib: node_zlib(nodeZlib.createDeflate, { level: config.deflate_level }) + }; + + decompress_fns = { + uncompressed: uncompressed, + zip: node_zlib(nodeZlib.createInflateRaw), + zlib: node_zlib(nodeZlib.createInflate), + bzip2: bzip2(Bunzip.decode) + }; +} else { // Use JS fallbacks + compress_fns = { + zip: pako_zlib(pako.Deflate, { raw: true, level: config.deflate_level }), + zlib: pako_zlib(pako.Deflate, { level: config.deflate_level }) + }; + + decompress_fns = { + uncompressed: uncompressed, + zip: pako_zlib(pako.Inflate, { raw: true }), + zlib: pako_zlib(pako.Inflate), + bzip2: bzip2(Bunzip.decode) + }; +} diff --git a/src/packet/compressed_data.js b/src/packet/compressed_data.js deleted file mode 100644 index f6656407..00000000 --- a/src/packet/compressed_data.js +++ /dev/null @@ -1,199 +0,0 @@ -// GPG4Browsers - An OpenPGP implementation in javascript -// Copyright (C) 2011 Recurity Labs GmbH -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 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 - -/** - * @requires web-stream-tools - * @requires pako - * @requires seek-bzip - * @requires config - * @requires enums - * @requires util - * @requires packet - */ - -import { Deflate } from 'pako/lib/deflate'; -import { Inflate } from 'pako/lib/inflate'; -import { Z_SYNC_FLUSH, Z_FINISH } from 'pako/lib/zlib/constants'; -import { decode as BunzipDecode } from 'seek-bzip'; -import stream from 'web-stream-tools'; -import config from '../config'; -import enums from '../enums'; -import util from '../util'; -import { - LiteralDataPacket, - OnePassSignaturePacket, - SignaturePacket -} from '../packet'; - -/** - * Implementation of the Compressed Data Packet (Tag 8) - * - * {@link https://tools.ietf.org/html/rfc4880#section-5.6|RFC4880 5.6}: - * The Compressed Data packet contains compressed data. Typically, - * this packet is found as the contents of an encrypted packet, or following - * a Signature or One-Pass Signature packet, and contains a literal data packet. - * @memberof module:packet - */ -class CompressedDataPacket { - constructor() { - /** - * Packet type - * @type {module:enums.packet} - */ - this.tag = enums.packet.compressedData; - /** - * List of packets - * @type {PacketList} - */ - this.packets = null; - /** - * Compression algorithm - * @type {compression} - */ - this.algorithm = 'zip'; - - /** - * Compressed packet data - * @type {Uint8Array | ReadableStream} - */ - this.compressed = null; - } - - /** - * Parsing function for the packet. - * @param {Uint8Array | ReadableStream} bytes Payload of a tag 8 packet - */ - async read(bytes, streaming) { - await stream.parse(bytes, async reader => { - - // One octet that gives the algorithm used to compress the packet. - this.algorithm = enums.read(enums.compression, await reader.readByte()); - - // Compressed data, which makes up the remainder of the packet. - this.compressed = reader.remainder(); - - await this.decompress(streaming); - }); - } - - - /** - * Return the compressed packet. - * @returns {Uint8Array | ReadableStream} binary compressed packet - */ - write() { - if (this.compressed === null) { - this.compress(); - } - - return util.concat([new Uint8Array([enums.write(enums.compression, this.algorithm)]), this.compressed]); - } - - - /** - * Decompression method for decompressing the compressed data - * read by read_packet - */ - async decompress(streaming) { - - if (!decompress_fns[this.algorithm]) { - throw new Error(this.algorithm + ' decompression not supported'); - } - - await this.packets.read(decompress_fns[this.algorithm](this.compressed), { - LiteralDataPacket, - OnePassSignaturePacket, - SignaturePacket - }, streaming); - } - - /** - * Compress the packet data (member decompressedData) - */ - compress() { - - if (!compress_fns[this.algorithm]) { - throw new Error(this.algorithm + ' compression not supported'); - } - - this.compressed = compress_fns[this.algorithm](this.packets.write()); - } -} - -export default CompressedDataPacket; - -////////////////////////// -// // -// Helper functions // -// // -////////////////////////// - - -const nodeZlib = util.getNodeZlib(); - -function uncompressed(data) { - return data; -} - -function node_zlib(func, options = {}) { - return function (data) { - return stream.nodeToWeb(stream.webToNode(data).pipe(func(options))); - }; -} - -function pako_zlib(constructor, options = {}) { - return function(data) { - const obj = new constructor(options); - return stream.transform(data, value => { - if (value.length) { - obj.push(value, Z_SYNC_FLUSH); - return obj.result; - } - }, () => { - if (constructor === Deflate) { - obj.push([], Z_FINISH); - return obj.result; - } - }); - }; -} - -function bzip2(func) { - return function(data) { - return stream.fromAsync(async () => func(await stream.readToEnd(data))); - }; -} - -const compress_fns = nodeZlib ? { - zip: /*#__PURE__*/ node_zlib(nodeZlib.createDeflateRaw, { level: config.deflateLevel }), - zlib: /*#__PURE__*/ node_zlib(nodeZlib.createDeflate, { level: config.deflateLevel }) -} : { - zip: /*#__PURE__*/ pako_zlib(Deflate, { raw: true, level: config.deflateLevel }), - zlib: /*#__PURE__*/ pako_zlib(Deflate, { level: config.deflateLevel }) -}; - -const decompress_fns = nodeZlib ? { - uncompressed: uncompressed, - zip: /*#__PURE__*/ node_zlib(nodeZlib.createInflateRaw), - zlib: /*#__PURE__*/ node_zlib(nodeZlib.createInflate), - bzip2: /*#__PURE__*/ bzip2(BunzipDecode) -} : { - uncompressed: uncompressed, - zip: /*#__PURE__*/ pako_zlib(Inflate, { raw: true }), - zlib: /*#__PURE__*/ pako_zlib(Inflate), - bzip2: /*#__PURE__*/ bzip2(BunzipDecode) -}; diff --git a/src/packet/index.js b/src/packet/index.js index 499e7750..61361a1f 100644 --- a/src/packet/index.js +++ b/src/packet/index.js @@ -2,9 +2,19 @@ * @fileoverview OpenPGP packet types * @see module:packet/all_packets * @see module:packet/clone - * @see PacketList + * @see module:packet.List * @module packet */ -export * from './all_packets'; -export { default as PacketList } from './packetlist'; +import * as packets from './all_packets'; +import * as clone from './clone'; +import List from './packetlist'; + +const mod = { + List, + clone +}; + +Object.assign(mod, packets); + +export default mod; diff --git a/src/packet/literal.js b/src/packet/literal.js new file mode 100644 index 00000000..68395fb6 --- /dev/null +++ b/src/packet/literal.js @@ -0,0 +1,168 @@ +// GPG4Browsers - An OpenPGP implementation in javascript +// Copyright (C) 2011 Recurity Labs GmbH +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 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 + +/** + * @requires web-stream-tools + * @requires enums + * @requires util + */ + +import stream from 'web-stream-tools'; +import enums from '../enums'; +import util from '../util'; + +/** + * Implementation of the Literal Data Packet (Tag 11) + * + * {@link https://tools.ietf.org/html/rfc4880#section-5.9|RFC4880 5.9}: + * A Literal Data packet contains the body of a message; data that is not to be + * further interpreted. + * @param {Date} date the creation date of the literal package + * @memberof module:packet + * @constructor + */ +function Literal(date = new Date()) { + this.tag = enums.packet.literal; + this.format = 'utf8'; // default format for literal data packets + this.date = util.normalizeDate(date); + this.text = null; // textual data representation + this.data = null; // literal data representation + this.filename = 'msg.txt'; +} + +/** + * Set the packet data to a javascript native string, end of line + * will be normalized to \r\n and by default text is converted to UTF8 + * @param {String | ReadableStream} text Any native javascript string + * @param {utf8|binary|text|mime} format (optional) The format of the string of bytes + */ +Literal.prototype.setText = function(text, format = 'utf8') { + this.format = format; + this.text = text; + this.data = null; +}; + +/** + * Returns literal data packets as native JavaScript string + * with normalized end of line to \n + * @param {Boolean} clone (optional) Whether to return a clone so that getBytes/getText can be called again + * @returns {String | ReadableStream} literal data as text + */ +Literal.prototype.getText = function(clone = false) { + if (this.text === null || util.isStream(this.text)) { // Assume that this.text has been read + this.text = util.decode_utf8(util.nativeEOL(this.getBytes(clone))); + } + return this.text; +}; + +/** + * Set the packet data to value represented by the provided string of bytes. + * @param {Uint8Array | ReadableStream} bytes The string of bytes + * @param {utf8|binary|text|mime} format The format of the string of bytes + */ +Literal.prototype.setBytes = function(bytes, format) { + this.format = format; + this.data = bytes; + this.text = null; +}; + + +/** + * Get the byte sequence representing the literal packet data + * @param {Boolean} clone (optional) Whether to return a clone so that getBytes/getText can be called again + * @returns {Uint8Array | ReadableStream} A sequence of bytes + */ +Literal.prototype.getBytes = function(clone = false) { + if (this.data === null) { + // encode UTF8 and normalize EOL to \r\n + this.data = util.canonicalizeEOL(util.encode_utf8(this.text)); + } + if (clone) { + return stream.passiveClone(this.data); + } + return this.data; +}; + + +/** + * Sets the filename of the literal packet data + * @param {String} filename Any native javascript string + */ +Literal.prototype.setFilename = function(filename) { + this.filename = filename; +}; + + +/** + * Get the filename of the literal packet data + * @returns {String} filename + */ +Literal.prototype.getFilename = function() { + return this.filename; +}; + + +/** + * Parsing function for a literal data packet (tag 11). + * + * @param {Uint8Array | ReadableStream} input Payload of a tag 11 packet + * @returns {module:packet.Literal} object representation + */ +Literal.prototype.read = async function(bytes) { + await stream.parse(bytes, async reader => { + // - A one-octet field that describes how the data is formatted. + const format = enums.read(enums.literal, await reader.readByte()); + + const filename_len = await reader.readByte(); + this.filename = util.decode_utf8(await reader.readBytes(filename_len)); + + this.date = util.readDate(await reader.readBytes(4)); + + const data = reader.remainder(); + + this.setBytes(data, format); + }); +}; + +/** + * Creates a Uint8Array representation of the packet, excluding the data + * + * @returns {Uint8Array} Uint8Array representation of the packet + */ +Literal.prototype.writeHeader = function() { + const filename = util.encode_utf8(this.filename); + const filename_length = new Uint8Array([filename.length]); + + const format = new Uint8Array([enums.write(enums.literal, this.format)]); + const date = util.writeDate(this.date); + + return util.concatUint8Array([format, filename_length, filename, date]); +}; + +/** + * Creates a Uint8Array representation of the packet + * + * @returns {Uint8Array | ReadableStream} Uint8Array representation of the packet + */ +Literal.prototype.write = function() { + const header = this.writeHeader(); + const data = this.getBytes(); + + return util.concat([header, data]); +}; + +export default Literal; diff --git a/src/packet/literal_data.js b/src/packet/literal_data.js deleted file mode 100644 index a662fd8c..00000000 --- a/src/packet/literal_data.js +++ /dev/null @@ -1,171 +0,0 @@ -// GPG4Browsers - An OpenPGP implementation in javascript -// Copyright (C) 2011 Recurity Labs GmbH -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 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 - -/** - * @requires web-stream-tools - * @requires enums - * @requires util - */ - -import stream from 'web-stream-tools'; -import enums from '../enums'; -import util from '../util'; - -/** - * Implementation of the Literal Data Packet (Tag 11) - * - * {@link https://tools.ietf.org/html/rfc4880#section-5.9|RFC4880 5.9}: - * A Literal Data packet contains the body of a message; data that is not to be - * further interpreted. - * @memberof module:packet - */ -class LiteralDataPacket { - /** - * @param {Date} date the creation date of the literal package - */ - constructor(date = new Date()) { - this.tag = enums.packet.literalData; - this.format = 'utf8'; // default format for literal data packets - this.date = util.normalizeDate(date); - this.text = null; // textual data representation - this.data = null; // literal data representation - this.filename = 'msg.txt'; - } - - /** - * Set the packet data to a javascript native string, end of line - * will be normalized to \r\n and by default text is converted to UTF8 - * @param {String | ReadableStream} text Any native javascript string - * @param {utf8|binary|text|mime} format (optional) The format of the string of bytes - */ - setText(text, format = 'utf8') { - this.format = format; - this.text = text; - this.data = null; - } - - /** - * Returns literal data packets as native JavaScript string - * with normalized end of line to \n - * @param {Boolean} clone (optional) Whether to return a clone so that getBytes/getText can be called again - * @returns {String | ReadableStream} literal data as text - */ - getText(clone = false) { - if (this.text === null || util.isStream(this.text)) { // Assume that this.text has been read - this.text = util.decodeUtf8(util.nativeEOL(this.getBytes(clone))); - } - return this.text; - } - - /** - * Set the packet data to value represented by the provided string of bytes. - * @param {Uint8Array | ReadableStream} bytes The string of bytes - * @param {utf8|binary|text|mime} format The format of the string of bytes - */ - setBytes(bytes, format) { - this.format = format; - this.data = bytes; - this.text = null; - } - - - /** - * Get the byte sequence representing the literal packet data - * @param {Boolean} clone (optional) Whether to return a clone so that getBytes/getText can be called again - * @returns {Uint8Array | ReadableStream} A sequence of bytes - */ - getBytes(clone = false) { - if (this.data === null) { - // encode UTF8 and normalize EOL to \r\n - this.data = util.canonicalizeEOL(util.encodeUtf8(this.text)); - } - if (clone) { - return stream.passiveClone(this.data); - } - return this.data; - } - - - /** - * Sets the filename of the literal packet data - * @param {String} filename Any native javascript string - */ - setFilename(filename) { - this.filename = filename; - } - - - /** - * Get the filename of the literal packet data - * @returns {String} filename - */ - getFilename() { - return this.filename; - } - - - /** - * Parsing function for a literal data packet (tag 11). - * - * @param {Uint8Array | ReadableStream} input Payload of a tag 11 packet - * @returns {LiteralDataPacket} object representation - */ - async read(bytes) { - await stream.parse(bytes, async reader => { - // - A one-octet field that describes how the data is formatted. - const format = enums.read(enums.literal, await reader.readByte()); - - const filename_len = await reader.readByte(); - this.filename = util.decodeUtf8(await reader.readBytes(filename_len)); - - this.date = util.readDate(await reader.readBytes(4)); - - const data = reader.remainder(); - - this.setBytes(data, format); - }); - } - - /** - * Creates a Uint8Array representation of the packet, excluding the data - * - * @returns {Uint8Array} Uint8Array representation of the packet - */ - writeHeader() { - const filename = util.encodeUtf8(this.filename); - const filename_length = new Uint8Array([filename.length]); - - const format = new Uint8Array([enums.write(enums.literal, this.format)]); - const date = util.writeDate(this.date); - - return util.concatUint8Array([format, filename_length, filename, date]); - } - - /** - * Creates a Uint8Array representation of the packet - * - * @returns {Uint8Array | ReadableStream} Uint8Array representation of the packet - */ - write() { - const header = this.writeHeader(); - const data = this.getBytes(); - - return util.concat([header, data]); - } -} - -export default LiteralDataPacket; diff --git a/src/packet/marker.js b/src/packet/marker.js index 4a4ac80e..6fa03c63 100644 --- a/src/packet/marker.js +++ b/src/packet/marker.js @@ -15,8 +15,6 @@ // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -/* eslint class-methods-use-this: ["error", { "exceptMethods": ["read"] }] */ - /** * @requires enums */ @@ -34,32 +32,31 @@ import enums from '../enums'; * * Such a packet MUST be ignored when received. * @memberof module:packet + * @constructor */ -class MarkerPacket { - constructor() { - this.tag = enums.packet.marker; - } +function Marker() { + this.tag = enums.packet.marker; +} - /** - * Parsing function for a literal data packet (tag 10). - * - * @param {String} input Payload of a tag 10 packet - * @param {Integer} position - * Position to start reading from the input string - * @param {Integer} len - * Length of the packet or the remaining length of - * input at position - * @returns {MarkerPacket} Object representation - */ - read(bytes) { - if (bytes[0] === 0x50 && // P - bytes[1] === 0x47 && // G - bytes[2] === 0x50) { // P - return true; - } - // marker packet does not contain "PGP" - return false; +/** + * Parsing function for a literal data packet (tag 10). + * + * @param {String} input Payload of a tag 10 packet + * @param {Integer} position + * Position to start reading from the input string + * @param {Integer} len + * Length of the packet or the remaining length of + * input at position + * @returns {module:packet.Marker} Object representation + */ +Marker.prototype.read = function (bytes) { + if (bytes[0] === 0x50 && // P + bytes[1] === 0x47 && // G + bytes[2] === 0x50) { // P + return true; } -} + // marker packet does not contain "PGP" + return false; +}; -export default MarkerPacket; +export default Marker; diff --git a/src/packet/one_pass_signature.js b/src/packet/one_pass_signature.js index e8c0de91..c8bc32ba 100644 --- a/src/packet/one_pass_signature.js +++ b/src/packet/one_pass_signature.js @@ -24,7 +24,7 @@ */ import stream from 'web-stream-tools'; -import SignaturePacket from './signature'; +import Signature from './signature'; import type_keyid from '../type/keyid'; import enums from '../enums'; import util from '../util'; @@ -39,113 +39,118 @@ import util from '../util'; * packet to be placed at the end of the message, so that the signer * can compute the entire signed message in one pass. * @memberof module:packet + * @constructor */ -class OnePassSignaturePacket { - constructor() { - /** - * Packet type - * @type {module:enums.packet} - */ - this.tag = enums.packet.onePassSignature; - /** A one-octet version number. The current version is 3. */ - this.version = null; - /** - * A one-octet signature type. - * Signature types are described in - * {@link https://tools.ietf.org/html/rfc4880#section-5.2.1|RFC4880 Section 5.2.1}. - */ - this.signatureType = null; - /** - * A one-octet number describing the hash algorithm used. - * @see {@link https://tools.ietf.org/html/rfc4880#section-9.4|RFC4880 9.4} - */ - this.hashAlgorithm = null; - /** - * A one-octet number describing the public-key algorithm used. - * @see {@link https://tools.ietf.org/html/rfc4880#section-9.1|RFC4880 9.1} - */ - this.publicKeyAlgorithm = null; - /** An eight-octet number holding the Key ID of the signing key. */ - this.issuerKeyId = null; - /** - * A one-octet number holding a flag showing whether the signature is nested. - * A zero value indicates that the next packet is another One-Pass Signature packet - * that describes another signature to be applied to the same message data. - */ - this.flags = null; - } - +function OnePassSignature() { /** - * parsing function for a one-pass signature packet (tag 4). - * @param {Uint8Array} bytes payload of a tag 4 packet - * @returns {OnePassSignaturePacket} object representation + * Packet type + * @type {module:enums.packet} */ - read(bytes) { - let mypos = 0; - // A one-octet version number. The current version is 3. - this.version = bytes[mypos++]; - - // A one-octet signature type. Signature types are described in - // Section 5.2.1. - this.signatureType = bytes[mypos++]; - - // A one-octet number describing the hash algorithm used. - this.hashAlgorithm = bytes[mypos++]; - - // A one-octet number describing the public-key algorithm used. - this.publicKeyAlgorithm = bytes[mypos++]; - - // An eight-octet number holding the Key ID of the signing key. - this.issuerKeyId = new type_keyid(); - this.issuerKeyId.read(bytes.subarray(mypos, mypos + 8)); - mypos += 8; - - // A one-octet number holding a flag showing whether the signature - // is nested. A zero value indicates that the next packet is - // another One-Pass Signature packet that describes another - // signature to be applied to the same message data. - this.flags = bytes[mypos++]; - return this; - } - + this.tag = enums.packet.onePassSignature; + /** A one-octet version number. The current version is 3. */ + this.version = null; + /** + * A one-octet signature type. + * Signature types are described in + * {@link https://tools.ietf.org/html/rfc4880#section-5.2.1|RFC4880 Section 5.2.1}. + */ + this.signatureType = null; /** - * creates a string representation of a one-pass signature packet - * @returns {Uint8Array} a Uint8Array representation of a one-pass signature packet + * A one-octet number describing the hash algorithm used. + * @see {@link https://tools.ietf.org/html/rfc4880#section-9.4|RFC4880 9.4} */ - write() { - const start = new Uint8Array([3, enums.write(enums.signature, this.signatureType), - enums.write(enums.hash, this.hashAlgorithm), - enums.write(enums.publicKey, this.publicKeyAlgorithm)]); + this.hashAlgorithm = null; + /** + * A one-octet number describing the public-key algorithm used. + * @see {@link https://tools.ietf.org/html/rfc4880#section-9.1|RFC4880 9.1} + */ + this.publicKeyAlgorithm = null; + /** An eight-octet number holding the Key ID of the signing key. */ + this.issuerKeyId = null; + /** + * A one-octet number holding a flag showing whether the signature is nested. + * A zero value indicates that the next packet is another One-Pass Signature packet + * that describes another signature to be applied to the same message data. + */ + this.flags = null; +} - const end = new Uint8Array([this.flags]); +/** + * parsing function for a one-pass signature packet (tag 4). + * @param {Uint8Array} bytes payload of a tag 4 packet + * @returns {module:packet.OnePassSignature} object representation + */ +OnePassSignature.prototype.read = function (bytes) { + let mypos = 0; + // A one-octet version number. The current version is 3. + this.version = bytes[mypos++]; + + // A one-octet signature type. Signature types are described in + // Section 5.2.1. + this.signatureType = bytes[mypos++]; + + // A one-octet number describing the hash algorithm used. + this.hashAlgorithm = bytes[mypos++]; + + // A one-octet number describing the public-key algorithm used. + this.publicKeyAlgorithm = bytes[mypos++]; + + // An eight-octet number holding the Key ID of the signing key. + this.issuerKeyId = new type_keyid(); + this.issuerKeyId.read(bytes.subarray(mypos, mypos + 8)); + mypos += 8; + + // A one-octet number holding a flag showing whether the signature + // is nested. A zero value indicates that the next packet is + // another One-Pass Signature packet that describes another + // signature to be applied to the same message data. + this.flags = bytes[mypos++]; + return this; +}; - return util.concatUint8Array([start, this.issuerKeyId.write(), end]); - } +/** + * creates a string representation of a one-pass signature packet + * @returns {Uint8Array} a Uint8Array representation of a one-pass signature packet + */ +OnePassSignature.prototype.write = function () { + const start = new Uint8Array([3, enums.write(enums.signature, this.signatureType), + enums.write(enums.hash, this.hashAlgorithm), + enums.write(enums.publicKey, this.publicKeyAlgorithm)]); - calculateTrailer(...args) { - return stream.fromAsync(async () => SignaturePacket.prototype.calculateTrailer.apply(await this.correspondingSig, args)); - } + const end = new Uint8Array([this.flags]); - async verify() { - const correspondingSig = await this.correspondingSig; - if (!correspondingSig || correspondingSig.tag !== enums.packet.signature) { - throw new Error('Corresponding signature packet missing'); - } - if ( - correspondingSig.signatureType !== this.signatureType || - correspondingSig.hashAlgorithm !== this.hashAlgorithm || - correspondingSig.publicKeyAlgorithm !== this.publicKeyAlgorithm || - !correspondingSig.issuerKeyId.equals(this.issuerKeyId) - ) { - throw new Error('Corresponding signature packet does not match one-pass signature packet'); - } - correspondingSig.hashed = this.hashed; - return correspondingSig.verify.apply(correspondingSig, arguments); - } -} + return util.concatUint8Array([start, this.issuerKeyId.write(), end]); +}; -OnePassSignaturePacket.prototype.hash = SignaturePacket.prototype.hash; -OnePassSignaturePacket.prototype.toHash = SignaturePacket.prototype.toHash; -OnePassSignaturePacket.prototype.toSign = SignaturePacket.prototype.toSign; +/** + * Fix custom types after cloning + */ +OnePassSignature.prototype.postCloneTypeFix = function() { + this.issuerKeyId = type_keyid.fromClone(this.issuerKeyId); +}; + +OnePassSignature.prototype.hash = Signature.prototype.hash; +OnePassSignature.prototype.toHash = Signature.prototype.toHash; +OnePassSignature.prototype.toSign = Signature.prototype.toSign; +OnePassSignature.prototype.calculateTrailer = function(...args) { + return stream.fromAsync(async () => Signature.prototype.calculateTrailer.apply(await this.correspondingSig, args)); +}; + +OnePassSignature.prototype.verify = async function() { + const correspondingSig = await this.correspondingSig; + if (!correspondingSig || correspondingSig.tag !== enums.packet.signature) { + throw new Error('Corresponding signature packet missing'); + } + if ( + correspondingSig.signatureType !== this.signatureType || + correspondingSig.hashAlgorithm !== this.hashAlgorithm || + correspondingSig.publicKeyAlgorithm !== this.publicKeyAlgorithm || + !correspondingSig.issuerKeyId.equals(this.issuerKeyId) + ) { + throw new Error('Corresponding signature packet does not match one-pass signature packet'); + } + correspondingSig.hashed = this.hashed; + return correspondingSig.verify.apply(correspondingSig, arguments); +}; -export default OnePassSignaturePacket; +export default OnePassSignature; diff --git a/src/packet/packet.js b/src/packet/packet.js index 912df433..b50c15fa 100644 --- a/src/packet/packet.js +++ b/src/packet/packet.js @@ -29,266 +29,272 @@ import stream from 'web-stream-tools'; import enums from '../enums'; import util from '../util'; -export function readSimpleLength(bytes) { - let len = 0; - let offset; - const type = bytes[0]; +export default { + readSimpleLength: function(bytes) { + let len = 0; + let offset; + const type = bytes[0]; - if (type < 192) { - [len] = bytes; - offset = 1; - } else if (type < 255) { - len = ((bytes[0] - 192) << 8) + (bytes[1]) + 192; - offset = 2; - } else if (type === 255) { - len = util.readNumber(bytes.subarray(1, 1 + 4)); - offset = 5; - } + if (type < 192) { + [len] = bytes; + offset = 1; + } else if (type < 255) { + len = ((bytes[0] - 192) << 8) + (bytes[1]) + 192; + offset = 2; + } else if (type === 255) { + len = util.readNumber(bytes.subarray(1, 1 + 4)); + offset = 5; + } - return { - len: len, - offset: offset - }; -} + return { + len: len, + offset: offset + }; + }, -/** - * Encodes a given integer of length to the openpgp length specifier to a - * string - * - * @param {Integer} length The length to encode - * @returns {Uint8Array} String with openpgp length representation - */ -export function writeSimpleLength(length) { - if (length < 192) { - return new Uint8Array([length]); - } else if (length > 191 && length < 8384) { - /* - * let a = (total data packet length) - 192 let bc = two octet - * representation of a let d = b + 192 - */ - return new Uint8Array([((length - 192) >> 8) + 192, (length - 192) & 0xFF]); - } - return util.concatUint8Array([new Uint8Array([255]), util.writeNumber(length, 4)]); -} + /** + * Encodes a given integer of length to the openpgp length specifier to a + * string + * + * @param {Integer} length The length to encode + * @returns {Uint8Array} String with openpgp length representation + */ + writeSimpleLength: function(length) { + if (length < 192) { + return new Uint8Array([length]); + } else if (length > 191 && length < 8384) { + /* + * let a = (total data packet length) - 192 let bc = two octet + * representation of a let d = b + 192 + */ + return new Uint8Array([((length - 192) >> 8) + 192, (length - 192) & 0xFF]); + } + return util.concatUint8Array([new Uint8Array([255]), util.writeNumber(length, 4)]); + }, -export function writePartialLength(power) { - if (power < 0 || power > 30) { - throw new Error('Partial Length power must be between 1 and 30'); - } - return new Uint8Array([224 + power]); -} + writePartialLength: function(power) { + if (power < 0 || power > 30) { + throw new Error('Partial Length power must be between 1 and 30'); + } + return new Uint8Array([224 + power]); + }, -export function writeTag(tag_type) { - /* we're only generating v4 packet headers here */ - return new Uint8Array([0xC0 | tag_type]); -} + writeTag: function(tag_type) { + /* we're only generating v4 packet headers here */ + return new Uint8Array([0xC0 | tag_type]); + }, -/** - * Writes a packet header version 4 with the given tag_type and length to a - * string - * - * @param {Integer} tag_type Tag type - * @param {Integer} length Length of the payload - * @returns {String} String of the header - */ -export function writeHeader(tag_type, length) { - /* we're only generating v4 packet headers here */ - return util.concatUint8Array([writeTag(tag_type), writeSimpleLength(length)]); -} + /** + * Writes a packet header version 4 with the given tag_type and length to a + * string + * + * @param {Integer} tag_type Tag type + * @param {Integer} length Length of the payload + * @returns {String} String of the header + */ + writeHeader: function(tag_type, length) { + /* we're only generating v4 packet headers here */ + return util.concatUint8Array([this.writeTag(tag_type), this.writeSimpleLength(length)]); + }, -/** - * Whether the packet type supports partial lengths per RFC4880 - * @param {Integer} tag_type Tag type - * @returns {Boolean} String of the header - */ -export function supportsStreaming(tag_type) { - return [ - enums.packet.literalData, - enums.packet.compressedData, - enums.packet.symmetricallyEncryptedData, - enums.packet.symEncryptedIntegrityProtectedData, - enums.packet.AEADEncryptedData - ].includes(tag_type); -} + /** + * Whether the packet type supports partial lengths per RFC4880 + * @param {Integer} tag_type Tag type + * @returns {Boolean} String of the header + */ + supportsStreaming: function(tag_type) { + return [ + enums.packet.literal, + enums.packet.compressed, + enums.packet.symmetricallyEncrypted, + enums.packet.symEncryptedIntegrityProtected, + enums.packet.symEncryptedAEADProtected + ].includes(tag_type); + }, -/** - * Generic static Packet Parser function - * - * @param {Uint8Array | ReadableStream} input Input stream as string - * @param {Function} callback Function to call with the parsed packet - * @returns {Boolean} Returns false if the stream was empty and parsing is done, and true otherwise. - */ -export async function readPackets(input, streaming, callback) { - const reader = stream.getReader(input); - let writer; - let callbackReturned; - try { - const peekedBytes = await reader.peekBytes(2); - // some sanity checks - if (!peekedBytes || peekedBytes.length < 2 || (peekedBytes[0] & 0x80) === 0) { - throw new Error("Error during parsing. This message / key probably does not conform to a valid OpenPGP format."); - } - const headerByte = await reader.readByte(); - let tag = -1; - let format = -1; - let packet_length; + /** + * Generic static Packet Parser function + * + * @param {Uint8Array | ReadableStream} input Input stream as string + * @param {Function} callback Function to call with the parsed packet + * @returns {Boolean} Returns false if the stream was empty and parsing is done, and true otherwise. + */ + read: async function(input, streaming, callback) { + const reader = stream.getReader(input); + let writer; + let callbackReturned; + try { + const peekedBytes = await reader.peekBytes(2); + // some sanity checks + if (!peekedBytes || peekedBytes.length < 2 || (peekedBytes[0] & 0x80) === 0) { + throw new Error("Error during parsing. This message / key probably does not conform to a valid OpenPGP format."); + } + const headerByte = await reader.readByte(); + let tag = -1; + let format = -1; + let packet_length; - format = 0; // 0 = old format; 1 = new format - if ((headerByte & 0x40) !== 0) { - format = 1; - } + format = 0; // 0 = old format; 1 = new format + if ((headerByte & 0x40) !== 0) { + format = 1; + } - let packet_length_type; - if (format) { - // new format header - tag = headerByte & 0x3F; // bit 5-0 - } else { - // old format header - tag = (headerByte & 0x3F) >> 2; // bit 5-2 - packet_length_type = headerByte & 0x03; // bit 1-0 - } + let packet_length_type; + if (format) { + // new format header + tag = headerByte & 0x3F; // bit 5-0 + } else { + // old format header + tag = (headerByte & 0x3F) >> 2; // bit 5-2 + packet_length_type = headerByte & 0x03; // bit 1-0 + } - const packetSupportsStreaming = supportsStreaming(tag); - let packet = null; - if (streaming && packetSupportsStreaming) { - const transform = new stream.TransformStream(); - writer = stream.getWriter(transform.writable); - packet = transform.readable; - callbackReturned = callback({ tag, packet }); - } else { - packet = []; - } + const supportsStreaming = this.supportsStreaming(tag); + let packet = null; + if (streaming && supportsStreaming) { + const transform = new TransformStream(); + writer = stream.getWriter(transform.writable); + packet = transform.readable; + callbackReturned = callback({ tag, packet }); + } else { + packet = []; + } - let wasPartialLength; - do { - if (!format) { - // 4.2.1. Old Format Packet Lengths - switch (packet_length_type) { - case 0: - // The packet has a one-octet length. The header is 2 octets - // long. - packet_length = await reader.readByte(); - break; - case 1: - // The packet has a two-octet length. The header is 3 octets - // long. - packet_length = (await reader.readByte() << 8) | await reader.readByte(); - break; - case 2: - // The packet has a four-octet length. The header is 5 - // octets long. + let wasPartialLength; + do { + if (!format) { + // 4.2.1. Old Format Packet Lengths + switch (packet_length_type) { + case 0: + // The packet has a one-octet length. The header is 2 octets + // long. + packet_length = await reader.readByte(); + break; + case 1: + // The packet has a two-octet length. The header is 3 octets + // long. + packet_length = (await reader.readByte() << 8) | await reader.readByte(); + break; + case 2: + // The packet has a four-octet length. The header is 5 + // octets long. + packet_length = (await reader.readByte() << 24) | (await reader.readByte() << 16) | (await reader.readByte() << + 8) | await reader.readByte(); + break; + default: + // 3 - The packet is of indeterminate length. The header is 1 + // octet long, and the implementation must determine how long + // the packet is. If the packet is in a file, this means that + // the packet extends until the end of the file. In general, + // an implementation SHOULD NOT use indeterminate-length + // packets except where the end of the data will be clear + // from the context, and even then it is better to use a + // definite length, or a new format header. The new format + // headers described below have a mechanism for precisely + // encoding data of indeterminate length. + packet_length = Infinity; + break; + } + } else { // 4.2.2. New Format Packet Lengths + // 4.2.2.1. One-Octet Lengths + const lengthByte = await reader.readByte(); + wasPartialLength = false; + if (lengthByte < 192) { + packet_length = lengthByte; + // 4.2.2.2. Two-Octet Lengths + } else if (lengthByte >= 192 && lengthByte < 224) { + packet_length = ((lengthByte - 192) << 8) + (await reader.readByte()) + 192; + // 4.2.2.4. Partial Body Lengths + } else if (lengthByte > 223 && lengthByte < 255) { + packet_length = 1 << (lengthByte & 0x1F); + wasPartialLength = true; + if (!supportsStreaming) { + throw new TypeError('This packet type does not support partial lengths.'); + } + // 4.2.2.3. Five-Octet Lengths + } else { packet_length = (await reader.readByte() << 24) | (await reader.readByte() << 16) | (await reader.readByte() << 8) | await reader.readByte(); - break; - default: - // 3 - The packet is of indeterminate length. The header is 1 - // octet long, and the implementation must determine how long - // the packet is. If the packet is in a file, this means that - // the packet extends until the end of the file. In general, - // an implementation SHOULD NOT use indeterminate-length - // packets except where the end of the data will be clear - // from the context, and even then it is better to use a - // definite length, or a new format header. The new format - // headers described below have a mechanism for precisely - // encoding data of indeterminate length. - packet_length = Infinity; - break; - } - } else { // 4.2.2. New Format Packet Lengths - // 4.2.2.1. One-Octet Lengths - const lengthByte = await reader.readByte(); - wasPartialLength = false; - if (lengthByte < 192) { - packet_length = lengthByte; - // 4.2.2.2. Two-Octet Lengths - } else if (lengthByte >= 192 && lengthByte < 224) { - packet_length = ((lengthByte - 192) << 8) + (await reader.readByte()) + 192; - // 4.2.2.4. Partial Body Lengths - } else if (lengthByte > 223 && lengthByte < 255) { - packet_length = 1 << (lengthByte & 0x1F); - wasPartialLength = true; - if (!packetSupportsStreaming) { - throw new TypeError('This packet type does not support partial lengths.'); } - // 4.2.2.3. Five-Octet Lengths - } else { - packet_length = (await reader.readByte() << 24) | (await reader.readByte() << 16) | (await reader.readByte() << - 8) | await reader.readByte(); } - } - if (packet_length > 0) { - let bytesRead = 0; - while (true) { - if (writer) await writer.ready; - const { done, value } = await reader.read(); - if (done) { - if (packet_length === Infinity) break; - throw new Error('Unexpected end of packet'); - } - const chunk = packet_length === Infinity ? value : value.subarray(0, packet_length - bytesRead); - if (writer) await writer.write(chunk); - else packet.push(chunk); - bytesRead += value.length; - if (bytesRead >= packet_length) { - reader.unshift(value.subarray(packet_length - bytesRead + value.length)); - break; + if (packet_length > 0) { + let bytesRead = 0; + while (true) { + if (writer) await writer.ready; + const { done, value } = await reader.read(); + if (done) { + if (packet_length === Infinity) break; + throw new Error('Unexpected end of packet'); + } + const chunk = packet_length === Infinity ? value : value.subarray(0, packet_length - bytesRead); + if (writer) await writer.write(chunk); + else packet.push(chunk); + bytesRead += value.length; + if (bytesRead >= packet_length) { + reader.unshift(value.subarray(packet_length - bytesRead + value.length)); + break; + } } } - } - } while (wasPartialLength); + } while (wasPartialLength); - // If this was not a packet that "supports streaming", we peek to check - // whether it is the last packet in the message. We peek 2 bytes instead - // of 1 because the beginning of this function also peeks 2 bytes, and we - // want to cut a `subarray` of the correct length into `web-stream-tools`' - // `externalBuffer` as a tiny optimization here. - // - // If it *was* a streaming packet (i.e. the data packets), we peek at the - // entire remainder of the stream, in order to forward errors in the - // remainder of the stream to the packet data. (Note that this means we - // read/peek at all signature packets before closing the literal data - // packet, for example.) This forwards MDC errors to the literal data - // stream, for example, so that they don't get lost / forgotten on - // decryptedMessage.packets.stream, which we never look at. - // - // An example of what we do when stream-parsing a message containing - // [ one-pass signature packet, literal data packet, signature packet ]: - // 1. Read the one-pass signature packet - // 2. Peek 2 bytes of the literal data packet - // 3. Parse the one-pass signature packet - // - // 4. Read the literal data packet, simultaneously stream-parsing it - // 5. Peek until the end of the message - // 6. Finish parsing the literal data packet - // - // 7. Read the signature packet again (we already peeked at it in step 5) - // 8. Peek at the end of the stream again (`peekBytes` returns undefined) - // 9. Parse the signature packet - // - // Note that this means that if there's an error in the very end of the - // stream, such as an MDC error, we throw in step 5 instead of in step 8 - // (or never), which is the point of this exercise. - const nextPacket = await reader.peekBytes(packetSupportsStreaming ? Infinity : 2); - if (writer) { - await writer.ready; - await writer.close(); - } else { - packet = util.concatUint8Array(packet); - await callback({ tag, packet }); - } - return !nextPacket || !nextPacket.length; - } catch (e) { - if (writer) { - await writer.abort(e); - return true; - } else { - throw e; - } - } finally { - if (writer) { - await callbackReturned; + // If this was not a packet that "supports streaming", we peek to check + // whether it is the last packet in the message. We peek 2 bytes instead + // of 1 because the beginning of this function also peeks 2 bytes, and we + // want to cut a `subarray` of the correct length into `web-stream-tools`' + // `externalBuffer` as a tiny optimization here. + // + // If it *was* a streaming packet (i.e. the data packets), we peek at the + // entire remainder of the stream, in order to forward errors in the + // remainder of the stream to the packet data. (Note that this means we + // read/peek at all signature packets before closing the literal data + // packet, for example.) This forwards armor checksum errors to the + // encrypted data stream, for example, so that they don't get lost / + // forgotten on encryptedMessage.packets.stream, which we never look at. + // + // Note that subsequent packet parsing errors could still end up there if + // `config.tolerant` is set to false, or on malformed messages with + // multiple data packets, but usually it shouldn't happen. + // + // An example of what we do when stream-parsing a message containing + // [ one-pass signature packet, literal data packet, signature packet ]: + // 1. Read the one-pass signature packet + // 2. Peek 2 bytes of the literal data packet + // 3. Parse the one-pass signature packet + // + // 4. Read the literal data packet, simultaneously stream-parsing it + // 5. Peek until the end of the message + // 6. Finish parsing the literal data packet + // + // 7. Read the signature packet again (we already peeked at it in step 5) + // 8. Peek at the end of the stream again (`peekBytes` returns undefined) + // 9. Parse the signature packet + // + // Note that this means that if there's an error in the very end of the + // stream, such as an MDC error, we throw in step 5 instead of in step 8 + // (or never), which is the point of this exercise. + const nextPacket = await reader.peekBytes(supportsStreaming ? Infinity : 2); + if (writer) { + await writer.ready; + await writer.close(); + } else { + packet = util.concatUint8Array(packet); + await callback({ tag, packet }); + } + return !nextPacket || !nextPacket.length; + } catch (e) { + if (writer) { + await writer.abort(e); + return true; + } else { + throw e; + } + } finally { + if (writer) { + await callbackReturned; + } + reader.releaseLock(); } - reader.releaseLock(); } -} +}; diff --git a/src/packet/packetlist.js b/src/packet/packetlist.js index 7b51f684..a07b8a39 100644 --- a/src/packet/packetlist.js +++ b/src/packet/packetlist.js @@ -10,11 +10,7 @@ import stream from 'web-stream-tools'; import * as packets from './all_packets'; -import { - readPackets, supportsStreaming, - writeTag, writeHeader, - writePartialLength, writeSimpleLength -} from './packet'; +import packetParser from './packet'; import config from '../config'; import enums from '../enums'; import util from '../util'; @@ -24,176 +20,213 @@ import util from '../util'; * Take care when iterating over it - the packets themselves * are stored as numerical indices. * @memberof module:packet + * @constructor * @extends Array */ -class PacketList extends Array { +function List() { /** - * Reads a stream of binary data and interprets it as a list of packets. - * @param {Uint8Array | ReadableStream} bytes A Uint8Array of bytes. + * The number of packets contained within the list. + * @readonly + * @type {Integer} */ - async read(bytes, allowedPackets, streaming) { - this.stream = stream.transformPair(bytes, async (readable, writable) => { - const writer = stream.getWriter(writable); - try { - while (true) { - await writer.ready; - const done = await readPackets(readable, streaming, async parsed => { - try { - const tag = enums.read(enums.packet, parsed.tag); - const packet = packets.newPacketFromTag(tag, allowedPackets); - packet.packets = new PacketList(); - packet.fromStream = util.isStream(parsed.packet); - await packet.read(parsed.packet, streaming); - await writer.write(packet); - } catch (e) { - if (!config.tolerant || supportsStreaming(parsed.tag)) { - // The packets that support streaming are the ones that contain - // message data. Those are also the ones we want to be more strict - // about and throw on parse errors for. - await writer.abort(e); - } - util.printDebugError(e); + this.length = 0; +} + +List.prototype = []; + +/** + * Reads a stream of binary data and interprents it as a list of packets. + * @param {Uint8Array | ReadableStream} A Uint8Array of bytes. + */ +List.prototype.read = async function (bytes, streaming) { + this.stream = stream.transformPair(bytes, async (readable, writable) => { + const writer = stream.getWriter(writable); + try { + while (true) { + await writer.ready; + const done = await packetParser.read(readable, streaming, async parsed => { + try { + const tag = enums.read(enums.packet, parsed.tag); + const packet = packets.newPacketFromTag(tag); + packet.packets = new List(); + packet.fromStream = util.isStream(parsed.packet); + await packet.read(parsed.packet, streaming); + await writer.write(packet); + } catch (e) { + if (!config.tolerant || packetParser.supportsStreaming(parsed.tag)) { + // The packets that support streaming are the ones that contain + // message data. Those are also the ones we want to be more strict + // about and throw on parse errors for. + await writer.abort(e); } - }); - if (done) { - await writer.ready; - await writer.close(); - return; + util.print_debug_error(e); } + }); + if (done) { + await writer.ready; + await writer.close(); + return; } - } catch (e) { - await writer.abort(e); - } - }); - - // Wait until first few packets have been read - const reader = stream.getReader(this.stream); - while (true) { - const { done, value } = await reader.read(); - if (!done) { - this.push(value); - } else { - this.stream = null; - } - if (done || supportsStreaming(value.tag)) { - break; } + } catch (e) { + await writer.abort(e); + } + }); + + // Wait until first few packets have been read + const reader = stream.getReader(this.stream); + while (true) { + const { done, value } = await reader.read(); + if (!done) { + this.push(value); + } else { + this.stream = null; + } + if (done || packetParser.supportsStreaming(value.tag)) { + break; } - reader.releaseLock(); } + reader.releaseLock(); +}; - /** - * Creates a binary representation of openpgp objects contained within the - * class instance. - * @returns {Uint8Array} A Uint8Array containing valid openpgp packets. - */ - write() { - const arr = []; - - for (let i = 0; i < this.length; i++) { - const packetbytes = this[i].write(); - if (util.isStream(packetbytes) && supportsStreaming(this[i].tag)) { - let buffer = []; - let bufferLength = 0; - const minLength = 512; - arr.push(writeTag(this[i].tag)); - arr.push(stream.transform(packetbytes, value => { - buffer.push(value); - bufferLength += value.length; - if (bufferLength >= minLength) { - const powerOf2 = Math.min(Math.log(bufferLength) / Math.LN2 | 0, 30); - const chunkSize = 2 ** powerOf2; - const bufferConcat = util.concat([writePartialLength(powerOf2)].concat(buffer)); - buffer = [bufferConcat.subarray(1 + chunkSize)]; - bufferLength = buffer[0].length; - return bufferConcat.subarray(0, 1 + chunkSize); - } - }, () => util.concat([writeSimpleLength(bufferLength)].concat(buffer)))); - } else { - if (util.isStream(packetbytes)) { - let length = 0; - arr.push(stream.transform(stream.clone(packetbytes), value => { - length += value.length; - }, () => writeHeader(this[i].tag, length))); - } else { - arr.push(writeHeader(this[i].tag, packetbytes.length)); +/** + * Creates a binary representation of openpgp objects contained within the + * class instance. + * @returns {Uint8Array} A Uint8Array containing valid openpgp packets. + */ +List.prototype.write = function () { + const arr = []; + + for (let i = 0; i < this.length; i++) { + const packetbytes = this[i].write(); + if (util.isStream(packetbytes) && packetParser.supportsStreaming(this[i].tag)) { + let buffer = []; + let bufferLength = 0; + const minLength = 512; + arr.push(packetParser.writeTag(this[i].tag)); + arr.push(stream.transform(packetbytes, value => { + buffer.push(value); + bufferLength += value.length; + if (bufferLength >= minLength) { + const powerOf2 = Math.min(Math.log(bufferLength) / Math.LN2 | 0, 30); + const chunkSize = 2 ** powerOf2; + const bufferConcat = util.concat([packetParser.writePartialLength(powerOf2)].concat(buffer)); + buffer = [bufferConcat.subarray(1 + chunkSize)]; + bufferLength = buffer[0].length; + return bufferConcat.subarray(0, 1 + chunkSize); } - arr.push(packetbytes); + }, () => util.concat([packetParser.writeSimpleLength(bufferLength)].concat(buffer)))); + } else { + if (util.isStream(packetbytes)) { + let length = 0; + arr.push(stream.transform(stream.clone(packetbytes), value => { + length += value.length; + }, () => packetParser.writeHeader(this[i].tag, length))); + } else { + arr.push(packetParser.writeHeader(this[i].tag, packetbytes.length)); } + arr.push(packetbytes); } - - return util.concat(arr); } - /** - * Adds a packet to the list. This is the only supported method of doing so; - * writing to packetlist[i] directly will result in an error. - * @param {Object} packet Packet to push - */ - push(packet) { - if (!packet) { - return; - } - - packet.packets = packet.packets || new PacketList(); + return util.concat(arr); +}; - super.push(packet); +/** + * Adds a packet to the list. This is the only supported method of doing so; + * writing to packetlist[i] directly will result in an error. + * @param {Object} packet Packet to push + */ +List.prototype.push = function (packet) { + if (!packet) { + return; } - /** - * Creates a new PacketList with all packets from the given types - */ - filterByTag(...args) { - const filtered = new PacketList(); + packet.packets = packet.packets || new List(); - const handle = tag => packetType => tag === packetType; + this[this.length] = packet; + this.length++; +}; - for (let i = 0; i < this.length; i++) { - if (args.some(handle(this[i].tag))) { - filtered.push(this[i]); - } - } +/** + * Creates a new PacketList with all packets from the given types + */ +List.prototype.filterByTag = function (...args) { + const filtered = new List(); - return filtered; - } + const handle = tag => packetType => tag === packetType; - /** - * Traverses packet tree and returns first matching packet - * @param {module:enums.packet} type The packet type - * @returns {module:packet/packet|undefined} - */ - findPacket(type) { - return this.find(packet => packet.tag === type); + for (let i = 0; i < this.length; i++) { + if (args.some(handle(this[i].tag))) { + filtered.push(this[i]); + } } - /** - * Returns array of found indices by tag - */ - indexOfTag(...args) { - const tagIndex = []; - const that = this; + return filtered; +}; - const handle = tag => packetType => tag === packetType; +/** + * Traverses packet tree and returns first matching packet + * @param {module:enums.packet} type The packet type + * @returns {module:packet/packet|undefined} + */ +List.prototype.findPacket = function (type) { + return this.find(packet => packet.tag === type); +}; - for (let i = 0; i < this.length; i++) { - if (args.some(handle(that[i].tag))) { - tagIndex.push(i); - } +/** + * Returns array of found indices by tag + */ +List.prototype.indexOfTag = function (...args) { + const tagIndex = []; + const that = this; + + const handle = tag => packetType => tag === packetType; + + for (let i = 0; i < this.length; i++) { + if (args.some(handle(that[i].tag))) { + tagIndex.push(i); } - return tagIndex; } + return tagIndex; +}; - /** - * Concatenates packetlist or array of packets - */ - concat(packetlist) { - if (packetlist) { - for (let i = 0; i < packetlist.length; i++) { - this.push(packetlist[i]); - } +/** + * Concatenates packetlist or array of packets + */ +List.prototype.concat = function (packetlist) { + if (packetlist) { + for (let i = 0; i < packetlist.length; i++) { + this.push(packetlist[i]); } - return this; } -} + return this; +}; + +/** + * Allocate a new packetlist from structured packetlist clone + * See {@link https://w3c.github.io/html/infrastructure.html#safe-passing-of-structured-data} + * @param {Object} packetClone packetlist clone + * @returns {Object} new packetlist object with data from packetlist clone + */ +List.fromStructuredClone = function(packetlistClone) { + const packetlist = new List(); + for (let i = 0; i < packetlistClone.length; i++) { + const packet = packets.fromStructuredClone(packetlistClone[i]); + packetlist.push(packet); + if (packet.embeddedSignature) { + packet.embeddedSignature = packets.fromStructuredClone(packet.embeddedSignature); + } + if (packet.packets.length !== 0) { + packet.packets = this.fromStructuredClone(packet.packets); + } else { + packet.packets = new List(); + } + } + if (packetlistClone.stream) { + packetlist.stream = stream.transform(packetlistClone.stream, packet => packets.fromStructuredClone(packet)); + } + return packetlist; +}; -export default PacketList; +export default List; diff --git a/src/packet/public_key.js b/src/packet/public_key.js index 65ad0843..cc8415d4 100644 --- a/src/packet/public_key.js +++ b/src/packet/public_key.js @@ -15,19 +15,19 @@ // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -/* eslint class-methods-use-this: ["error", { "exceptMethods": ["isDecrypted"] }] */ - /** * @requires type/keyid + * @requires type/mpi * @requires config * @requires crypto * @requires enums * @requires util */ -import { Sha1 } from 'asmcrypto.js/dist_es8/hash/sha1/sha1'; -import { Sha256 } from 'asmcrypto.js/dist_es8/hash/sha256/sha256'; +import { Sha1 } from 'asmcrypto.js/dist_es5/hash/sha1/sha1'; +import { Sha256 } from 'asmcrypto.js/dist_es5/hash/sha256/sha256'; import type_keyid from '../type/keyid'; +import type_mpi from '../type/mpi'; import config from '../config'; import crypto from '../crypto'; import enums from '../enums'; @@ -44,219 +44,237 @@ import util from '../util'; * A Public-Key packet starts a series of packets that forms an OpenPGP * key (sometimes called an OpenPGP certificate). * @memberof module:packet + * @constructor */ -class PublicKeyPacket { - constructor(date = new Date()) { - /** - * Packet type - * @type {module:enums.packet} - */ - this.tag = enums.packet.publicKey; - /** - * Packet version - * @type {Integer} - */ - this.version = config.v5Keys ? 5 : 4; - /** - * Key creation date. - * @type {Date} - */ - this.created = util.normalizeDate(date); - /** - * Public key algorithm. - * @type {String} - */ - this.algorithm = null; - /** - * Algorithm specific public params - * @type {Object} - */ - this.publicParams = null; - /** - * Time until expiration in days (V3 only) - * @type {Integer} - */ - this.expirationTimeV3 = 0; - /** - * Fingerprint in lowercase hex - * @type {String} - */ - this.fingerprint = null; - /** - * Keyid - * @type {module:type/keyid} - */ - this.keyid = null; - } - +function PublicKey(date = new Date()) { /** - * Internal Parser for public keys as specified in {@link https://tools.ietf.org/html/rfc4880#section-5.5.2|RFC 4880 section 5.5.2 Public-Key Packet Formats} - * called by read_tag<num> - * @param {Uint8Array} bytes Input array to read the packet from - * @returns {Object} This object with attributes set by the parser + * Packet type + * @type {module:enums.packet} */ - read(bytes) { - let pos = 0; - // A one-octet version number (3, 4 or 5). - this.version = bytes[pos++]; - - if (this.version === 4 || this.version === 5) { - // - A four-octet number denoting the time that the key was created. - this.created = util.readDate(bytes.subarray(pos, pos + 4)); - pos += 4; - - // - A one-octet number denoting the public-key algorithm of this key. - this.algorithm = enums.read(enums.publicKey, bytes[pos++]); - const algo = enums.write(enums.publicKey, this.algorithm); - - if (this.version === 5) { - // - A four-octet scalar octet count for the following key material. - pos += 4; - } + this.tag = enums.packet.publicKey; + /** + * Packet version + * @type {Integer} + */ + this.version = config.v5_keys ? 5 : 4; + /** + * Key creation date. + * @type {Date} + */ + this.created = util.normalizeDate(date); + /** + * Public key algorithm. + * @type {String} + */ + this.algorithm = null; + /** + * Algorithm specific params + * @type {Array} + */ + this.params = []; + /** + * Time until expiration in days (V3 only) + * @type {Integer} + */ + this.expirationTimeV3 = 0; + /** + * Fingerprint in lowercase hex + * @type {String} + */ + this.fingerprint = null; + /** + * Keyid + * @type {module:type/keyid} + */ + this.keyid = null; +} - // - A series of values comprising the key material. - try { - const { read, publicParams } = crypto.parsePublicKeyParams(algo, bytes.subarray(pos)); - this.publicParams = publicParams; - pos += read; - } catch (err) { - throw new Error('Error reading MPIs'); - } +/** + * Internal Parser for public keys as specified in {@link https://tools.ietf.org/html/rfc4880#section-5.5.2|RFC 4880 section 5.5.2 Public-Key Packet Formats} + * called by read_tag<num> + * @param {Uint8Array} bytes Input array to read the packet from + * @returns {Object} This object with attributes set by the parser + */ +PublicKey.prototype.read = function (bytes) { + let pos = 0; + // A one-octet version number (3, 4 or 5). + this.version = bytes[pos++]; - return pos; - } - throw new Error('Version ' + this.version + ' of the key packet is unsupported.'); - } + if (this.version === 4 || this.version === 5) { + // - A four-octet number denoting the time that the key was created. + this.created = util.readDate(bytes.subarray(pos, pos + 4)); + pos += 4; - /** - * Creates an OpenPGP public key packet for the given key. - * @returns {Uint8Array} Bytes encoding the public key OpenPGP packet - */ - write() { - const arr = []; - // Version - arr.push(new Uint8Array([this.version])); - arr.push(util.writeDate(this.created)); - // A one-octet number denoting the public-key algorithm of this key + // - A one-octet number denoting the public-key algorithm of this key. + this.algorithm = enums.read(enums.publicKey, bytes[pos++]); const algo = enums.write(enums.publicKey, this.algorithm); - arr.push(new Uint8Array([algo])); - const params = crypto.serializeParams(algo, this.publicParams); if (this.version === 5) { - // A four-octet scalar octet count for the following key material - arr.push(util.writeNumber(params.length, 4)); + // - A four-octet scalar octet count for the following key material. + pos += 4; } - // Algorithm-specific params - arr.push(params); - return util.concatUint8Array(arr); - } - /** - * Write packet in order to be hashed; either for a signature or a fingerprint. - */ - writeForHash(version) { - const bytes = this.writePublicKey(); + // - A series of values comprising the key material. This is + // algorithm-specific and described in section XXXX. + const types = crypto.getPubKeyParamTypes(algo); + this.params = crypto.constructParams(types); - if (version === 5) { - return util.concatUint8Array([new Uint8Array([0x9A]), util.writeNumber(bytes.length, 4), bytes]); + for (let i = 0; i < types.length && pos < bytes.length; i++) { + pos += this.params[i].read(bytes.subarray(pos, bytes.length)); + if (pos > bytes.length) { + throw new Error('Error reading MPI @:' + pos); + } } - return util.concatUint8Array([new Uint8Array([0x99]), util.writeNumber(bytes.length, 2), bytes]); + + return pos; } + throw new Error('Version ' + this.version + ' of the key packet is unsupported.'); +}; - /** - * Check whether secret-key data is available in decrypted form. Returns null for public keys. - * @returns {Boolean|null} - */ - isDecrypted() { - return null; +/** + * Alias of read() + * @see module:packet.PublicKey#read + */ +PublicKey.prototype.readPublicKey = PublicKey.prototype.read; + +/** + * Same as write_private_key, but has less information because of + * public key. + * @returns {Uint8Array} OpenPGP packet body contents, + */ +PublicKey.prototype.write = function () { + const arr = []; + // Version + arr.push(new Uint8Array([this.version])); + arr.push(util.writeDate(this.created)); + // A one-octet number denoting the public-key algorithm of this key + const algo = enums.write(enums.publicKey, this.algorithm); + arr.push(new Uint8Array([algo])); + + const paramCount = crypto.getPubKeyParamTypes(algo).length; + const params = util.concatUint8Array(this.params.slice(0, paramCount).map(param => param.write())); + if (this.version === 5) { + // A four-octet scalar octet count for the following key material + arr.push(util.writeNumber(params.length, 4)); } + // Algorithm-specific params + arr.push(params); + return util.concatUint8Array(arr); +}; - /** - * Returns the creation time of the key - * @returns {Date} - */ - getCreationTime() { - return this.created; +/** + * Alias of write() + * @see module:packet.PublicKey#write + */ +PublicKey.prototype.writePublicKey = PublicKey.prototype.write; + +/** + * Write packet in order to be hashed; either for a signature or a fingerprint. + */ +PublicKey.prototype.writeForHash = function (version) { + const bytes = this.writePublicKey(); + + if (version === 5) { + return util.concatUint8Array([new Uint8Array([0x9A]), util.writeNumber(bytes.length, 4), bytes]); } + return util.concatUint8Array([new Uint8Array([0x99]), util.writeNumber(bytes.length, 2), bytes]); +}; - /** - * Calculates the key id of the key - * @returns {module:type/keyid} A 8 byte key id - */ - getKeyId() { - if (this.keyid) { - return this.keyid; - } - this.keyid = new type_keyid(); - if (this.version === 5) { - this.keyid.read(util.hexToUint8Array(this.getFingerprint()).subarray(0, 8)); - } else if (this.version === 4) { - this.keyid.read(util.hexToUint8Array(this.getFingerprint()).subarray(12, 20)); - } +/** + * Check whether secret-key data is available in decrypted form. Returns null for public keys. + * @returns {Boolean|null} + */ +PublicKey.prototype.isDecrypted = function() { + return null; +}; + +/** + * Returns the creation time of the key + * @returns {Date} + */ +PublicKey.prototype.getCreationTime = function() { + return this.created; +}; + +/** + * Calculates the key id of the key + * @returns {module:type/keyid} A 8 byte key id + */ +PublicKey.prototype.getKeyId = function () { + if (this.keyid) { return this.keyid; } + this.keyid = new type_keyid(); + if (this.version === 5) { + this.keyid.read(util.hex_to_Uint8Array(this.getFingerprint()).subarray(0, 8)); + } else if (this.version === 4) { + this.keyid.read(util.hex_to_Uint8Array(this.getFingerprint()).subarray(12, 20)); + } + return this.keyid; +}; - /** - * Calculates the fingerprint of the key - * @returns {Uint8Array} A Uint8Array containing the fingerprint - */ - getFingerprintBytes() { - if (this.fingerprint) { - return this.fingerprint; - } - const toHash = this.writeForHash(this.version); - if (this.version === 5) { - this.fingerprint = Sha256.bytes(toHash); - } else if (this.version === 4) { - this.fingerprint = Sha1.bytes(toHash); - } +/** + * Calculates the fingerprint of the key + * @returns {Uint8Array} A Uint8Array containing the fingerprint + */ +PublicKey.prototype.getFingerprintBytes = function () { + if (this.fingerprint) { return this.fingerprint; } - - /** - * Calculates the fingerprint of the key - * @returns {String} A string containing the fingerprint in lowercase hex - */ - getFingerprint() { - return util.uint8ArrayToHex(this.getFingerprintBytes()); + const toHash = this.writeForHash(this.version); + if (this.version === 5) { + this.fingerprint = Sha256.bytes(toHash); + } else if (this.version === 4) { + this.fingerprint = Sha1.bytes(toHash); } + return this.fingerprint; +}; - /** - * Calculates whether two keys have the same fingerprint without actually calculating the fingerprint - * @returns {Boolean} Whether the two keys have the same version and public key data - */ - hasSameFingerprintAs(other) { - return this.version === other.version && util.equalsUint8Array(this.writePublicKey(), other.writePublicKey()); - } +/** + * Calculates the fingerprint of the key + * @returns {String} A string containing the fingerprint in lowercase hex + */ +PublicKey.prototype.getFingerprint = function() { + return util.Uint8Array_to_hex(this.getFingerprintBytes()); +}; - /** - * Returns algorithm information - * @returns {Object} An object of the form {algorithm: String, bits:int, curve:String} - */ - getAlgorithmInfo() { - const result = {}; - result.algorithm = this.algorithm; - // RSA, DSA or ElGamal public modulo - const modulo = this.publicParams.n || this.publicParams.p; - if (modulo) { - result.bits = modulo.length * 8; - } else { - result.curve = this.publicParams.oid.getName(); - } - return result; - } -} +/** + * Calculates whether two keys have the same fingerprint without actually calculating the fingerprint + * @returns {Boolean} Whether the two keys have the same version and public key data + */ +PublicKey.prototype.hasSameFingerprintAs = function(other) { + return this.version === other.version && util.equalsUint8Array(this.writePublicKey(), other.writePublicKey()); +}; /** - * Alias of read() - * @see PublicKeyPacket#read + * Returns algorithm information + * @returns {Object} An object of the form {algorithm: String, rsaBits:int, curve:String} */ -PublicKeyPacket.prototype.readPublicKey = PublicKeyPacket.prototype.read; +PublicKey.prototype.getAlgorithmInfo = function () { + const result = {}; + result.algorithm = this.algorithm; + if (this.params[0] instanceof type_mpi) { + result.rsaBits = this.params[0].byteLength() * 8; + result.bits = result.rsaBits; // Deprecated. + } else { + result.curve = this.params[0].getName(); + } + return result; +}; /** - * Alias of write() - * @see PublicKeyPacket#write + * Fix custom types after cloning */ -PublicKeyPacket.prototype.writePublicKey = PublicKeyPacket.prototype.write; +PublicKey.prototype.postCloneTypeFix = function() { + const algo = enums.write(enums.publicKey, this.algorithm); + const types = crypto.getPubKeyParamTypes(algo); + for (let i = 0; i < types.length; i++) { + const param = this.params[i]; + this.params[i] = types[i].fromClone(param); + } + if (this.keyid) { + this.keyid = type_keyid.fromClone(this.keyid); + } +}; -export default PublicKeyPacket; +export default PublicKey; diff --git a/src/packet/public_key_encrypted_session_key.js b/src/packet/public_key_encrypted_session_key.js index f414007a..87e3396b 100644 --- a/src/packet/public_key_encrypted_session_key.js +++ b/src/packet/public_key_encrypted_session_key.js @@ -17,6 +17,7 @@ /** * @requires type/keyid + * @requires type/mpi * @requires crypto * @requires enums * @requires util @@ -43,99 +44,118 @@ import util from '../util'; * public key, decrypts the session key, and then uses the session key to * decrypt the message. * @memberof module:packet + * @constructor */ -class PublicKeyEncryptedSessionKeyPacket { - constructor() { - this.tag = enums.packet.publicKeyEncryptedSessionKey; - this.version = 3; +function PublicKeyEncryptedSessionKey() { + this.tag = enums.packet.publicKeyEncryptedSessionKey; + this.version = 3; - this.publicKeyId = new type_keyid(); - this.publicKeyAlgorithm = null; + this.publicKeyId = new type_keyid(); + this.publicKeyAlgorithm = null; - this.sessionKey = null; - this.sessionKeyAlgorithm = null; + this.sessionKey = null; + this.sessionKeyAlgorithm = null; - /** @type {Object} */ - this.encrypted = {}; + /** @type {Array} */ + this.encrypted = []; +} + +/** + * Parsing function for a publickey encrypted session key packet (tag 1). + * + * @param {Uint8Array} input Payload of a tag 1 packet + * @param {Integer} position Position to start reading from the input string + * @param {Integer} len Length of the packet or the remaining length of + * input at position + * @returns {module:packet.PublicKeyEncryptedSessionKey} Object representation + */ +PublicKeyEncryptedSessionKey.prototype.read = function (bytes) { + this.version = bytes[0]; + this.publicKeyId.read(bytes.subarray(1, bytes.length)); + this.publicKeyAlgorithm = enums.read(enums.publicKey, bytes[9]); + + let i = 10; + + const algo = enums.write(enums.publicKey, this.publicKeyAlgorithm); + const types = crypto.getEncSessionKeyParamTypes(algo); + this.encrypted = crypto.constructParams(types); + + for (let j = 0; j < types.length; j++) { + i += this.encrypted[j].read(bytes.subarray(i, bytes.length)); } +}; + +/** + * Create a string representation of a tag 1 packet + * + * @returns {Uint8Array} The Uint8Array representation + */ +PublicKeyEncryptedSessionKey.prototype.write = function () { + const arr = [new Uint8Array([this.version]), this.publicKeyId.write(), new Uint8Array([enums.write(enums.publicKey, this.publicKeyAlgorithm)])]; - /** - * Parsing function for a publickey encrypted session key packet (tag 1). - * - * @param {Uint8Array} bytes Payload of a tag 1 packet - */ - read(bytes) { - this.version = bytes[0]; - this.publicKeyId.read(bytes.subarray(1, bytes.length)); - this.publicKeyAlgorithm = enums.read(enums.publicKey, bytes[9]); - - const algo = enums.write(enums.publicKey, this.publicKeyAlgorithm); - this.encrypted = crypto.parseEncSessionKeyParams(algo, bytes.subarray(10)); + for (let i = 0; i < this.encrypted.length; i++) { + arr.push(this.encrypted[i].write()); } - /** - * Create a binary representation of a tag 1 packet - * - * @returns {Uint8Array} The Uint8Array representation - */ - write() { - const algo = enums.write(enums.publicKey, this.publicKeyAlgorithm); - - const arr = [ - new Uint8Array([this.version]), - this.publicKeyId.write(), - new Uint8Array([enums.write(enums.publicKey, this.publicKeyAlgorithm)]), - crypto.serializeParams(algo, this.encrypted) - ]; - - return util.concatUint8Array(arr); + return util.concatUint8Array(arr); +}; + +/** + * Encrypt session key packet + * @param {module:packet.PublicKey} key Public key + * @returns {Promise} + * @async + */ +PublicKeyEncryptedSessionKey.prototype.encrypt = async function (key) { + let data = String.fromCharCode(enums.write(enums.symmetric, this.sessionKeyAlgorithm)); + + data += util.Uint8Array_to_str(this.sessionKey); + data += util.Uint8Array_to_str(util.write_checksum(this.sessionKey)); + const algo = enums.write(enums.publicKey, this.publicKeyAlgorithm); + this.encrypted = await crypto.publicKeyEncrypt( + algo, key.params, data, key.getFingerprintBytes()); + return true; +}; + +/** + * Decrypts the session key (only for public key encrypted session key + * packets (tag 1) + * + * @param {module:packet.SecretKey} key + * Private key with secret params unlocked + * @returns {Promise} + * @async + */ +PublicKeyEncryptedSessionKey.prototype.decrypt = async function (key) { + const algo = enums.write(enums.publicKey, this.publicKeyAlgorithm); + const keyAlgo = enums.write(enums.publicKey, key.algorithm); + // check that session key algo matches the secret key algo + if (algo !== keyAlgo) { + throw new Error('Decryption error'); } + const decoded = await crypto.publicKeyDecrypt(algo, key.params, this.encrypted, key.getFingerprintBytes()); + const checksum = util.str_to_Uint8Array(decoded.substr(decoded.length - 2)); + key = util.str_to_Uint8Array(decoded.substring(1, decoded.length - 2)); - /** - * Encrypt session key packet - * @param {PublicKeyPacket} key Public key - * @returns {Promise} - * @async - */ - async encrypt(key) { - const data = util.concatUint8Array([ - new Uint8Array([enums.write(enums.symmetric, this.sessionKeyAlgorithm)]), - this.sessionKey, - util.writeChecksum(this.sessionKey) - ]); - const algo = enums.write(enums.publicKey, this.publicKeyAlgorithm); - this.encrypted = await crypto.publicKeyEncrypt( - algo, key.publicParams, data, key.getFingerprintBytes()); - return true; + if (!util.equalsUint8Array(checksum, util.write_checksum(key))) { + throw new Error('Decryption error'); + } else { + this.sessionKey = key; + this.sessionKeyAlgorithm = enums.read(enums.symmetric, decoded.charCodeAt(0)); } + return true; +}; - /** - * Decrypts the session key (only for public key encrypted session key - * packets (tag 1) - * - * @param {SecretKeyPacket} key - * Private key with secret params unlocked - * @returns {Promise} - * @async - */ - async decrypt(key) { - const algo = enums.write(enums.publicKey, this.publicKeyAlgorithm); - const keyAlgo = enums.write(enums.publicKey, key.algorithm); - // check that session key algo matches the secret key algo - if (algo !== keyAlgo) { - throw new Error('Decryption error'); - } - const decoded = await crypto.publicKeyDecrypt(algo, key.publicParams, key.privateParams, this.encrypted, key.getFingerprintBytes()); - const checksum = decoded.subarray(decoded.length - 2); - const sessionKey = decoded.subarray(1, decoded.length - 2); - if (!util.equalsUint8Array(checksum, util.writeChecksum(sessionKey))) { - throw new Error('Decryption error'); - } else { - this.sessionKey = sessionKey; - this.sessionKeyAlgorithm = enums.read(enums.symmetric, decoded[0]); - } - return true; +/** + * Fix custom types after cloning + */ +PublicKeyEncryptedSessionKey.prototype.postCloneTypeFix = function() { + this.publicKeyId = type_keyid.fromClone(this.publicKeyId); + const algo = enums.write(enums.publicKey, this.publicKeyAlgorithm); + const types = crypto.getEncSessionKeyParamTypes(algo); + for (let i = 0; i < this.encrypted.length; i++) { + this.encrypted[i] = types[i].fromClone(this.encrypted[i]); } -} +}; -export default PublicKeyEncryptedSessionKeyPacket; +export default PublicKeyEncryptedSessionKey; diff --git a/src/packet/public_subkey.js b/src/packet/public_subkey.js index dec082f0..4472d088 100644 --- a/src/packet/public_subkey.js +++ b/src/packet/public_subkey.js @@ -20,7 +20,7 @@ * @requires enums */ -import PublicKeyPacket from './public_key'; +import PublicKey from './public_key'; import enums from '../enums'; /** @@ -30,13 +30,15 @@ import enums from '../enums'; * provides signature services, and the subkeys provide encryption * services. * @memberof module:packet - * @extends PublicKeyPacket + * @constructor + * @extends module:packet.PublicKey */ -class PublicSubkeyPacket extends PublicKeyPacket { - constructor() { - super(); - this.tag = enums.packet.publicSubkey; - } +function PublicSubkey() { + PublicKey.call(this); + this.tag = enums.packet.publicSubkey; } -export default PublicSubkeyPacket; +PublicSubkey.prototype = new PublicKey(); +PublicSubkey.prototype.constructor = PublicSubkey; + +export default PublicSubkey; diff --git a/src/packet/secret_key.js b/src/packet/secret_key.js index 48f2643e..8258c619 100644 --- a/src/packet/secret_key.js +++ b/src/packet/secret_key.js @@ -24,7 +24,8 @@ * @requires util */ -import PublicKeyPacket from './public_key'; +import PublicKey from './public_key'; +import type_keyid from '../type/keyid.js'; import type_s2k from '../type/s2k'; import crypto from '../crypto'; import enums from '../enums'; @@ -35,405 +36,436 @@ import util from '../util'; * Public-Key packet, including the public-key material, but also * includes the secret-key material after all the public-key fields. * @memberof module:packet - * @extends PublicKeyPacket + * @constructor + * @extends module:packet.PublicKey */ -class SecretKeyPacket extends PublicKeyPacket { - constructor(date = new Date()) { - super(date); - /** - * Packet type - * @type {module:enums.packet} - */ - this.tag = enums.packet.secretKey; - /** - * Secret-key data - */ - this.keyMaterial = null; - /** - * Indicates whether secret-key data is encrypted. `this.isEncrypted === false` means data is available in decrypted form. - */ - this.isEncrypted = null; - /** - * S2K usage - * @type {Integer} - */ - this.s2k_usage = 0; - /** - * S2K object - * @type {type/s2k} - */ - this.s2k = null; - /** - * Symmetric algorithm - * @type {String} - */ - this.symmetric = null; - /** - * AEAD algorithm - * @type {String} - */ - this.aead = null; - /** - * Decrypted private parameters, referenced by name - * @type {Object} - */ - this.privateParams = null; - } - - // 5.5.3. Secret-Key Packet Formats - +function SecretKey(date = new Date()) { + PublicKey.call(this, date); /** - * Internal parser for private keys as specified in - * {@link https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-04#section-5.5.3|RFC4880bis-04 section 5.5.3} - * @param {String} bytes Input string to read the packet from + * Packet type + * @type {module:enums.packet} */ - read(bytes) { - // - A Public-Key or Public-Subkey packet, as described above. - let i = this.readPublicKey(bytes); - - // - One octet indicating string-to-key usage conventions. Zero - // indicates that the secret-key data is not encrypted. 255 or 254 - // indicates that a string-to-key specifier is being given. Any - // other value is a symmetric-key encryption algorithm identifier. - this.s2k_usage = bytes[i++]; - - // - Only for a version 5 packet, a one-octet scalar octet count of - // the next 4 optional fields. - if (this.version === 5) { - i++; - } - - // - [Optional] If string-to-key usage octet was 255, 254, or 253, a - // one-octet symmetric encryption algorithm. - if (this.s2k_usage === 255 || this.s2k_usage === 254 || this.s2k_usage === 253) { - this.symmetric = bytes[i++]; - this.symmetric = enums.read(enums.symmetric, this.symmetric); - - // - [Optional] If string-to-key usage octet was 253, a one-octet - // AEAD algorithm. - if (this.s2k_usage === 253) { - this.aead = bytes[i++]; - this.aead = enums.read(enums.aead, this.aead); - } + this.tag = enums.packet.secretKey; + /** + * Secret-key data + */ + this.keyMaterial = null; + /** + * Indicates whether secret-key data is encrypted. `this.isEncrypted === false` means data is available in decrypted form. + */ + this.isEncrypted = null; + /** + * S2K usage + * @type {Integer} + */ + this.s2k_usage = 0; + /** + * S2K object + * @type {type/s2k} + */ + this.s2k = null; + /** + * Symmetric algorithm + * @type {String} + */ + this.symmetric = null; + /** + * AEAD algorithm + * @type {String} + */ + this.aead = null; +} - // - [Optional] If string-to-key usage octet was 255, 254, or 253, a - // string-to-key specifier. The length of the string-to-key - // specifier is implied by its type, as described above. - this.s2k = new type_s2k(); - i += this.s2k.read(bytes.subarray(i, bytes.length)); +SecretKey.prototype = new PublicKey(); +SecretKey.prototype.constructor = SecretKey; - if (this.s2k.type === 'gnu-dummy') { - return; - } - } else if (this.s2k_usage) { - this.symmetric = this.s2k_usage; - this.symmetric = enums.read(enums.symmetric, this.symmetric); - } +// Helper function - // - [Optional] If secret data is encrypted (string-to-key usage octet - // not zero), an Initial Vector (IV) of the same length as the - // cipher's block size. - if (this.s2k_usage) { - this.iv = bytes.subarray( - i, - i + crypto.cipher[this.symmetric].blockSize - ); +function parse_cleartext_params(cleartext, algorithm) { + const algo = enums.write(enums.publicKey, algorithm); + const types = crypto.getPrivKeyParamTypes(algo); + const params = crypto.constructParams(types); + let p = 0; - i += this.iv.length; + for (let i = 0; i < types.length && p < cleartext.length; i++) { + p += params[i].read(cleartext.subarray(p, cleartext.length)); + if (p > cleartext.length) { + throw new Error('Error reading param @:' + p); } + } - // - Only for a version 5 packet, a four-octet scalar octet count for - // the following key material. - if (this.version === 5) { - i += 4; - } + return params; +} - // - Plain or encrypted multiprecision integers comprising the secret - // key data. These algorithm-specific fields are as described - // below. - this.keyMaterial = bytes.subarray(i); - this.isEncrypted = !!this.s2k_usage; +function write_cleartext_params(params, algorithm) { + const arr = []; + const algo = enums.write(enums.publicKey, algorithm); + const numPublicParams = crypto.getPubKeyParamTypes(algo).length; - if (!this.isEncrypted) { - const cleartext = this.keyMaterial.subarray(0, -2); - if (!util.equalsUint8Array(util.writeChecksum(cleartext), this.keyMaterial.subarray(-2))) { - throw new Error('Key checksum mismatch'); - } - try { - const algo = enums.write(enums.publicKey, this.algorithm); - const { privateParams } = crypto.parsePrivateKeyParams(algo, cleartext, this.publicParams); - this.privateParams = privateParams; - } catch (err) { - throw new Error('Error reading MPIs'); - } - } + for (let i = numPublicParams; i < params.length; i++) { + arr.push(params[i].write()); } - /** - * Creates an OpenPGP key packet for the given key. - * @returns {Uint8Array} A string of bytes containing the secret key OpenPGP packet - */ - write() { - const arr = [this.writePublicKey()]; + return util.concatUint8Array(arr); +} - arr.push(new Uint8Array([this.s2k_usage])); - const optionalFieldsArr = []; - // - [Optional] If string-to-key usage octet was 255, 254, or 253, a - // one- octet symmetric encryption algorithm. - if (this.s2k_usage === 255 || this.s2k_usage === 254 || this.s2k_usage === 253) { - optionalFieldsArr.push(enums.write(enums.symmetric, this.symmetric)); - - // - [Optional] If string-to-key usage octet was 253, a one-octet - // AEAD algorithm. - if (this.s2k_usage === 253) { - optionalFieldsArr.push(enums.write(enums.aead, this.aead)); - } +// 5.5.3. Secret-Key Packet Formats - // - [Optional] If string-to-key usage octet was 255, 254, or 253, a - // string-to-key specifier. The length of the string-to-key - // specifier is implied by its type, as described above. - optionalFieldsArr.push(...this.s2k.write()); - } +/** + * Internal parser for private keys as specified in + * {@link https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-04#section-5.5.3|RFC4880bis-04 section 5.5.3} + * @param {String} bytes Input string to read the packet from + */ +SecretKey.prototype.read = function (bytes) { + // - A Public-Key or Public-Subkey packet, as described above. + let i = this.readPublicKey(bytes); + + // - One octet indicating string-to-key usage conventions. Zero + // indicates that the secret-key data is not encrypted. 255 or 254 + // indicates that a string-to-key specifier is being given. Any + // other value is a symmetric-key encryption algorithm identifier. + this.s2k_usage = bytes[i++]; + + // - Only for a version 5 packet, a one-octet scalar octet count of + // the next 4 optional fields. + if (this.version === 5) { + i++; + } - // - [Optional] If secret data is encrypted (string-to-key usage octet - // not zero), an Initial Vector (IV) of the same length as the - // cipher's block size. - if (this.s2k_usage && this.s2k.type !== 'gnu-dummy') { - optionalFieldsArr.push(...this.iv); - } + // - [Optional] If string-to-key usage octet was 255, 254, or 253, a + // one-octet symmetric encryption algorithm. + if (this.s2k_usage === 255 || this.s2k_usage === 254 || this.s2k_usage === 253) { + this.symmetric = bytes[i++]; + this.symmetric = enums.read(enums.symmetric, this.symmetric); - if (this.version === 5) { - arr.push(new Uint8Array([optionalFieldsArr.length])); + // - [Optional] If string-to-key usage octet was 253, a one-octet + // AEAD algorithm. + if (this.s2k_usage === 253) { + this.aead = bytes[i++]; + this.aead = enums.read(enums.aead, this.aead); } - arr.push(new Uint8Array(optionalFieldsArr)); - - if (!this.isDummy()) { - if (!this.s2k_usage) { - const algo = enums.write(enums.publicKey, this.algorithm); - const cleartextParams = crypto.serializeParams(algo, this.privateParams); - this.keyMaterial = util.concatUint8Array([ - cleartextParams, - util.writeChecksum(cleartextParams) - ]); - } - if (this.version === 5) { - arr.push(util.writeNumber(this.keyMaterial.length, 4)); - } - arr.push(this.keyMaterial); - } + // - [Optional] If string-to-key usage octet was 255, 254, or 253, a + // string-to-key specifier. The length of the string-to-key + // specifier is implied by its type, as described above. + this.s2k = new type_s2k(); + i += this.s2k.read(bytes.subarray(i, bytes.length)); - return util.concatUint8Array(arr); + if (this.s2k.type === 'gnu-dummy') { + return; + } + } else if (this.s2k_usage) { + this.symmetric = this.s2k_usage; + this.symmetric = enums.read(enums.symmetric, this.symmetric); } - /** - * Check whether secret-key data is available in decrypted form. - * Returns false for gnu-dummy keys and null for public keys. - * @returns {Boolean|null} - */ - isDecrypted() { - return this.isEncrypted === false; + // - [Optional] If secret data is encrypted (string-to-key usage octet + // not zero), an Initial Vector (IV) of the same length as the + // cipher's block size. + if (this.s2k_usage) { + this.iv = bytes.subarray( + i, + i + crypto.cipher[this.symmetric].blockSize + ); + + i += this.iv.length; } - /** - * Check whether this is a gnu-dummy key - * @returns {Boolean} - */ - isDummy() { - return !!(this.s2k && this.s2k.type === 'gnu-dummy'); + // - Only for a version 5 packet, a four-octet scalar octet count for + // the following key material. + if (this.version === 5) { + i += 4; } - /** - * Remove private key material, converting the key to a dummy one. - * The resulting key cannot be used for signing/decrypting but can still verify signatures. - */ - makeDummy() { - if (this.isDummy()) { - return; - } - if (this.isDecrypted()) { - this.clearPrivateParams(); + // - Plain or encrypted multiprecision integers comprising the secret + // key data. These algorithm-specific fields are as described + // below. + this.keyMaterial = bytes.subarray(i); + this.isEncrypted = !!this.s2k_usage; + + if (!this.isEncrypted) { + const cleartext = this.keyMaterial.subarray(0, -2); + if (!util.equalsUint8Array(util.write_checksum(cleartext), this.keyMaterial.subarray(-2))) { + throw new Error('Key checksum mismatch'); } - this.isEncrypted = null; - this.keyMaterial = null; - this.s2k = new type_s2k(); - this.s2k.algorithm = 0; - this.s2k.c = 0; - this.s2k.type = 'gnu-dummy'; - this.s2k_usage = 254; - this.symmetric = 'aes256'; + const privParams = parse_cleartext_params(cleartext, this.algorithm); + this.params = this.params.concat(privParams); } +}; - /** - * Encrypt the payload. By default, we use aes256 and iterated, salted string - * to key specifier. If the key is in a decrypted state (isEncrypted === false) - * and the passphrase is empty or undefined, the key will be set as not encrypted. - * This can be used to remove passphrase protection after calling decrypt(). - * @param {String} passphrase - * @throws {Error} if encryption was not successful - * @async - */ - async encrypt(passphrase) { - if (this.isDummy()) { - return; - } +/** + * Creates an OpenPGP key packet for the given key. + * @returns {String} A string of bytes containing the secret key OpenPGP packet + */ +SecretKey.prototype.write = function () { + const arr = [this.writePublicKey()]; - if (!this.isDecrypted()) { - throw new Error('Key packet is already encrypted'); - } + arr.push(new Uint8Array([this.s2k_usage])); - if (this.isDecrypted() && !passphrase) { - this.s2k_usage = 0; - return; - } else if (!passphrase) { - throw new Error('The key must be decrypted before removing passphrase protection.'); + const optionalFieldsArr = []; + // - [Optional] If string-to-key usage octet was 255, 254, or 253, a + // one- octet symmetric encryption algorithm. + if (this.s2k_usage === 255 || this.s2k_usage === 254 || this.s2k_usage === 253) { + optionalFieldsArr.push(enums.write(enums.symmetric, this.symmetric)); + + // - [Optional] If string-to-key usage octet was 253, a one-octet + // AEAD algorithm. + if (this.s2k_usage === 253) { + optionalFieldsArr.push(enums.write(enums.aead, this.aead)); } - this.s2k = new type_s2k(); - this.s2k.salt = await crypto.random.getRandomBytes(8); - const algo = enums.write(enums.publicKey, this.algorithm); - const cleartext = crypto.serializeParams(algo, this.privateParams); - this.symmetric = 'aes256'; - const key = await produceEncryptionKey(this.s2k, passphrase, this.symmetric); - const blockLen = crypto.cipher[this.symmetric].blockSize; - this.iv = await crypto.random.getRandomBytes(blockLen); + // - [Optional] If string-to-key usage octet was 255, 254, or 253, a + // string-to-key specifier. The length of the string-to-key + // specifier is implied by its type, as described above. + optionalFieldsArr.push(...this.s2k.write()); + } - if (this.version === 5) { - this.s2k_usage = 253; - this.aead = 'eax'; - const mode = crypto[this.aead]; - const modeInstance = await mode(this.symmetric, key); - this.keyMaterial = await modeInstance.encrypt(cleartext, this.iv.subarray(0, mode.ivLength), new Uint8Array()); - } else { - this.s2k_usage = 254; - this.keyMaterial = await crypto.cfb.encrypt(this.symmetric, key, util.concatUint8Array([ - cleartext, - await crypto.hash.sha1(cleartext) - ]), this.iv); - } + // - [Optional] If secret data is encrypted (string-to-key usage octet + // not zero), an Initial Vector (IV) of the same length as the + // cipher's block size. + if (this.s2k_usage && this.s2k.type !== 'gnu-dummy') { + optionalFieldsArr.push(...this.iv); } - /** - * Decrypts the private key params which are needed to use the key. - * {@link SecretKeyPacket.isDecrypted} should be false, as - * otherwise calls to this function will throw an error. - * @param {String} passphrase The passphrase for this private key as string - * @throws {Error} if decryption was not successful - * @async - */ - async decrypt(passphrase) { - if (this.isDummy()) { - return false; + if (this.version === 5) { + arr.push(new Uint8Array([optionalFieldsArr.length])); + } + arr.push(new Uint8Array(optionalFieldsArr)); + + if (!this.isDummy()) { + if (!this.s2k_usage) { + const cleartextParams = write_cleartext_params(this.params, this.algorithm); + this.keyMaterial = util.concatUint8Array([ + cleartextParams, + util.write_checksum(cleartextParams) + ]); } - if (this.isDecrypted()) { - throw new Error('Key packet is already decrypted.'); + if (this.version === 5) { + arr.push(util.writeNumber(this.keyMaterial.length, 4)); } + arr.push(this.keyMaterial); + } - let key; - if (this.s2k_usage === 254 || this.s2k_usage === 253) { - key = await produceEncryptionKey(this.s2k, passphrase, this.symmetric); - } else if (this.s2k_usage === 255) { - throw new Error('Encrypted private key is authenticated using an insecure two-byte hash'); - } else { - throw new Error('Private key is encrypted using an insecure S2K function: unsalted MD5'); - } + return util.concatUint8Array(arr); +}; - let cleartext; - if (this.s2k_usage === 253) { - const mode = crypto[this.aead]; - try { - const modeInstance = await mode(this.symmetric, key); - cleartext = await modeInstance.decrypt(this.keyMaterial, this.iv.subarray(0, mode.ivLength), new Uint8Array()); - } catch (err) { - if (err.message === 'Authentication tag mismatch') { - throw new Error('Incorrect key passphrase: ' + err.message); - } - throw err; - } - } else { - const cleartextWithHash = await crypto.cfb.decrypt(this.symmetric, key, this.keyMaterial, this.iv); +/** + * Check whether secret-key data is available in decrypted form. Returns null for public keys. + * @returns {Boolean|null} + */ +SecretKey.prototype.isDecrypted = function() { + return this.isEncrypted === false; +}; - cleartext = cleartextWithHash.subarray(0, -20); - const hash = await crypto.hash.sha1(cleartext); +/** + * Check whether this is a gnu-dummy key + * @returns {Boolean} + */ +SecretKey.prototype.isDummy = function() { + return !!(this.s2k && this.s2k.type === 'gnu-dummy'); +}; - if (!util.equalsUint8Array(hash, cleartextWithHash.subarray(-20))) { - throw new Error('Incorrect key passphrase'); - } - } +/** + * Remove private key material, converting the key to a dummy one + * The resulting key cannot be used for signing/decrypting but can still verify signatures + */ +SecretKey.prototype.makeDummy = function () { + if (this.isDummy()) { + return; + } + if (!this.isDecrypted()) { + // this is technically not needed, but makes the conversion simpler + throw new Error("Key is not decrypted"); + } + this.clearPrivateParams(); + this.keyMaterial = null; + this.isEncrypted = false; + this.s2k = new type_s2k(); + this.s2k.algorithm = 0; + this.s2k.c = 0; + this.s2k.type = 'gnu-dummy'; + this.s2k_usage = 254; + this.symmetric = 'aes256'; +}; - try { - const algo = enums.write(enums.publicKey, this.algorithm); - const { privateParams } = crypto.parsePrivateKeyParams(algo, cleartext, this.publicParams); - this.privateParams = privateParams; - } catch (err) { - throw new Error('Error reading MPIs'); - } - this.isEncrypted = false; - this.keyMaterial = null; +/** + * Encrypt the payload. By default, we use aes256 and iterated, salted string + * to key specifier. If the key is in a decrypted state (isEncrypted === false) + * and the passphrase is empty or undefined, the key will be set as not encrypted. + * This can be used to remove passphrase protection after calling decrypt(). + * @param {String} passphrase + * @returns {Promise} + * @async + */ +SecretKey.prototype.encrypt = async function (passphrase) { + if (this.isDummy()) { + return false; + } + + if (!this.isDecrypted()) { + throw new Error('Key packet is already encrypted'); + } + + if (this.isDecrypted() && !passphrase) { this.s2k_usage = 0; + return false; + } else if (!passphrase) { + throw new Error('The key must be decrypted before removing passphrase protection.'); } - /** - * Checks that the key parameters are consistent - * @throws {Error} if validation was not successful - * @async - */ - async validate() { - if (this.isDummy()) { - return; - } + this.s2k = new type_s2k(); + this.s2k.salt = await crypto.random.getRandomBytes(8); + const cleartext = write_cleartext_params(this.params, this.algorithm); + this.symmetric = 'aes256'; + const key = await produceEncryptionKey(this.s2k, passphrase, this.symmetric); + const blockLen = crypto.cipher[this.symmetric].blockSize; + this.iv = await crypto.random.getRandomBytes(blockLen); + + if (this.version === 5) { + this.s2k_usage = 253; + this.aead = 'eax'; + const mode = crypto[this.aead]; + const modeInstance = await mode(this.symmetric, key); + this.keyMaterial = await modeInstance.encrypt(cleartext, this.iv.subarray(0, mode.ivLength), new Uint8Array()); + } else { + this.s2k_usage = 254; + this.keyMaterial = await crypto.cfb.encrypt(this.symmetric, key, util.concatUint8Array([ + cleartext, + await crypto.hash.sha1(cleartext) + ]), this.iv); + } + return true; +}; - if (!this.isDecrypted()) { - throw new Error('Key is not decrypted'); - } +async function produceEncryptionKey(s2k, passphrase, algorithm) { + return s2k.produce_key( + passphrase, + crypto.cipher[algorithm].keySize + ); +} + +/** + * Decrypts the private key params which are needed to use the key. + * {@link module:packet.SecretKey.isDecrypted} should be false, as + * otherwise calls to this function will throw an error. + * @param {String} passphrase The passphrase for this private key as string + * @returns {Promise} + * @async + */ +SecretKey.prototype.decrypt = async function (passphrase) { + if (this.isDummy()) { + this.isEncrypted = false; + return false; + } + + if (this.isDecrypted()) { + throw new Error('Key packet is already decrypted.'); + } - const algo = enums.write(enums.publicKey, this.algorithm); + let key; + if (this.s2k_usage === 254 || this.s2k_usage === 253) { + key = await produceEncryptionKey(this.s2k, passphrase, this.symmetric); + } else if (this.s2k_usage === 255) { + throw new Error('Encrypted private key is authenticated using an insecure two-byte hash'); + } else { + throw new Error('Private key is encrypted using an insecure S2K function: unsalted MD5'); + } - let validParams; + let cleartext; + if (this.s2k_usage === 253) { + const mode = crypto[this.aead]; try { - // this can throw if some parameters are undefined - validParams = await crypto.validateParams(algo, this.publicParams, this.privateParams); - } catch (_) { - validParams = false; + const modeInstance = await mode(this.symmetric, key); + cleartext = await modeInstance.decrypt(this.keyMaterial, this.iv.subarray(0, mode.ivLength), new Uint8Array()); + } catch (err) { + if (err.message === 'Authentication tag mismatch') { + throw new Error('Incorrect key passphrase: ' + err.message); + } + throw err; } - if (!validParams) { - throw new Error('Key is invalid'); + } else { + const cleartextWithHash = await crypto.cfb.decrypt(this.symmetric, key, this.keyMaterial, this.iv); + + cleartext = cleartextWithHash.subarray(0, -20); + const hash = await crypto.hash.sha1(cleartext); + + if (!util.equalsUint8Array(hash, cleartextWithHash.subarray(-20))) { + throw new Error('Incorrect key passphrase'); } } - async generate(bits, curve) { - const algo = enums.write(enums.publicKey, this.algorithm); - const { privateParams, publicParams } = await crypto.generateParams(algo, bits, curve); - this.privateParams = privateParams; - this.publicParams = publicParams; - this.isEncrypted = false; + const privParams = parse_cleartext_params(cleartext, this.algorithm); + this.params = this.params.concat(privParams); + this.isEncrypted = false; + this.keyMaterial = null; + this.s2k_usage = 0; + + return true; +}; + +SecretKey.prototype.generate = async function (bits, curve) { + const algo = enums.write(enums.publicKey, this.algorithm); + this.params = await crypto.generateParams(algo, bits, curve); + this.isEncrypted = false; +}; + +/** + * Checks that the key parameters are consistent + * @throws {Error} if validation was not successful + * @async + */ +SecretKey.prototype.validate = async function () { + if (this.isDummy()) { + return; } - /** - * Clear private key parameters - */ - clearPrivateParams() { - if (this.isDummy()) { - return; - } + if (!this.isDecrypted()) { + throw new Error('Key is not decrypted'); + } + + const algo = enums.write(enums.publicKey, this.algorithm); + const validParams = await crypto.validateParams(algo, this.params); + if (!validParams) { + throw new Error('Key is invalid'); + } +}; - Object.keys(this.privateParams).forEach(name => { - const param = this.privateParams[name]; - param.fill(0); - delete this.privateParams[name]; - }); - this.privateParams = null; +/** + * Clear private key parameters + */ +SecretKey.prototype.clearPrivateParams = function () { + if (this.s2k && this.s2k.type === 'gnu-dummy') { this.isEncrypted = true; + return; } -} -async function produceEncryptionKey(s2k, passphrase, algorithm) { - return s2k.produce_key( - passphrase, - crypto.cipher[algorithm].keySize - ); -} + const algo = enums.write(enums.publicKey, this.algorithm); + const publicParamCount = crypto.getPubKeyParamTypes(algo).length; + this.params.slice(publicParamCount).forEach(param => { + param.data.fill(0); + }); + this.params.length = publicParamCount; + this.isEncrypted = true; +}; + +/** + * Fix custom types after cloning + */ +SecretKey.prototype.postCloneTypeFix = function() { + const algo = enums.write(enums.publicKey, this.algorithm); + const types = [].concat(crypto.getPubKeyParamTypes(algo), crypto.getPrivKeyParamTypes(algo)); + for (let i = 0; i < this.params.length; i++) { + const param = this.params[i]; + this.params[i] = types[i].fromClone(param); + } + if (this.keyid) { + this.keyid = type_keyid.fromClone(this.keyid); + } + if (this.s2k) { + this.s2k = type_s2k.fromClone(this.s2k); + } +}; -export default SecretKeyPacket; +export default SecretKey; diff --git a/src/packet/secret_subkey.js b/src/packet/secret_subkey.js index 86904734..824b77b3 100644 --- a/src/packet/secret_subkey.js +++ b/src/packet/secret_subkey.js @@ -20,20 +20,22 @@ * @requires enums */ -import SecretKeyPacket from './secret_key'; +import SecretKey from './secret_key'; import enums from '../enums'; /** * A Secret-Subkey packet (tag 7) is the subkey analog of the Secret * Key packet and has exactly the same format. * @memberof module:packet - * @extends SecretKeyPacket + * @constructor + * @extends module:packet.SecretKey */ -class SecretSubkeyPacket extends SecretKeyPacket { - constructor(date = new Date()) { - super(date); - this.tag = enums.packet.secretSubkey; - } +function SecretSubkey(date = new Date()) { + SecretKey.call(this, date); + this.tag = enums.packet.secretSubkey; } -export default SecretSubkeyPacket; +SecretSubkey.prototype = new SecretKey(); +SecretSubkey.prototype.constructor = SecretSubkey; + +export default SecretSubkey; diff --git a/src/packet/signature.js b/src/packet/signature.js index 821a8d49..1e04357f 100644 --- a/src/packet/signature.js +++ b/src/packet/signature.js @@ -19,14 +19,16 @@ * @requires web-stream-tools * @requires packet/packet * @requires type/keyid + * @requires type/mpi * @requires crypto * @requires enums * @requires util */ import stream from 'web-stream-tools'; -import { readSimpleLength, writeSimpleLength } from './packet'; +import packet from './packet'; import type_keyid from '../type/keyid.js'; +import type_mpi from '../type/mpi.js'; import crypto from '../crypto'; import enums from '../enums'; import util from '../util'; @@ -40,723 +42,745 @@ import config from '../config'; * some data. The most common signatures are a signature of a file or a * block of text, and a signature that is a certification of a User ID. * @memberof module:packet + * @constructor + * @param {Date} date the creation date of the signature */ -class SignaturePacket { - /** - * @param {Date} date the creation date of the signature - */ - constructor(date = new Date()) { - this.tag = enums.packet.signature; - this.version = 4; // This is set to 5 below if we sign with a V5 key. - this.signatureType = null; - this.hashAlgorithm = null; - this.publicKeyAlgorithm = null; - - this.signatureData = null; - this.unhashedSubpackets = []; - this.signedHashValue = null; - - this.created = util.normalizeDate(date); - this.signatureExpirationTime = null; - this.signatureNeverExpires = true; - this.exportable = null; - this.trustLevel = null; - this.trustAmount = null; - this.regularExpression = null; - this.revocable = null; - this.keyExpirationTime = null; - this.keyNeverExpires = null; - this.preferredSymmetricAlgorithms = null; - this.revocationKeyClass = null; - this.revocationKeyAlgorithm = null; - this.revocationKeyFingerprint = null; - this.issuerKeyId = new type_keyid(); - this.rawNotations = []; - this.notations = {}; - this.preferredHashAlgorithms = null; - this.preferredCompressionAlgorithms = null; - this.keyServerPreferences = null; - this.preferredKeyServer = null; - this.isPrimaryUserID = null; - this.policyURI = null; - this.keyFlags = null; - this.signersUserId = null; - this.reasonForRevocationFlag = null; - this.reasonForRevocationString = null; - this.features = null; - this.signatureTargetPublicKeyAlgorithm = null; - this.signatureTargetHashAlgorithm = null; - this.signatureTargetHash = null; - this.embeddedSignature = null; - this.issuerKeyVersion = null; - this.issuerFingerprint = null; - this.preferredAeadAlgorithms = null; - - this.verified = null; - this.revoked = null; - } - - /** - * parsing function for a signature packet (tag 2). - * @param {String} bytes payload of a tag 2 packet - * @returns {SignaturePacket} object representation - */ - read(bytes) { - let i = 0; - this.version = bytes[i++]; - - if (this.version !== 4 && this.version !== 5) { - throw new Error('Version ' + this.version + ' of the signature is unsupported.'); - } +function Signature(date = new Date()) { + this.tag = enums.packet.signature; + this.version = 4; // This is set to 5 below if we sign with a V5 key. + this.signatureType = null; + this.hashAlgorithm = null; + this.publicKeyAlgorithm = null; + + this.signatureData = null; + this.unhashedSubpackets = []; + this.signedHashValue = null; + + this.created = util.normalizeDate(date); + this.signatureExpirationTime = null; + this.signatureNeverExpires = true; + this.exportable = null; + this.trustLevel = null; + this.trustAmount = null; + this.regularExpression = null; + this.revocable = null; + this.keyExpirationTime = null; + this.keyNeverExpires = null; + this.preferredSymmetricAlgorithms = null; + this.revocationKeyClass = null; + this.revocationKeyAlgorithm = null; + this.revocationKeyFingerprint = null; + this.issuerKeyId = new type_keyid(); + this.rawNotations = []; + this.notations = {}; + this.preferredHashAlgorithms = null; + this.preferredCompressionAlgorithms = null; + this.keyServerPreferences = null; + this.preferredKeyServer = null; + this.isPrimaryUserID = null; + this.policyURI = null; + this.keyFlags = null; + this.signersUserId = null; + this.reasonForRevocationFlag = null; + this.reasonForRevocationString = null; + this.features = null; + this.signatureTargetPublicKeyAlgorithm = null; + this.signatureTargetHashAlgorithm = null; + this.signatureTargetHash = null; + this.embeddedSignature = null; + this.issuerKeyVersion = null; + this.issuerFingerprint = null; + this.preferredAeadAlgorithms = null; + + this.verified = null; + this.revoked = null; +} - this.signatureType = bytes[i++]; - this.publicKeyAlgorithm = bytes[i++]; - this.hashAlgorithm = bytes[i++]; +/** + * parsing function for a signature packet (tag 2). + * @param {String} bytes payload of a tag 2 packet + * @param {Integer} position position to start reading from the bytes string + * @param {Integer} len length of the packet or the remaining length of bytes at position + * @returns {module:packet.Signature} object representation + */ +Signature.prototype.read = function (bytes) { + let i = 0; + this.version = bytes[i++]; - // hashed subpackets - i += this.read_sub_packets(bytes.subarray(i, bytes.length), true); + if (this.version !== 4 && this.version !== 5) { + throw new Error('Version ' + this.version + ' of the signature is unsupported.'); + } - // A V4 signature hashes the packet body - // starting from its first field, the version number, through the end - // of the hashed subpacket data. Thus, the fields hashed are the - // signature version, the signature type, the public-key algorithm, the - // hash algorithm, the hashed subpacket length, and the hashed - // subpacket body. - this.signatureData = bytes.subarray(0, i); + this.signatureType = bytes[i++]; + this.publicKeyAlgorithm = bytes[i++]; + this.hashAlgorithm = bytes[i++]; - // unhashed subpackets - i += this.read_sub_packets(bytes.subarray(i, bytes.length), false); + // hashed subpackets + i += this.read_sub_packets(bytes.subarray(i, bytes.length), true); - // Two-octet field holding left 16 bits of signed hash value. - this.signedHashValue = bytes.subarray(i, i + 2); - i += 2; + // A V4 signature hashes the packet body + // starting from its first field, the version number, through the end + // of the hashed subpacket data. Thus, the fields hashed are the + // signature version, the signature type, the public-key algorithm, the + // hash algorithm, the hashed subpacket length, and the hashed + // subpacket body. + this.signatureData = bytes.subarray(0, i); - this.params = crypto.signature.parseSignatureParams(this.publicKeyAlgorithm, bytes.subarray(i, bytes.length)); - } + // unhashed subpackets + i += this.read_sub_packets(bytes.subarray(i, bytes.length), false); - /** - * @returns {Uint8Array | ReadableStream} - */ - write_params() { - if (this.params instanceof Promise) { - return stream.fromAsync( - async () => crypto.serializeParams(this.publicKeyAlgorithm, await this.params) - ); - } - return crypto.serializeParams(this.publicKeyAlgorithm, this.params); - } + // Two-octet field holding left 16 bits of signed hash value. + this.signedHashValue = bytes.subarray(i, i + 2); + i += 2; - write() { - const arr = []; - arr.push(this.signatureData); - arr.push(this.write_unhashed_sub_packets()); - arr.push(this.signedHashValue); - arr.push(this.write_params()); - return util.concat(arr); + this.signature = bytes.subarray(i, bytes.length); +}; + +Signature.prototype.write = function () { + const arr = []; + arr.push(this.signatureData); + arr.push(this.write_unhashed_sub_packets()); + arr.push(this.signedHashValue); + arr.push(stream.clone(this.signature)); + return util.concat(arr); +}; + +/** + * Signs provided data. This needs to be done prior to serialization. + * @param {module:packet.SecretKey} key private key used to sign the message. + * @param {Object} data Contains packets to be signed. + * @param {Boolean} detached (optional) whether to create a detached signature + * @param {Boolean} streaming (optional) whether to process data as a stream + * @returns {Promise} + * @async + */ +Signature.prototype.sign = async function (key, data, detached = false, streaming = false) { + const signatureType = enums.write(enums.signature, this.signatureType); + const publicKeyAlgorithm = enums.write(enums.publicKey, this.publicKeyAlgorithm); + const hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm); + + if (key.version === 5) { + this.version = 5; + } + const arr = [new Uint8Array([this.version, signatureType, publicKeyAlgorithm, hashAlgorithm])]; + + this.issuerKeyVersion = key.version; + this.issuerFingerprint = key.getFingerprintBytes(); + this.issuerKeyId = key.getKeyId(); + + // Add hashed subpackets + arr.push(this.write_hashed_sub_packets()); + + this.signatureData = util.concat(arr); + + const toHash = this.toHash(signatureType, data, detached); + const hash = await this.hash(signatureType, data, toHash, detached); + + this.signedHashValue = stream.slice(stream.clone(hash), 0, 2); + const params = key.params; + const signed = async () => crypto.signature.sign( + publicKeyAlgorithm, hashAlgorithm, params, toHash, await stream.readToEnd(hash) + ); + if (streaming) { + this.signature = stream.fromAsync(signed); + } else { + this.signature = await signed(); + + // Store the fact that this signature is valid, e.g. for when we call `await + // getLatestValidSignature(this.revocationSignatures, key, data)` later. + // Note that this only holds up if the key and data passed to verify are the + // same as the ones passed to sign. + this.verified = true; } + return true; +}; - /** - * Signs provided data. This needs to be done prior to serialization. - * @param {SecretKeyPacket} key private key used to sign the message. - * @param {Object} data Contains packets to be signed. - * @param {Boolean} detached (optional) whether to create a detached signature - * @param {Boolean} streaming (optional) whether to process data as a stream - * @throws {Error} if signing failed - * @async - */ - async sign(key, data, detached = false, streaming = false) { - const signatureType = enums.write(enums.signature, this.signatureType); - const publicKeyAlgorithm = enums.write(enums.publicKey, this.publicKeyAlgorithm); - const hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm); - - if (key.version === 5) { - this.version = 5; - } - const arr = [new Uint8Array([this.version, signatureType, publicKeyAlgorithm, hashAlgorithm])]; - - this.issuerKeyVersion = key.version; - this.issuerFingerprint = key.getFingerprintBytes(); - this.issuerKeyId = key.getKeyId(); - - // Add hashed subpackets - arr.push(this.write_hashed_sub_packets()); - - this.signatureData = util.concat(arr); - - const toHash = this.toHash(signatureType, data, detached); - const hash = await this.hash(signatureType, data, toHash, detached); - - this.signedHashValue = stream.slice(stream.clone(hash), 0, 2); - const signed = async () => crypto.signature.sign( - publicKeyAlgorithm, hashAlgorithm, key.publicParams, key.privateParams, toHash, await stream.readToEnd(hash) - ); - if (streaming) { - this.params = signed(); - } else { - this.params = await signed(); - - // Store the fact that this signature is valid, e.g. for when we call `await - // getLatestValidSignature(this.revocationSignatures, key, data)` later. - // Note that this only holds up if the key and data passed to verify are the - // same as the ones passed to sign. - this.verified = true; - } +/** + * Creates Uint8Array of bytes of all subpacket data except Issuer and Embedded Signature subpackets + * @returns {Uint8Array} subpacket data + */ +Signature.prototype.write_hashed_sub_packets = function () { + const sub = enums.signatureSubpacket; + const arr = []; + let bytes; + if (this.created !== null) { + arr.push(write_sub_packet(sub.signature_creation_time, util.writeDate(this.created))); + } + if (this.signatureExpirationTime !== null) { + arr.push(write_sub_packet(sub.signature_expiration_time, util.writeNumber(this.signatureExpirationTime, 4))); + } + if (this.exportable !== null) { + arr.push(write_sub_packet(sub.exportable_certification, new Uint8Array([this.exportable ? 1 : 0]))); + } + if (this.trustLevel !== null) { + bytes = new Uint8Array([this.trustLevel, this.trustAmount]); + arr.push(write_sub_packet(sub.trust_signature, bytes)); + } + if (this.regularExpression !== null) { + arr.push(write_sub_packet(sub.regular_expression, this.regularExpression)); + } + if (this.revocable !== null) { + arr.push(write_sub_packet(sub.revocable, new Uint8Array([this.revocable ? 1 : 0]))); + } + if (this.keyExpirationTime !== null) { + arr.push(write_sub_packet(sub.key_expiration_time, util.writeNumber(this.keyExpirationTime, 4))); + } + if (this.preferredSymmetricAlgorithms !== null) { + bytes = util.str_to_Uint8Array(util.Uint8Array_to_str(this.preferredSymmetricAlgorithms)); + arr.push(write_sub_packet(sub.preferred_symmetric_algorithms, bytes)); + } + if (this.revocationKeyClass !== null) { + bytes = new Uint8Array([this.revocationKeyClass, this.revocationKeyAlgorithm]); + bytes = util.concat([bytes, this.revocationKeyFingerprint]); + arr.push(write_sub_packet(sub.revocation_key, bytes)); + } + this.rawNotations.forEach(([{ name, value, humanReadable }]) => { + bytes = [new Uint8Array([humanReadable ? 0x80 : 0, 0, 0, 0])]; + // 2 octets of name length + bytes.push(util.writeNumber(name.length, 2)); + // 2 octets of value length + bytes.push(util.writeNumber(value.length, 2)); + bytes.push(util.str_to_Uint8Array(name)); + bytes.push(value); + bytes = util.concat(bytes); + arr.push(write_sub_packet(sub.notation_data, bytes)); + }); + if (this.preferredHashAlgorithms !== null) { + bytes = util.str_to_Uint8Array(util.Uint8Array_to_str(this.preferredHashAlgorithms)); + arr.push(write_sub_packet(sub.preferred_hash_algorithms, bytes)); + } + if (this.preferredCompressionAlgorithms !== null) { + bytes = util.str_to_Uint8Array(util.Uint8Array_to_str(this.preferredCompressionAlgorithms)); + arr.push(write_sub_packet(sub.preferred_compression_algorithms, bytes)); + } + if (this.keyServerPreferences !== null) { + bytes = util.str_to_Uint8Array(util.Uint8Array_to_str(this.keyServerPreferences)); + arr.push(write_sub_packet(sub.key_server_preferences, bytes)); + } + if (this.preferredKeyServer !== null) { + arr.push(write_sub_packet(sub.preferred_key_server, util.str_to_Uint8Array(this.preferredKeyServer))); + } + if (this.isPrimaryUserID !== null) { + arr.push(write_sub_packet(sub.primary_user_id, new Uint8Array([this.isPrimaryUserID ? 1 : 0]))); + } + if (this.policyURI !== null) { + arr.push(write_sub_packet(sub.policy_uri, util.str_to_Uint8Array(this.policyURI))); + } + if (this.keyFlags !== null) { + bytes = util.str_to_Uint8Array(util.Uint8Array_to_str(this.keyFlags)); + arr.push(write_sub_packet(sub.key_flags, bytes)); + } + if (this.signersUserId !== null) { + arr.push(write_sub_packet(sub.signers_user_id, util.str_to_Uint8Array(this.signersUserId))); + } + if (this.reasonForRevocationFlag !== null) { + bytes = util.str_to_Uint8Array(String.fromCharCode(this.reasonForRevocationFlag) + this.reasonForRevocationString); + arr.push(write_sub_packet(sub.reason_for_revocation, bytes)); + } + if (this.features !== null) { + bytes = util.str_to_Uint8Array(util.Uint8Array_to_str(this.features)); + arr.push(write_sub_packet(sub.features, bytes)); + } + if (this.signatureTargetPublicKeyAlgorithm !== null) { + bytes = [new Uint8Array([this.signatureTargetPublicKeyAlgorithm, this.signatureTargetHashAlgorithm])]; + bytes.push(util.str_to_Uint8Array(this.signatureTargetHash)); + bytes = util.concat(bytes); + arr.push(write_sub_packet(sub.signature_target, bytes)); + } + if (this.preferredAeadAlgorithms !== null) { + bytes = util.str_to_Uint8Array(util.Uint8Array_to_str(this.preferredAeadAlgorithms)); + arr.push(write_sub_packet(sub.preferred_aead_algorithms, bytes)); } - /** - * Creates Uint8Array of bytes of all subpacket data except Issuer and Embedded Signature subpackets - * @returns {Uint8Array} subpacket data - */ - write_hashed_sub_packets() { - const sub = enums.signatureSubpacket; - const arr = []; - let bytes; - if (this.created !== null) { - arr.push(write_sub_packet(sub.signatureCreationTime, util.writeDate(this.created))); - } - if (this.signatureExpirationTime !== null) { - arr.push(write_sub_packet(sub.signatureExpirationTime, util.writeNumber(this.signatureExpirationTime, 4))); - } - if (this.exportable !== null) { - arr.push(write_sub_packet(sub.exportableCertification, new Uint8Array([this.exportable ? 1 : 0]))); - } - if (this.trustLevel !== null) { - bytes = new Uint8Array([this.trustLevel, this.trustAmount]); - arr.push(write_sub_packet(sub.trustSignature, bytes)); - } - if (this.regularExpression !== null) { - arr.push(write_sub_packet(sub.regularExpression, this.regularExpression)); - } - if (this.revocable !== null) { - arr.push(write_sub_packet(sub.revocable, new Uint8Array([this.revocable ? 1 : 0]))); - } - if (this.keyExpirationTime !== null) { - arr.push(write_sub_packet(sub.keyExpirationTime, util.writeNumber(this.keyExpirationTime, 4))); - } - if (this.preferredSymmetricAlgorithms !== null) { - bytes = util.strToUint8Array(util.uint8ArrayToStr(this.preferredSymmetricAlgorithms)); - arr.push(write_sub_packet(sub.preferredSymmetricAlgorithms, bytes)); - } - if (this.revocationKeyClass !== null) { - bytes = new Uint8Array([this.revocationKeyClass, this.revocationKeyAlgorithm]); - bytes = util.concat([bytes, this.revocationKeyFingerprint]); - arr.push(write_sub_packet(sub.revocationKey, bytes)); - } - this.rawNotations.forEach(([{ name, value, humanReadable }]) => { - bytes = [new Uint8Array([humanReadable ? 0x80 : 0, 0, 0, 0])]; - // 2 octets of name length - bytes.push(util.writeNumber(name.length, 2)); - // 2 octets of value length - bytes.push(util.writeNumber(value.length, 2)); - bytes.push(util.strToUint8Array(name)); - bytes.push(value); - bytes = util.concat(bytes); - arr.push(write_sub_packet(sub.notationData, bytes)); - }); - if (this.preferredHashAlgorithms !== null) { - bytes = util.strToUint8Array(util.uint8ArrayToStr(this.preferredHashAlgorithms)); - arr.push(write_sub_packet(sub.preferredHashAlgorithms, bytes)); - } - if (this.preferredCompressionAlgorithms !== null) { - bytes = util.strToUint8Array(util.uint8ArrayToStr(this.preferredCompressionAlgorithms)); - arr.push(write_sub_packet(sub.preferredCompressionAlgorithms, bytes)); - } - if (this.keyServerPreferences !== null) { - bytes = util.strToUint8Array(util.uint8ArrayToStr(this.keyServerPreferences)); - arr.push(write_sub_packet(sub.keyServerPreferences, bytes)); - } - if (this.preferredKeyServer !== null) { - arr.push(write_sub_packet(sub.preferredKeyServer, util.strToUint8Array(this.preferredKeyServer))); - } - if (this.isPrimaryUserID !== null) { - arr.push(write_sub_packet(sub.primaryUserId, new Uint8Array([this.isPrimaryUserID ? 1 : 0]))); - } - if (this.policyURI !== null) { - arr.push(write_sub_packet(sub.policyUri, util.strToUint8Array(this.policyURI))); - } - if (this.keyFlags !== null) { - bytes = util.strToUint8Array(util.uint8ArrayToStr(this.keyFlags)); - arr.push(write_sub_packet(sub.keyFlags, bytes)); - } - if (this.signersUserId !== null) { - arr.push(write_sub_packet(sub.signersUserId, util.strToUint8Array(this.signersUserId))); - } - if (this.reasonForRevocationFlag !== null) { - bytes = util.strToUint8Array(String.fromCharCode(this.reasonForRevocationFlag) + this.reasonForRevocationString); - arr.push(write_sub_packet(sub.reasonForRevocation, bytes)); - } - if (this.features !== null) { - bytes = util.strToUint8Array(util.uint8ArrayToStr(this.features)); - arr.push(write_sub_packet(sub.features, bytes)); - } - if (this.signatureTargetPublicKeyAlgorithm !== null) { - bytes = [new Uint8Array([this.signatureTargetPublicKeyAlgorithm, this.signatureTargetHashAlgorithm])]; - bytes.push(util.strToUint8Array(this.signatureTargetHash)); - bytes = util.concat(bytes); - arr.push(write_sub_packet(sub.signatureTarget, bytes)); - } - if (this.preferredAeadAlgorithms !== null) { - bytes = util.strToUint8Array(util.uint8ArrayToStr(this.preferredAeadAlgorithms)); - arr.push(write_sub_packet(sub.preferredAeadAlgorithms, bytes)); - } + const result = util.concat(arr); + const length = util.writeNumber(result.length, 2); - const result = util.concat(arr); - const length = util.writeNumber(result.length, 2); + return util.concat([length, result]); +}; - return util.concat([length, result]); +/** + * Creates Uint8Array of bytes of Issuer and Embedded Signature subpackets + * @returns {Uint8Array} subpacket data + */ +Signature.prototype.write_unhashed_sub_packets = function() { + const sub = enums.signatureSubpacket; + const arr = []; + let bytes; + if (!this.issuerKeyId.isNull() && this.issuerKeyVersion !== 5) { + // If the version of [the] key is greater than 4, this subpacket + // MUST NOT be included in the signature. + arr.push(write_sub_packet(sub.issuer, this.issuerKeyId.write())); + } + if (this.embeddedSignature !== null) { + arr.push(write_sub_packet(sub.embedded_signature, this.embeddedSignature.write())); + } + if (this.issuerFingerprint !== null) { + bytes = [new Uint8Array([this.issuerKeyVersion]), this.issuerFingerprint]; + bytes = util.concat(bytes); + arr.push(write_sub_packet(sub.issuer_fingerprint, bytes)); } + this.unhashedSubpackets.forEach(data => { + arr.push(packet.writeSimpleLength(data.length)); + arr.push(data); + }); - /** - * Creates Uint8Array of bytes of Issuer and Embedded Signature subpackets - * @returns {Uint8Array} subpacket data - */ - write_unhashed_sub_packets() { - const sub = enums.signatureSubpacket; - const arr = []; - let bytes; - if (!this.issuerKeyId.isNull() && this.issuerKeyVersion !== 5) { - // If the version of [the] key is greater than 4, this subpacket - // MUST NOT be included in the signature. - arr.push(write_sub_packet(sub.issuer, this.issuerKeyId.write())); - } - if (this.embeddedSignature !== null) { - arr.push(write_sub_packet(sub.embeddedSignature, this.embeddedSignature.write())); - } - if (this.issuerFingerprint !== null) { - bytes = [new Uint8Array([this.issuerKeyVersion]), this.issuerFingerprint]; - bytes = util.concat(bytes); - arr.push(write_sub_packet(sub.issuerFingerprint, bytes)); - } - this.unhashedSubpackets.forEach(data => { - arr.push(writeSimpleLength(data.length)); - arr.push(data); - }); + const result = util.concat(arr); + const length = util.writeNumber(result.length, 2); - const result = util.concat(arr); - const length = util.writeNumber(result.length, 2); + return util.concat([length, result]); +}; - return util.concat([length, result]); - } +/** + * Creates a string representation of a sub signature packet + * @see {@link https://tools.ietf.org/html/rfc4880#section-5.2.3.1|RFC4880 5.2.3.1} + * @see {@link https://tools.ietf.org/html/rfc4880#section-5.2.3.2|RFC4880 5.2.3.2} + * @param {Integer} type subpacket signature type. + * @param {String} data data to be included + * @returns {String} a string-representation of a sub signature packet + * @private + */ +function write_sub_packet(type, data) { + const arr = []; + arr.push(packet.writeSimpleLength(data.length + 1)); + arr.push(new Uint8Array([type])); + arr.push(data); + return util.concat(arr); +} - // V4 signature sub packets +// V4 signature sub packets - read_sub_packet(bytes, trusted = true) { - let mypos = 0; +Signature.prototype.read_sub_packet = function (bytes, trusted = true) { + let mypos = 0; - const read_array = (prop, bytes) => { - this[prop] = []; + const read_array = (prop, bytes) => { + this[prop] = []; - for (let i = 0; i < bytes.length; i++) { - this[prop].push(bytes[i]); - } - }; - - // The leftmost bit denotes a "critical" packet - const critical = bytes[mypos] & 0x80; - const type = bytes[mypos] & 0x7F; - - // GPG puts the Issuer and Signature subpackets in the unhashed area. - // Tampering with those invalidates the signature, so we can trust them. - // Ignore all other unhashed subpackets. - if (!trusted && ![ - enums.signatureSubpacket.issuer, - enums.signatureSubpacket.issuerFingerprint, - enums.signatureSubpacket.embeddedSignature - ].includes(type)) { - this.unhashedSubpackets.push(bytes.subarray(mypos, bytes.length)); - return; + for (let i = 0; i < bytes.length; i++) { + this[prop].push(bytes[i]); } + }; - mypos++; + // The leftmost bit denotes a "critical" packet + const critical = bytes[mypos] & 0x80; + const type = bytes[mypos] & 0x7F; - // subpacket type - switch (type) { - case 2: - // Signature Creation Time - this.created = util.readDate(bytes.subarray(mypos, bytes.length)); - break; - case 3: { - // Signature Expiration Time in seconds - const seconds = util.readNumber(bytes.subarray(mypos, bytes.length)); - - this.signatureNeverExpires = seconds === 0; - this.signatureExpirationTime = seconds; + // GPG puts the Issuer and Signature subpackets in the unhashed area. + // Tampering with those invalidates the signature, so we can trust them. + // Ignore all other unhashed subpackets. + if (!trusted && ![ + enums.signatureSubpacket.issuer, + enums.signatureSubpacket.issuer_fingerprint, + enums.signatureSubpacket.embedded_signature + ].includes(type)) { + this.unhashedSubpackets.push(bytes.subarray(mypos, bytes.length)); + return; + } - break; + mypos++; + + // subpacket type + switch (type) { + case 2: + // Signature Creation Time + this.created = util.readDate(bytes.subarray(mypos, bytes.length)); + break; + case 3: { + // Signature Expiration Time in seconds + const seconds = util.readNumber(bytes.subarray(mypos, bytes.length)); + + this.signatureNeverExpires = seconds === 0; + this.signatureExpirationTime = seconds; + + break; + } + case 4: + // Exportable Certification + this.exportable = bytes[mypos++] === 1; + break; + case 5: + // Trust Signature + this.trustLevel = bytes[mypos++]; + this.trustAmount = bytes[mypos++]; + break; + case 6: + // Regular Expression + this.regularExpression = bytes[mypos]; + break; + case 7: + // Revocable + this.revocable = bytes[mypos++] === 1; + break; + case 9: { + // Key Expiration Time in seconds + const seconds = util.readNumber(bytes.subarray(mypos, bytes.length)); + + this.keyExpirationTime = seconds; + this.keyNeverExpires = seconds === 0; + + break; + } + case 11: + // Preferred Symmetric Algorithms + read_array('preferredSymmetricAlgorithms', bytes.subarray(mypos, bytes.length)); + break; + case 12: + // Revocation Key + // (1 octet of class, 1 octet of public-key algorithm ID, 20 + // octets of + // fingerprint) + this.revocationKeyClass = bytes[mypos++]; + this.revocationKeyAlgorithm = bytes[mypos++]; + this.revocationKeyFingerprint = bytes.subarray(mypos, mypos + 20); + break; + + case 16: + // Issuer + this.issuerKeyId.read(bytes.subarray(mypos, bytes.length)); + break; + + case 20: { + // Notation Data + const humanReadable = !!(bytes[mypos] & 0x80); + + // We extract key/value tuple from the byte stream. + mypos += 4; + const m = util.readNumber(bytes.subarray(mypos, mypos + 2)); + mypos += 2; + const n = util.readNumber(bytes.subarray(mypos, mypos + 2)); + mypos += 2; + + const name = util.Uint8Array_to_str(bytes.subarray(mypos, mypos + m)); + const value = bytes.subarray(mypos + m, mypos + m + n); + + this.rawNotations.push({ name, humanReadable, value }); + + if (humanReadable) { + this.notations[name] = util.Uint8Array_to_str(value); } - case 4: - // Exportable Certification - this.exportable = bytes[mypos++] === 1; - break; - case 5: - // Trust Signature - this.trustLevel = bytes[mypos++]; - this.trustAmount = bytes[mypos++]; - break; - case 6: - // Regular Expression - this.regularExpression = bytes[mypos]; - break; - case 7: - // Revocable - this.revocable = bytes[mypos++] === 1; - break; - case 9: { - // Key Expiration Time in seconds - const seconds = util.readNumber(bytes.subarray(mypos, bytes.length)); - - this.keyExpirationTime = seconds; - this.keyNeverExpires = seconds === 0; - - break; - } - case 11: - // Preferred Symmetric Algorithms - read_array('preferredSymmetricAlgorithms', bytes.subarray(mypos, bytes.length)); - break; - case 12: - // Revocation Key - // (1 octet of class, 1 octet of public-key algorithm ID, 20 - // octets of - // fingerprint) - this.revocationKeyClass = bytes[mypos++]; - this.revocationKeyAlgorithm = bytes[mypos++]; - this.revocationKeyFingerprint = bytes.subarray(mypos, mypos + 20); - break; - - case 16: - // Issuer - this.issuerKeyId.read(bytes.subarray(mypos, bytes.length)); - break; - - case 20: { - // Notation Data - const humanReadable = !!(bytes[mypos] & 0x80); - - // We extract key/value tuple from the byte stream. - mypos += 4; - const m = util.readNumber(bytes.subarray(mypos, mypos + 2)); - mypos += 2; - const n = util.readNumber(bytes.subarray(mypos, mypos + 2)); - mypos += 2; - - const name = util.uint8ArrayToStr(bytes.subarray(mypos, mypos + m)); - const value = bytes.subarray(mypos + m, mypos + m + n); - - this.rawNotations.push({ name, humanReadable, value }); - - if (humanReadable) { - this.notations[name] = util.uint8ArrayToStr(value); - } - - if (critical && (config.knownNotations.indexOf(name) === -1)) { - throw new Error("Unknown critical notation: " + name); - } - break; + + if (critical && (config.known_notations.indexOf(name) === -1)) { + throw new Error("Unknown critical notation: " + name); } - case 21: - // Preferred Hash Algorithms - read_array('preferredHashAlgorithms', bytes.subarray(mypos, bytes.length)); - break; - case 22: - // Preferred Compression Algorithms - read_array('preferredCompressionAlgorithms', bytes.subarray(mypos, bytes.length)); - break; - case 23: - // Key Server Preferences - read_array('keyServerPreferences', bytes.subarray(mypos, bytes.length)); - break; - case 24: - // Preferred Key Server - this.preferredKeyServer = util.uint8ArrayToStr(bytes.subarray(mypos, bytes.length)); - break; - case 25: - // Primary User ID - this.isPrimaryUserID = bytes[mypos++] !== 0; - break; - case 26: - // Policy URI - this.policyURI = util.uint8ArrayToStr(bytes.subarray(mypos, bytes.length)); - break; - case 27: - // Key Flags - read_array('keyFlags', bytes.subarray(mypos, bytes.length)); - break; - case 28: - // Signer's User ID - this.signersUserId = util.uint8ArrayToStr(bytes.subarray(mypos, bytes.length)); - break; - case 29: - // Reason for Revocation - this.reasonForRevocationFlag = bytes[mypos++]; - this.reasonForRevocationString = util.uint8ArrayToStr(bytes.subarray(mypos, bytes.length)); - break; - case 30: - // Features - read_array('features', bytes.subarray(mypos, bytes.length)); - break; - case 31: { - // Signature Target - // (1 octet public-key algorithm, 1 octet hash algorithm, N octets hash) - this.signatureTargetPublicKeyAlgorithm = bytes[mypos++]; - this.signatureTargetHashAlgorithm = bytes[mypos++]; - - const len = crypto.getHashByteLength(this.signatureTargetHashAlgorithm); - - this.signatureTargetHash = util.uint8ArrayToStr(bytes.subarray(mypos, mypos + len)); - break; + break; + } + case 21: + // Preferred Hash Algorithms + read_array('preferredHashAlgorithms', bytes.subarray(mypos, bytes.length)); + break; + case 22: + // Preferred Compression Algorithms + read_array('preferredCompressionAlgorithms', bytes.subarray(mypos, bytes.length)); + break; + case 23: + // Key Server Preferences + read_array('keyServerPreferences', bytes.subarray(mypos, bytes.length)); + break; + case 24: + // Preferred Key Server + this.preferredKeyServer = util.Uint8Array_to_str(bytes.subarray(mypos, bytes.length)); + break; + case 25: + // Primary User ID + this.isPrimaryUserID = bytes[mypos++] !== 0; + break; + case 26: + // Policy URI + this.policyURI = util.Uint8Array_to_str(bytes.subarray(mypos, bytes.length)); + break; + case 27: + // Key Flags + read_array('keyFlags', bytes.subarray(mypos, bytes.length)); + break; + case 28: + // Signer's User ID + this.signersUserId = util.Uint8Array_to_str(bytes.subarray(mypos, bytes.length)); + break; + case 29: + // Reason for Revocation + this.reasonForRevocationFlag = bytes[mypos++]; + this.reasonForRevocationString = util.Uint8Array_to_str(bytes.subarray(mypos, bytes.length)); + break; + case 30: + // Features + read_array('features', bytes.subarray(mypos, bytes.length)); + break; + case 31: { + // Signature Target + // (1 octet public-key algorithm, 1 octet hash algorithm, N octets hash) + this.signatureTargetPublicKeyAlgorithm = bytes[mypos++]; + this.signatureTargetHashAlgorithm = bytes[mypos++]; + + const len = crypto.getHashByteLength(this.signatureTargetHashAlgorithm); + + this.signatureTargetHash = util.Uint8Array_to_str(bytes.subarray(mypos, mypos + len)); + break; + } + case 32: + // Embedded Signature + this.embeddedSignature = new Signature(); + this.embeddedSignature.read(bytes.subarray(mypos, bytes.length)); + break; + case 33: + // Issuer Fingerprint + this.issuerKeyVersion = bytes[mypos++]; + this.issuerFingerprint = bytes.subarray(mypos, bytes.length); + if (this.issuerKeyVersion === 5) { + this.issuerKeyId.read(this.issuerFingerprint); + } else { + this.issuerKeyId.read(this.issuerFingerprint.subarray(-8)); } - case 32: - // Embedded Signature - this.embeddedSignature = new SignaturePacket(); - this.embeddedSignature.read(bytes.subarray(mypos, bytes.length)); - break; - case 33: - // Issuer Fingerprint - this.issuerKeyVersion = bytes[mypos++]; - this.issuerFingerprint = bytes.subarray(mypos, bytes.length); - if (this.issuerKeyVersion === 5) { - this.issuerKeyId.read(this.issuerFingerprint); - } else { - this.issuerKeyId.read(this.issuerFingerprint.subarray(-8)); - } - break; - case 34: - // Preferred AEAD Algorithms - read_array.call(this, 'preferredAeadAlgorithms', bytes.subarray(mypos, bytes.length)); - break; - default: { - const err = new Error("Unknown signature subpacket type " + type + " @:" + mypos); - if (critical) { - throw err; - } else { - util.printDebug(err); - } + break; + case 34: + // Preferred AEAD Algorithms + read_array.call(this, 'preferredAeadAlgorithms', bytes.subarray(mypos, bytes.length)); + break; + default: { + const err = new Error("Unknown signature subpacket type " + type + " @:" + mypos); + if (critical) { + throw err; + } else { + util.print_debug(err); } } } +}; - read_sub_packets(bytes, trusted = true) { - // Two-octet scalar octet count for following subpacket data. - const subpacket_length = util.readNumber(bytes.subarray(0, 2)); +Signature.prototype.read_sub_packets = function(bytes, trusted = true) { + // Two-octet scalar octet count for following subpacket data. + const subpacket_length = util.readNumber(bytes.subarray(0, 2)); - let i = 2; + let i = 2; - // subpacket data set (zero or more subpackets) - while (i < 2 + subpacket_length) { - const len = readSimpleLength(bytes.subarray(i, bytes.length)); - i += len.offset; + // subpacket data set (zero or more subpackets) + while (i < 2 + subpacket_length) { + const len = packet.readSimpleLength(bytes.subarray(i, bytes.length)); + i += len.offset; - this.read_sub_packet(bytes.subarray(i, i + len.len), trusted); + this.read_sub_packet(bytes.subarray(i, i + len.len), trusted); - i += len.len; - } - - return i; + i += len.len; } - // Produces data to produce signature on - toSign(type, data) { - const t = enums.signature; + return i; +}; - switch (type) { - case t.binary: - if (data.text !== null) { - return util.encodeUtf8(data.getText(true)); - } - return data.getBytes(true); +// Produces data to produce signature on +Signature.prototype.toSign = function (type, data) { + const t = enums.signature; - case t.text: { - const bytes = data.getBytes(true); - // normalize EOL to \r\n - return util.canonicalizeEOL(bytes); + switch (type) { + case t.binary: + if (data.text !== null) { + return util.encode_utf8(data.getText(true)); } - case t.standalone: - return new Uint8Array(0); - - case t.certGeneric: - case t.certPersona: - case t.certCasual: - case t.certPositive: - case t.certRevocation: { - let packet; - let tag; - - if (data.userId) { - tag = 0xB4; - packet = data.userId; - } else if (data.userAttribute) { - tag = 0xD1; - packet = data.userAttribute; - } else { - throw new Error('Either a userId or userAttribute packet needs to be ' + - 'supplied for certification.'); - } - - const bytes = packet.write(); - - return util.concat([this.toSign(t.key, data), - new Uint8Array([tag]), - util.writeNumber(bytes.length, 4), - bytes]); + return data.getBytes(true); + + case t.text: { + const bytes = data.getBytes(true); + // normalize EOL to \r\n + return util.canonicalizeEOL(bytes); + } + case t.standalone: + return new Uint8Array(0); + + case t.cert_generic: + case t.cert_persona: + case t.cert_casual: + case t.cert_positive: + case t.cert_revocation: { + let packet; + let tag; + + if (data.userId) { + tag = 0xB4; + packet = data.userId; + } else if (data.userAttribute) { + tag = 0xD1; + packet = data.userAttribute; + } else { + throw new Error('Either a userId or userAttribute packet needs to be ' + + 'supplied for certification.'); } - case t.subkeyBinding: - case t.subkeyRevocation: - case t.keyBinding: - return util.concat([this.toSign(t.key, data), this.toSign(t.key, { - key: data.bind - })]); - - case t.key: - if (data.key === undefined) { - throw new Error('Key packet is required for this signature.'); - } - return data.key.writeForHash(this.version); - - case t.keyRevocation: - return this.toSign(t.key, data); - case t.timestamp: - return new Uint8Array(0); - case t.thirdParty: - throw new Error('Not implemented'); - default: - throw new Error('Unknown signature type.'); + + const bytes = packet.write(); + + return util.concat([this.toSign(t.key, data), + new Uint8Array([tag]), + util.writeNumber(bytes.length, 4), + bytes]); } - } + case t.subkey_binding: + case t.subkey_revocation: + case t.key_binding: + return util.concat([this.toSign(t.key, data), this.toSign(t.key, { + key: data.bind + })]); - calculateTrailer(data, detached) { - let length = 0; - return stream.transform(stream.clone(this.signatureData), value => { - length += value.length; - }, () => { - const arr = []; - if (this.version === 5 && (this.signatureType === enums.signature.binary || this.signatureType === enums.signature.text)) { - if (detached) { - arr.push(new Uint8Array(6)); - } else { - arr.push(data.writeHeader()); - } - } - arr.push(new Uint8Array([this.version, 0xFF])); - if (this.version === 5) { - arr.push(new Uint8Array(4)); + case t.key: + if (data.key === undefined) { + throw new Error('Key packet is required for this signature.'); } - arr.push(util.writeNumber(length, 4)); - // For v5, this should really be writeNumber(length, 8) rather than the - // hardcoded 4 zero bytes above - return util.concat(arr); - }); + return data.key.writeForHash(this.version); + + case t.key_revocation: + return this.toSign(t.key, data); + case t.timestamp: + return new Uint8Array(0); + case t.third_party: + throw new Error('Not implemented'); + default: + throw new Error('Unknown signature type.'); } +}; - toHash(signatureType, data, detached = false) { - const bytes = this.toSign(signatureType, data); - - return util.concat([bytes, this.signatureData, this.calculateTrailer(data, detached)]); - } - async hash(signatureType, data, toHash, detached = false, streaming = true) { - const hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm); - if (!toHash) toHash = this.toHash(signatureType, data, detached); - if (!streaming && util.isStream(toHash)) { - return stream.fromAsync(async () => this.hash(signatureType, data, await stream.readToEnd(toHash), detached)); +Signature.prototype.calculateTrailer = function (data, detached) { + let length = 0; + return stream.transform(stream.clone(this.signatureData), value => { + length += value.length; + }, () => { + const arr = []; + if (this.version === 5 && (this.signatureType === enums.signature.binary || this.signatureType === enums.signature.text)) { + if (detached) { + arr.push(new Uint8Array(6)); + } else { + arr.push(data.writeHeader()); + } } - return crypto.hash.digest(hashAlgorithm, toHash); - } - - /** - * verifies the signature packet. Note: not all signature types are implemented - * @param {PublicSubkeyPacket|PublicKeyPacket| - * SecretSubkeyPacket|SecretKeyPacket} key the public key to verify the signature - * @param {module:enums.signature} signatureType expected signature type - * @param {String|Object} data data which on the signature applies - * @param {Boolean} detached (optional) whether to verify a detached signature - * @param {Boolean} streaming (optional) whether to process data as a stream - * @throws {Error} if signature validation failed - * @async - */ - async verify(key, signatureType, data, detached = false, streaming = false) { - const publicKeyAlgorithm = enums.write(enums.publicKey, this.publicKeyAlgorithm); - const hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm); - - if (publicKeyAlgorithm !== enums.write(enums.publicKey, key.algorithm)) { - throw new Error('Public key algorithm used to sign signature does not match issuer key algorithm.'); + arr.push(new Uint8Array([this.version, 0xFF])); + if (this.version === 5) { + arr.push(new Uint8Array(4)); } + arr.push(util.writeNumber(length, 4)); + // For v5, this should really be writeNumber(length, 8) rather than the + // hardcoded 4 zero bytes above + return util.concat(arr); + }); +}; - let toHash; - let hash; - if (this.hashed) { - hash = await this.hashed; - } else { - toHash = this.toHash(signatureType, data, detached); - if (!streaming) toHash = await stream.readToEnd(toHash); - hash = await this.hash(signatureType, data, toHash); - } - hash = await stream.readToEnd(hash); - if (this.signedHashValue[0] !== hash[0] || - this.signedHashValue[1] !== hash[1]) { - throw new Error('Message digest did not match'); - } - this.params = await this.params; +Signature.prototype.toHash = function(signatureType, data, detached = false) { + const bytes = this.toSign(signatureType, data); - const verified = await crypto.signature.verify( - publicKeyAlgorithm, hashAlgorithm, this.params, key.publicParams, - toHash, hash - ); - if (!verified) { - throw new Error('Signature verification failed'); - } - if (config.rejectHashAlgorithms.has(hashAlgorithm)) { - throw new Error('Insecure hash algorithm: ' + enums.read(enums.hash, hashAlgorithm).toUpperCase()); - } - if (config.rejectMessageHashAlgorithms.has(hashAlgorithm) && - [enums.signature.binary, enums.signature.text].includes(this.signatureType)) { - throw new Error('Insecure message hash algorithm: ' + enums.read(enums.hash, hashAlgorithm).toUpperCase()); - } - if (this.revocationKeyClass !== null) { - throw new Error('This key is intended to be revoked with an authorized key, which OpenPGP.js does not support.'); - } - this.verified = true; + return util.concat([bytes, this.signatureData, this.calculateTrailer(data, detached)]); +}; + +Signature.prototype.hash = async function(signatureType, data, toHash, detached = false, streaming = true) { + const hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm); + if (!toHash) toHash = this.toHash(signatureType, data, detached); + if (!streaming && util.isStream(toHash)) { + return stream.fromAsync(async () => this.hash(signatureType, data, await stream.readToEnd(toHash), detached)); } + return crypto.hash.digest(hashAlgorithm, toHash); +}; - /** - * Verifies signature expiration date - * @param {Date} date (optional) use the given date for verification instead of the current time - * @returns {Boolean} true if expired - */ - isExpired(date = new Date()) { - const normDate = util.normalizeDate(date); - if (normDate !== null) { - const expirationTime = this.getExpirationTime(); - return !(this.created <= normDate && normDate <= expirationTime); - } - return false; + +/** + * verifies the signature packet. Note: not all signature types are implemented + * @param {module:packet.PublicSubkey|module:packet.PublicKey| + * module:packet.SecretSubkey|module:packet.SecretKey} key the public key to verify the signature + * @param {module:enums.signature} signatureType expected signature type + * @param {String|Object} data data which on the signature applies + * @param {Boolean} detached (optional) whether to verify a detached signature + * @returns {Promise} True if message is verified, else false. + * @async + */ +Signature.prototype.verify = async function (key, signatureType, data, detached = false, streaming = false) { + const publicKeyAlgorithm = enums.write(enums.publicKey, this.publicKeyAlgorithm); + const hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm); + + if (publicKeyAlgorithm !== enums.write(enums.publicKey, key.algorithm)) { + throw new Error('Public key algorithm used to sign signature does not match issuer key algorithm.'); } - /** - * Returns the expiration time of the signature or Infinity if signature does not expire - * @returns {Date} expiration time - */ - getExpirationTime() { - return !this.signatureNeverExpires ? new Date(this.created.getTime() + this.signatureExpirationTime * 1000) : Infinity; + let toHash; + let hash; + if (this.hashed) { + hash = await this.hashed; + } else { + toHash = this.toHash(signatureType, data, detached); + if (!streaming) toHash = await stream.readToEnd(toHash); + hash = await this.hash(signatureType, data, toHash); + } + hash = await stream.readToEnd(hash); + if (this.signedHashValue[0] !== hash[0] || + this.signedHashValue[1] !== hash[1]) { + throw new Error('Message digest did not match'); } -} + + let mpicount = 0; + // Algorithm-Specific Fields for RSA signatures: + // - multiprecision number (MPI) of RSA signature value m**d mod n. + if (publicKeyAlgorithm > 0 && publicKeyAlgorithm < 4) { + mpicount = 1; + + // Algorithm-Specific Fields for DSA, ECDSA, and EdDSA signatures: + // - MPI of DSA value r. + // - MPI of DSA value s. + } else if (publicKeyAlgorithm === enums.publicKey.dsa || + publicKeyAlgorithm === enums.publicKey.ecdsa || + publicKeyAlgorithm === enums.publicKey.eddsa) { + mpicount = 2; + } + + // EdDSA signature parameters are encoded in little-endian format + // https://tools.ietf.org/html/rfc8032#section-5.1.2 + const endian = publicKeyAlgorithm === enums.publicKey.eddsa ? 'le' : 'be'; + const mpi = []; + let i = 0; + this.signature = await stream.readToEnd(this.signature); + for (let j = 0; j < mpicount; j++) { + mpi[j] = new type_mpi(); + i += mpi[j].read(this.signature.subarray(i, this.signature.length), endian); + } + const verified = await crypto.signature.verify( + publicKeyAlgorithm, hashAlgorithm, mpi, key.params, + toHash, hash + ); + if (!verified) { + throw new Error('Signature verification failed'); + } + if (config.reject_hash_algorithms.has(hashAlgorithm)) { + throw new Error('Insecure hash algorithm: ' + enums.read(enums.hash, hashAlgorithm).toUpperCase()); + } + if (config.reject_message_hash_algorithms.has(hashAlgorithm) && + [enums.signature.binary, enums.signature.text].includes(this.signatureType)) { + throw new Error('Insecure message hash algorithm: ' + enums.read(enums.hash, hashAlgorithm).toUpperCase()); + } + if (this.revocationKeyClass !== null) { + throw new Error('This key is intended to be revoked with an authorized key, which OpenPGP.js does not support.'); + } + this.verified = true; + return true; +}; /** - * Creates a string representation of a sub signature packet - * @see {@link https://tools.ietf.org/html/rfc4880#section-5.2.3.1|RFC4880 5.2.3.1} - * @see {@link https://tools.ietf.org/html/rfc4880#section-5.2.3.2|RFC4880 5.2.3.2} - * @param {Integer} type subpacket signature type. - * @param {String} data data to be included - * @returns {String} a string-representation of a sub signature packet - * @private + * Verifies signature expiration date + * @param {Date} date (optional) use the given date for verification instead of the current time + * @returns {Boolean} true if expired */ -function write_sub_packet(type, data) { - const arr = []; - arr.push(writeSimpleLength(data.length + 1)); - arr.push(new Uint8Array([type])); - arr.push(data); - return util.concat(arr); -} +Signature.prototype.isExpired = function (date = new Date()) { + const normDate = util.normalizeDate(date); + if (normDate !== null) { + const expirationTime = this.getExpirationTime(); + return !(this.created <= normDate && normDate <= expirationTime); + } + return false; +}; + +/** + * Returns the expiration time of the signature or Infinity if signature does not expire + * @returns {Date} expiration time + */ +Signature.prototype.getExpirationTime = function () { + return !this.signatureNeverExpires ? new Date(this.created.getTime() + this.signatureExpirationTime * 1000) : Infinity; +}; + +/** + * Fix custom types after cloning + */ +Signature.prototype.postCloneTypeFix = function() { + this.issuerKeyId = type_keyid.fromClone(this.issuerKeyId); +}; -export default SignaturePacket; +export default Signature; diff --git a/src/packet/sym_encrypted_aead_protected.js b/src/packet/sym_encrypted_aead_protected.js new file mode 100644 index 00000000..88564f8d --- /dev/null +++ b/src/packet/sym_encrypted_aead_protected.js @@ -0,0 +1,189 @@ +// 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 + +/** + * @requires web-stream-tools + * @requires config + * @requires crypto + * @requires enums + * @requires util + */ + +import stream from 'web-stream-tools'; +import config from '../config'; +import crypto from '../crypto'; +import enums from '../enums'; +import util from '../util'; + +const VERSION = 1; // A one-octet version number of the data packet. + +/** + * 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 + * @memberof module:packet + * @constructor + */ +function SymEncryptedAEADProtected() { + this.tag = enums.packet.symEncryptedAEADProtected; + this.version = VERSION; + this.cipherAlgo = null; + this.aeadAlgorithm = 'eax'; + this.aeadAlgo = null; + this.chunkSizeByte = null; + this.iv = null; + this.encrypted = null; + this.packets = null; +} + +export default SymEncryptedAEADProtected; + +/** + * Parse an encrypted payload of bytes in the order: version, IV, ciphertext (see specification) + * @param {Uint8Array | ReadableStream} bytes + */ +SymEncryptedAEADProtected.prototype.read = async function (bytes) { + await stream.parse(bytes, async reader => { + if (await reader.readByte() !== VERSION) { // The only currently defined value is 1. + throw new Error('Invalid packet version.'); + } + this.cipherAlgo = await reader.readByte(); + this.aeadAlgo = await reader.readByte(); + this.chunkSizeByte = await reader.readByte(); + const mode = crypto[enums.read(enums.aead, this.aeadAlgo)]; + this.iv = await reader.readBytes(mode.ivLength); + this.encrypted = reader.remainder(); + }); +}; + +/** + * Write the encrypted payload of bytes in the order: version, IV, ciphertext (see specification) + * @returns {Uint8Array | ReadableStream} The encrypted payload + */ +SymEncryptedAEADProtected.prototype.write = function () { + return util.concat([new Uint8Array([this.version, this.cipherAlgo, this.aeadAlgo, this.chunkSizeByte]), 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 + * @param {Boolean} streaming Whether the top-level function will return a stream + * @returns {Boolean} + * @async + */ +SymEncryptedAEADProtected.prototype.decrypt = async function (sessionKeyAlgorithm, key, streaming) { + await this.packets.read(await this.crypt('decrypt', key, stream.clone(this.encrypted), streaming), streaming); + return true; +}; + +/** + * 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 + * @param {Boolean} streaming Whether the top-level function will return a stream + * @async + */ +SymEncryptedAEADProtected.prototype.encrypt = async function (sessionKeyAlgorithm, key, streaming) { + this.cipherAlgo = enums.write(enums.symmetric, sessionKeyAlgorithm); + this.aeadAlgo = enums.write(enums.aead, this.aeadAlgorithm); + const mode = crypto[enums.read(enums.aead, this.aeadAlgo)]; + this.iv = await crypto.random.getRandomBytes(mode.ivLength); // generate new random IV + this.chunkSizeByte = config.aead_chunk_size_byte; + const data = this.packets.write(); + this.encrypted = await this.crypt('encrypt', key, data, streaming); +}; + +/** + * En/decrypt the payload. + * @param {encrypt|decrypt} fn Whether to encrypt or decrypt + * @param {Uint8Array} key The session key used to en/decrypt the payload + * @param {Uint8Array | ReadableStream} data The data to en/decrypt + * @param {Boolean} streaming Whether the top-level function will return a stream + * @returns {Uint8Array | ReadableStream} + * @async + */ +SymEncryptedAEADProtected.prototype.crypt = async function (fn, key, data, streaming) { + const cipher = enums.read(enums.symmetric, this.cipherAlgo); + const mode = crypto[enums.read(enums.aead, this.aeadAlgo)]; + const modeInstance = await mode(cipher, key); + const tagLengthIfDecrypting = fn === 'decrypt' ? mode.tagLength : 0; + const tagLengthIfEncrypting = fn === 'encrypt' ? mode.tagLength : 0; + const chunkSize = 2 ** (this.chunkSizeByte + 6) + tagLengthIfDecrypting; // ((uint64_t)1 << (c + 6)) + const adataBuffer = new ArrayBuffer(21); + const adataArray = new Uint8Array(adataBuffer, 0, 13); + const adataTagArray = new Uint8Array(adataBuffer); + const adataView = new DataView(adataBuffer); + const chunkIndexArray = new Uint8Array(adataBuffer, 5, 8); + adataArray.set([0xC0 | this.tag, this.version, this.cipherAlgo, this.aeadAlgo, this.chunkSizeByte], 0); + let chunkIndex = 0; + let latestPromise = Promise.resolve(); + let cryptedBytes = 0; + let queuedBytes = 0; + const iv = this.iv; + return stream.transformPair(data, async (readable, writable) => { + const reader = stream.getReader(readable); + const buffer = new TransformStream({}, { + highWaterMark: streaming ? util.getHardwareConcurrency() * 2 ** (this.chunkSizeByte + 6) : Infinity, + size: array => array.length + }); + stream.pipe(buffer.readable, writable); + const writer = stream.getWriter(buffer.writable); + try { + while (true) { + let chunk = await reader.readBytes(chunkSize + tagLengthIfDecrypting) || new Uint8Array(); + const finalChunk = chunk.subarray(chunk.length - tagLengthIfDecrypting); + chunk = chunk.subarray(0, chunk.length - tagLengthIfDecrypting); + let cryptedPromise; + let done; + if (!chunkIndex || chunk.length) { + reader.unshift(finalChunk); + cryptedPromise = modeInstance[fn](chunk, mode.getNonce(iv, chunkIndexArray), adataArray); + queuedBytes += chunk.length - tagLengthIfDecrypting + tagLengthIfEncrypting; + } else { + // After the last chunk, we either encrypt a final, empty + // data chunk to get the final authentication tag or + // validate that final authentication tag. + adataView.setInt32(13 + 4, cryptedBytes); // Should be setInt64(13, ...) + cryptedPromise = modeInstance[fn](finalChunk, mode.getNonce(iv, chunkIndexArray), adataTagArray); + queuedBytes += tagLengthIfEncrypting; + done = true; + } + cryptedBytes += chunk.length - tagLengthIfDecrypting; + // eslint-disable-next-line no-loop-func + latestPromise = latestPromise.then(() => cryptedPromise).then(async crypted => { + await writer.ready; + await writer.write(crypted); + queuedBytes -= crypted.length; + }).catch(err => writer.abort(err)); + if (done || queuedBytes > writer.desiredSize) { + await latestPromise; // Respect backpressure + } + if (!done) { + adataView.setInt32(5 + 4, ++chunkIndex); // Should be setInt64(5, ...) + } else { + await writer.close(); + break; + } + } + } catch (e) { + await writer.abort(e); + } + }); +}; diff --git a/src/packet/sym_encrypted_integrity_protected.js b/src/packet/sym_encrypted_integrity_protected.js new file mode 100644 index 00000000..68d20314 --- /dev/null +++ b/src/packet/sym_encrypted_integrity_protected.js @@ -0,0 +1,139 @@ +// GPG4Browsers - An OpenPGP implementation in javascript +// Copyright (C) 2011 Recurity Labs GmbH +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 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 + +/** + * @requires asmcrypto.js + * @requires web-stream-tools + * @requires config + * @requires crypto + * @requires enums + * @requires util + */ + +import stream from 'web-stream-tools'; +import config from '../config'; +import crypto from '../crypto'; +import enums from '../enums'; +import util from '../util'; + +const VERSION = 1; // A one-octet version number of the data packet. + +/** + * Implementation of the Sym. Encrypted Integrity Protected Data Packet (Tag 18) + * + * {@link https://tools.ietf.org/html/rfc4880#section-5.13|RFC4880 5.13}: + * The Symmetrically Encrypted Integrity Protected Data packet is + * a variant of the Symmetrically Encrypted Data packet. It is a new feature + * created for OpenPGP that addresses the problem of detecting a modification to + * encrypted data. It is used in combination with a Modification Detection Code + * packet. + * @memberof module:packet + * @constructor + */ +function SymEncryptedIntegrityProtected() { + this.tag = enums.packet.symEncryptedIntegrityProtected; + this.version = VERSION; + /** The encrypted payload. */ + this.encrypted = null; // string + /** + * If after decrypting the packet this is set to true, + * a modification has been detected and thus the contents + * should be discarded. + * @type {Boolean} + */ + this.modification = false; + this.packets = null; +} + +SymEncryptedIntegrityProtected.prototype.read = async function (bytes) { + await stream.parse(bytes, async reader => { + + // - A one-octet version number. The only currently defined value is 1. + if (await reader.readByte() !== VERSION) { + throw new Error('Invalid packet version.'); + } + + // - Encrypted data, the output of the selected symmetric-key cipher + // operating in Cipher Feedback mode with shift amount equal to the + // block size of the cipher (CFB-n where n is the block size). + this.encrypted = reader.remainder(); + }); +}; + +SymEncryptedIntegrityProtected.prototype.write = function () { + return util.concat([new Uint8Array([VERSION]), this.encrypted]); +}; + +/** + * Encrypt the payload in the packet. + * @param {String} sessionKeyAlgorithm The selected symmetric encryption algorithm to be used e.g. 'aes128' + * @param {Uint8Array} key The key of cipher blocksize length to be used + * @param {Boolean} streaming Whether to set this.encrypted to a stream + * @returns {Promise} + * @async + */ +SymEncryptedIntegrityProtected.prototype.encrypt = async function (sessionKeyAlgorithm, key, streaming) { + let bytes = this.packets.write(); + if (!streaming) bytes = await stream.readToEnd(bytes); + const prefix = await crypto.getPrefixRandom(sessionKeyAlgorithm); + const mdc = new Uint8Array([0xD3, 0x14]); // modification detection code packet + + const tohash = util.concat([prefix, bytes, mdc]); + const hash = await crypto.hash.sha1(stream.passiveClone(tohash)); + const plaintext = util.concat([tohash, hash]); + + this.encrypted = await crypto.cfb.encrypt(sessionKeyAlgorithm, key, plaintext, new Uint8Array(crypto.cipher[sessionKeyAlgorithm].blockSize)); + return true; +}; + +/** + * Decrypts the encrypted data contained in the packet. + * @param {String} sessionKeyAlgorithm The selected symmetric encryption algorithm to be used e.g. 'aes128' + * @param {Uint8Array} key The key of cipher blocksize length to be used + * @param {Boolean} streaming Whether to read this.encrypted as a stream + * @returns {Promise} + * @async + */ +SymEncryptedIntegrityProtected.prototype.decrypt = async function (sessionKeyAlgorithm, key, streaming) { + let encrypted = stream.clone(this.encrypted); + if (!streaming) encrypted = await stream.readToEnd(encrypted); + const decrypted = await crypto.cfb.decrypt(sessionKeyAlgorithm, key, encrypted, new Uint8Array(crypto.cipher[sessionKeyAlgorithm].blockSize)); + + // there must be a modification detection code packet as the + // last packet and everything gets hashed except the hash itself + const realHash = stream.slice(stream.passiveClone(decrypted), -20); + const tohash = stream.slice(decrypted, 0, -20); + const verifyHash = Promise.all([ + stream.readToEnd(await crypto.hash.sha1(stream.passiveClone(tohash))), + stream.readToEnd(realHash) + ]).then(([hash, mdc]) => { + if (!util.equalsUint8Array(hash, mdc)) { + throw new Error('Modification detected.'); + } + return new Uint8Array(); + }); + const bytes = stream.slice(tohash, crypto.cipher[sessionKeyAlgorithm].blockSize + 2); // Remove random prefix + let packetbytes = stream.slice(bytes, 0, -2); // Remove MDC packet + packetbytes = stream.concat([packetbytes, stream.fromAsync(() => verifyHash)]); + if (!util.isStream(encrypted) || !config.allow_unauthenticated_stream) { + packetbytes = await stream.readToEnd(packetbytes); + } + await this.packets.read(packetbytes, streaming); + return true; +}; + +export default SymEncryptedIntegrityProtected; diff --git a/src/packet/sym_encrypted_integrity_protected_data.js b/src/packet/sym_encrypted_integrity_protected_data.js deleted file mode 100644 index e31e9f94..00000000 --- a/src/packet/sym_encrypted_integrity_protected_data.js +++ /dev/null @@ -1,152 +0,0 @@ -// GPG4Browsers - An OpenPGP implementation in javascript -// Copyright (C) 2011 Recurity Labs GmbH -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 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 - -/** - * @requires asmcrypto.js - * @requires web-stream-tools - * @requires config - * @requires crypto - * @requires enums - * @requires util - * @requires packet - */ - -import stream from 'web-stream-tools'; -import config from '../config'; -import crypto from '../crypto'; -import enums from '../enums'; -import util from '../util'; -import { - LiteralDataPacket, - CompressedDataPacket, - OnePassSignaturePacket, - SignaturePacket -} from '../packet'; - -const VERSION = 1; // A one-octet version number of the data packet. - -/** - * Implementation of the Sym. Encrypted Integrity Protected Data Packet (Tag 18) - * - * {@link https://tools.ietf.org/html/rfc4880#section-5.13|RFC4880 5.13}: - * The Symmetrically Encrypted Integrity Protected Data packet is - * a variant of the Symmetrically Encrypted Data packet. It is a new feature - * created for OpenPGP that addresses the problem of detecting a modification to - * encrypted data. It is used in combination with a Modification Detection Code - * packet. - * @memberof module:packet - */ -class SymEncryptedIntegrityProtectedDataPacket { - constructor() { - this.tag = enums.packet.symEncryptedIntegrityProtectedData; - this.version = VERSION; - /** The encrypted payload. */ - this.encrypted = null; // string - /** - * If after decrypting the packet this is set to true, - * a modification has been detected and thus the contents - * should be discarded. - * @type {Boolean} - */ - this.modification = false; - this.packets = null; - } - - async read(bytes) { - await stream.parse(bytes, async reader => { - - // - A one-octet version number. The only currently defined value is 1. - if (await reader.readByte() !== VERSION) { - throw new Error('Invalid packet version.'); - } - - // - Encrypted data, the output of the selected symmetric-key cipher - // operating in Cipher Feedback mode with shift amount equal to the - // block size of the cipher (CFB-n where n is the block size). - this.encrypted = reader.remainder(); - }); - } - - write() { - return util.concat([new Uint8Array([VERSION]), this.encrypted]); - } - - /** - * Encrypt the payload in the packet. - * @param {String} sessionKeyAlgorithm The selected symmetric encryption algorithm to be used e.g. 'aes128' - * @param {Uint8Array} key The key of cipher blocksize length to be used - * @param {Boolean} streaming Whether to set this.encrypted to a stream - * @returns {Promise} - * @async - */ - async encrypt(sessionKeyAlgorithm, key, streaming) { - let bytes = this.packets.write(); - if (!streaming) bytes = await stream.readToEnd(bytes); - const prefix = await crypto.getPrefixRandom(sessionKeyAlgorithm); - const mdc = new Uint8Array([0xD3, 0x14]); // modification detection code packet - - const tohash = util.concat([prefix, bytes, mdc]); - const hash = await crypto.hash.sha1(stream.passiveClone(tohash)); - const plaintext = util.concat([tohash, hash]); - - this.encrypted = await crypto.cfb.encrypt(sessionKeyAlgorithm, key, plaintext, new Uint8Array(crypto.cipher[sessionKeyAlgorithm].blockSize)); - return true; - } - - /** - * Decrypts the encrypted data contained in the packet. - * @param {String} sessionKeyAlgorithm The selected symmetric encryption algorithm to be used e.g. 'aes128' - * @param {Uint8Array} key The key of cipher blocksize length to be used - * @param {Boolean} streaming Whether to read this.encrypted as a stream - * @returns {Promise} - * @async - */ - async decrypt(sessionKeyAlgorithm, key, streaming) { - let encrypted = stream.clone(this.encrypted); - if (!streaming) encrypted = await stream.readToEnd(encrypted); - const decrypted = await crypto.cfb.decrypt(sessionKeyAlgorithm, key, encrypted, new Uint8Array(crypto.cipher[sessionKeyAlgorithm].blockSize)); - - // there must be a modification detection code packet as the - // last packet and everything gets hashed except the hash itself - const realHash = stream.slice(stream.passiveClone(decrypted), -20); - const tohash = stream.slice(decrypted, 0, -20); - const verifyHash = Promise.all([ - stream.readToEnd(await crypto.hash.sha1(stream.passiveClone(tohash))), - stream.readToEnd(realHash) - ]).then(([hash, mdc]) => { - if (!util.equalsUint8Array(hash, mdc)) { - throw new Error('Modification detected.'); - } - return new Uint8Array(); - }); - const bytes = stream.slice(tohash, crypto.cipher[sessionKeyAlgorithm].blockSize + 2); // Remove random prefix - let packetbytes = stream.slice(bytes, 0, -2); // Remove MDC packet - packetbytes = stream.concat([packetbytes, stream.fromAsync(() => verifyHash)]); - if (!util.isStream(encrypted) || !config.allowUnauthenticatedStream) { - packetbytes = await stream.readToEnd(packetbytes); - } - await this.packets.read(packetbytes, { - LiteralDataPacket, - CompressedDataPacket, - OnePassSignaturePacket, - SignaturePacket - }, streaming); - return true; - } -} - -export default SymEncryptedIntegrityProtectedDataPacket; diff --git a/src/packet/sym_encrypted_session_key.js b/src/packet/sym_encrypted_session_key.js index c1fa93de..b1422080 100644 --- a/src/packet/sym_encrypted_session_key.js +++ b/src/packet/sym_encrypted_session_key.js @@ -30,162 +30,175 @@ import enums from '../enums'; import util from '../util'; /** - * Symmetric-Key Encrypted Session Key Packets (Tag 3) + * Public-Key Encrypted Session Key Packets (Tag 1) * - * {@link https://tools.ietf.org/html/rfc4880#section-5.3|RFC4880 5.3}: - * The Symmetric-Key Encrypted Session Key packet holds the - * symmetric-key encryption of a session key used to encrypt a message. - * Zero or more Public-Key Encrypted Session Key packets and/or - * Symmetric-Key Encrypted Session Key packets may precede a - * Symmetrically Encrypted Data packet that holds an encrypted message. - * The message is encrypted with a session key, and the session key is - * itself encrypted and stored in the Encrypted Session Key packet or - * the Symmetric-Key Encrypted Session Key packet. + * {@link https://tools.ietf.org/html/rfc4880#section-5.1|RFC4880 5.1}: + * A Public-Key Encrypted Session Key packet holds the session key + * used to encrypt a message. Zero or more Public-Key Encrypted Session Key + * packets and/or Symmetric-Key Encrypted Session Key packets may precede a + * Symmetrically Encrypted Data Packet, which holds an encrypted message. The + * message is encrypted with the session key, and the session key is itself + * encrypted and stored in the Encrypted Session Key packet(s). The + * Symmetrically Encrypted Data Packet is preceded by one Public-Key Encrypted + * Session Key packet for each OpenPGP key to which the message is encrypted. + * The recipient of the message finds a session key that is encrypted to their + * public key, decrypts the session key, and then uses the session key to + * decrypt the message. * @memberof module:packet + * @constructor */ -class SymEncryptedSessionKeyPacket { - constructor() { - this.tag = enums.packet.symEncryptedSessionKey; - this.version = config.aeadProtect ? 5 : 4; - this.sessionKey = null; - this.sessionKeyEncryptionAlgorithm = null; - this.sessionKeyAlgorithm = 'aes256'; - this.aeadAlgorithm = enums.read(enums.aead, config.aeadMode); - this.encrypted = null; - this.s2k = null; - this.iv = null; +function SymEncryptedSessionKey() { + this.tag = enums.packet.symEncryptedSessionKey; + this.version = config.aead_protect ? 5 : 4; + this.sessionKey = null; + this.sessionKeyEncryptionAlgorithm = null; + this.sessionKeyAlgorithm = 'aes256'; + this.aeadAlgorithm = enums.read(enums.aead, config.aead_mode); + this.encrypted = null; + this.s2k = null; + this.iv = null; +} + +/** + * Parsing function for a symmetric encrypted session key packet (tag 3). + * + * @param {Uint8Array} input Payload of a tag 1 packet + * @param {Integer} position Position to start reading from the input string + * @param {Integer} len + * Length of the packet or the remaining length of + * input at position + * @returns {module:packet.SymEncryptedSessionKey} Object representation + */ +SymEncryptedSessionKey.prototype.read = function(bytes) { + let offset = 0; + + // A one-octet version number. The only currently defined version is 4. + this.version = bytes[offset++]; + + // A one-octet number describing the symmetric algorithm used. + const algo = enums.read(enums.symmetric, bytes[offset++]); + + if (this.version === 5) { + // A one-octet AEAD algorithm. + this.aeadAlgorithm = enums.read(enums.aead, bytes[offset++]); } - /** - * Parsing function for a symmetric encrypted session key packet (tag 3). - * - * @param {Uint8Array} bytes Payload of a tag 3 packet - */ - read(bytes) { - let offset = 0; + // A string-to-key (S2K) specifier, length as defined above. + this.s2k = new type_s2k(); + offset += this.s2k.read(bytes.subarray(offset, bytes.length)); - // A one-octet version number. The only currently defined version is 4. - this.version = bytes[offset++]; + if (this.version === 5) { + const mode = crypto[this.aeadAlgorithm]; - // A one-octet number describing the symmetric algorithm used. - const algo = enums.read(enums.symmetric, bytes[offset++]); + // A starting initialization vector of size specified by the AEAD + // algorithm. + this.iv = bytes.subarray(offset, offset += mode.ivLength); + } - if (this.version === 5) { - // A one-octet AEAD algorithm. - this.aeadAlgorithm = enums.read(enums.aead, bytes[offset++]); - } + // The encrypted session key itself, which is decrypted with the + // string-to-key object. This is optional in version 4. + if (this.version === 5 || offset < bytes.length) { + this.encrypted = bytes.subarray(offset, bytes.length); + this.sessionKeyEncryptionAlgorithm = algo; + } else { + this.sessionKeyAlgorithm = algo; + } +}; - // A string-to-key (S2K) specifier, length as defined above. - this.s2k = new type_s2k(); - offset += this.s2k.read(bytes.subarray(offset, bytes.length)); +SymEncryptedSessionKey.prototype.write = function() { + const algo = this.encrypted === null ? + this.sessionKeyAlgorithm : + this.sessionKeyEncryptionAlgorithm; - if (this.version === 5) { - const mode = crypto[this.aeadAlgorithm]; + let bytes; - // A starting initialization vector of size specified by the AEAD - // algorithm. - this.iv = bytes.subarray(offset, offset += mode.ivLength); - } + if (this.version === 5) { + bytes = util.concatUint8Array([new Uint8Array([this.version, enums.write(enums.symmetric, algo), enums.write(enums.aead, this.aeadAlgorithm)]), this.s2k.write(), this.iv, this.encrypted]); + } else { + bytes = util.concatUint8Array([new Uint8Array([this.version, enums.write(enums.symmetric, algo)]), this.s2k.write()]); - // The encrypted session key itself, which is decrypted with the - // string-to-key object. This is optional in version 4. - if (this.version === 5 || offset < bytes.length) { - this.encrypted = bytes.subarray(offset, bytes.length); - this.sessionKeyEncryptionAlgorithm = algo; - } else { - this.sessionKeyAlgorithm = algo; + if (this.encrypted !== null) { + bytes = util.concatUint8Array([bytes, this.encrypted]); } } - /** - * Create a binary representation of a tag 3 packet - * - * @returns {Uint8Array} The Uint8Array representation - */ - write() { - const algo = this.encrypted === null ? - this.sessionKeyAlgorithm : - this.sessionKeyEncryptionAlgorithm; - - let bytes; - - if (this.version === 5) { - bytes = util.concatUint8Array([new Uint8Array([this.version, enums.write(enums.symmetric, algo), enums.write(enums.aead, this.aeadAlgorithm)]), this.s2k.write(), this.iv, this.encrypted]); - } else { - bytes = util.concatUint8Array([new Uint8Array([this.version, enums.write(enums.symmetric, algo)]), this.s2k.write()]); - - if (this.encrypted !== null) { - bytes = util.concatUint8Array([bytes, this.encrypted]); - } - } + return bytes; +}; - return bytes; +/** + * Decrypts the session key + * @param {String} passphrase The passphrase in string form + * @returns {Promise} + * @async + */ +SymEncryptedSessionKey.prototype.decrypt = async function(passphrase) { + const algo = this.sessionKeyEncryptionAlgorithm !== null ? + this.sessionKeyEncryptionAlgorithm : + this.sessionKeyAlgorithm; + + const length = crypto.cipher[algo].keySize; + const key = await this.s2k.produce_key(passphrase, length); + + if (this.version === 5) { + const mode = crypto[this.aeadAlgorithm]; + const adata = new Uint8Array([0xC0 | this.tag, this.version, enums.write(enums.symmetric, this.sessionKeyEncryptionAlgorithm), enums.write(enums.aead, this.aeadAlgorithm)]); + const modeInstance = await mode(algo, key); + this.sessionKey = await modeInstance.decrypt(this.encrypted, this.iv, adata); + } else if (this.encrypted !== null) { + const decrypted = await crypto.cfb.decrypt(algo, key, this.encrypted, new Uint8Array(crypto.cipher[algo].blockSize)); + + this.sessionKeyAlgorithm = enums.read(enums.symmetric, decrypted[0]); + this.sessionKey = decrypted.subarray(1, decrypted.length); + } else { + this.sessionKey = key; } - /** - * Decrypts the session key - * @param {String} passphrase The passphrase in string form - * @throws {Error} if decryption was not successful - * @async - */ - async decrypt(passphrase) { - const algo = this.sessionKeyEncryptionAlgorithm !== null ? - this.sessionKeyEncryptionAlgorithm : - this.sessionKeyAlgorithm; - - const length = crypto.cipher[algo].keySize; - const key = await this.s2k.produce_key(passphrase, length); - - if (this.version === 5) { - const mode = crypto[this.aeadAlgorithm]; - const adata = new Uint8Array([0xC0 | this.tag, this.version, enums.write(enums.symmetric, this.sessionKeyEncryptionAlgorithm), enums.write(enums.aead, this.aeadAlgorithm)]); - const modeInstance = await mode(algo, key); - this.sessionKey = await modeInstance.decrypt(this.encrypted, this.iv, adata); - } else if (this.encrypted !== null) { - const decrypted = await crypto.cfb.decrypt(algo, key, this.encrypted, new Uint8Array(crypto.cipher[algo].blockSize)); - - this.sessionKeyAlgorithm = enums.read(enums.symmetric, decrypted[0]); - this.sessionKey = decrypted.subarray(1, decrypted.length); - } else { - this.sessionKey = key; - } - } + return true; +}; - /** - * Encrypts the session key - * @param {String} passphrase The passphrase in string form - * @throws {Error} if encryption was not successful - * @async - */ - async encrypt(passphrase) { - const algo = this.sessionKeyEncryptionAlgorithm !== null ? - this.sessionKeyEncryptionAlgorithm : - this.sessionKeyAlgorithm; +/** + * Encrypts the session key + * @param {String} passphrase The passphrase in string form + * @returns {Promise} + * @async + */ +SymEncryptedSessionKey.prototype.encrypt = async function(passphrase) { + const algo = this.sessionKeyEncryptionAlgorithm !== null ? + this.sessionKeyEncryptionAlgorithm : + this.sessionKeyAlgorithm; - this.sessionKeyEncryptionAlgorithm = algo; + this.sessionKeyEncryptionAlgorithm = algo; - this.s2k = new type_s2k(); - this.s2k.salt = await crypto.random.getRandomBytes(8); + this.s2k = new type_s2k(); + this.s2k.salt = await crypto.random.getRandomBytes(8); - const length = crypto.cipher[algo].keySize; - const key = await this.s2k.produce_key(passphrase, length); + const length = crypto.cipher[algo].keySize; + const key = await this.s2k.produce_key(passphrase, length); - if (this.sessionKey === null) { - this.sessionKey = await crypto.generateSessionKey(this.sessionKeyAlgorithm); - } + if (this.sessionKey === null) { + this.sessionKey = await crypto.generateSessionKey(this.sessionKeyAlgorithm); + } - if (this.version === 5) { - const mode = crypto[this.aeadAlgorithm]; - this.iv = await crypto.random.getRandomBytes(mode.ivLength); // generate new random IV - const adata = new Uint8Array([0xC0 | this.tag, this.version, enums.write(enums.symmetric, this.sessionKeyEncryptionAlgorithm), enums.write(enums.aead, this.aeadAlgorithm)]); - const modeInstance = await mode(algo, key); - this.encrypted = await modeInstance.encrypt(this.sessionKey, this.iv, adata); - } else { - const algo_enum = new Uint8Array([enums.write(enums.symmetric, this.sessionKeyAlgorithm)]); - const private_key = util.concatUint8Array([algo_enum, this.sessionKey]); - this.encrypted = await crypto.cfb.encrypt(algo, key, private_key, new Uint8Array(crypto.cipher[algo].blockSize)); - } + if (this.version === 5) { + const mode = crypto[this.aeadAlgorithm]; + this.iv = await crypto.random.getRandomBytes(mode.ivLength); // generate new random IV + const adata = new Uint8Array([0xC0 | this.tag, this.version, enums.write(enums.symmetric, this.sessionKeyEncryptionAlgorithm), enums.write(enums.aead, this.aeadAlgorithm)]); + const modeInstance = await mode(algo, key); + this.encrypted = await modeInstance.encrypt(this.sessionKey, this.iv, adata); + } else { + const algo_enum = new Uint8Array([enums.write(enums.symmetric, this.sessionKeyAlgorithm)]); + const private_key = util.concatUint8Array([algo_enum, this.sessionKey]); + this.encrypted = await crypto.cfb.encrypt(algo, key, private_key, new Uint8Array(crypto.cipher[algo].blockSize)); } -} -export default SymEncryptedSessionKeyPacket; + return true; +}; + +/** + * Fix custom types after cloning + */ +SymEncryptedSessionKey.prototype.postCloneTypeFix = function() { + this.s2k = type_s2k.fromClone(this.s2k); +}; + +export default SymEncryptedSessionKey; diff --git a/src/packet/symmetrically_encrypted.js b/src/packet/symmetrically_encrypted.js new file mode 100644 index 00000000..79a5944b --- /dev/null +++ b/src/packet/symmetrically_encrypted.js @@ -0,0 +1,118 @@ +// GPG4Browsers - An OpenPGP implementation in javascript +// Copyright (C) 2011 Recurity Labs GmbH +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 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 + +/** + * @requires web-stream-tools + * @requires config + * @requires crypto + * @requires enums + * @requires util + */ + +import stream from 'web-stream-tools'; +import config from '../config'; +import crypto from '../crypto'; +import enums from '../enums'; +import util from '../util'; + +/** + * Implementation of the Symmetrically Encrypted Data Packet (Tag 9) + * + * {@link https://tools.ietf.org/html/rfc4880#section-5.7|RFC4880 5.7}: + * The Symmetrically Encrypted Data packet contains data encrypted with a + * symmetric-key algorithm. When it has been decrypted, it contains other + * packets (usually a literal data packet or compressed data packet, but in + * theory other Symmetrically Encrypted Data packets or sequences of packets + * that form whole OpenPGP messages). + * @memberof module:packet + * @constructor + */ +function SymmetricallyEncrypted() { + /** + * Packet type + * @type {module:enums.packet} + */ + this.tag = enums.packet.symmetricallyEncrypted; + /** + * Encrypted secret-key data + */ + this.encrypted = null; + /** + * Decrypted packets contained within. + * @type {module:packet.List} + */ + this.packets = null; + /** + * When true, decrypt fails if message is not integrity protected + * @see module:config.ignore_mdc_error + */ + this.ignore_mdc_error = config.ignore_mdc_error; +} + +SymmetricallyEncrypted.prototype.read = function (bytes) { + this.encrypted = bytes; +}; + +SymmetricallyEncrypted.prototype.write = function () { + return this.encrypted; +}; + +/** + * Decrypt the symmetrically-encrypted packet data + * See {@link https://tools.ietf.org/html/rfc4880#section-9.2|RFC 4880 9.2} for algorithms. + * @param {module:enums.symmetric} sessionKeyAlgorithm Symmetric key algorithm to use + * @param {Uint8Array} key The key of cipher blocksize length to be used + * @returns {Promise} + * @async + */ +SymmetricallyEncrypted.prototype.decrypt = async function (sessionKeyAlgorithm, key) { + // If MDC errors are not being ignored, all missing MDC packets in symmetrically encrypted data should throw an error + if (!this.ignore_mdc_error) { + throw new Error('Decryption failed due to missing MDC.'); + } + + this.encrypted = await stream.readToEnd(this.encrypted); + const decrypted = await crypto.cfb.decrypt(sessionKeyAlgorithm, key, + this.encrypted.subarray(crypto.cipher[sessionKeyAlgorithm].blockSize + 2), + this.encrypted.subarray(2, crypto.cipher[sessionKeyAlgorithm].blockSize + 2) + ); + + await this.packets.read(decrypted); + + return true; +}; + +/** + * Encrypt the symmetrically-encrypted packet data + * See {@link https://tools.ietf.org/html/rfc4880#section-9.2|RFC 4880 9.2} for algorithms. + * @param {module:enums.symmetric} sessionKeyAlgorithm Symmetric key algorithm to use + * @param {Uint8Array} key The key of cipher blocksize length to be used + * @returns {Promise} + * @async + */ +SymmetricallyEncrypted.prototype.encrypt = async function (algo, key) { + const data = this.packets.write(); + + const prefix = await crypto.getPrefixRandom(algo); + const FRE = await crypto.cfb.encrypt(algo, key, prefix, new Uint8Array(crypto.cipher[algo].blockSize)); + const ciphertext = await crypto.cfb.encrypt(algo, key, data, FRE.subarray(2)); + this.encrypted = util.concat([FRE, ciphertext]); + + return true; +}; + +export default SymmetricallyEncrypted; diff --git a/src/packet/symmetrically_encrypted_data.js b/src/packet/symmetrically_encrypted_data.js deleted file mode 100644 index ec7fdf31..00000000 --- a/src/packet/symmetrically_encrypted_data.js +++ /dev/null @@ -1,127 +0,0 @@ -// GPG4Browsers - An OpenPGP implementation in javascript -// Copyright (C) 2011 Recurity Labs GmbH -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 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 - -/** - * @requires web-stream-tools - * @requires config - * @requires crypto - * @requires enums - * @requires util - * @requires packet - */ - -import stream from 'web-stream-tools'; -import config from '../config'; -import crypto from '../crypto'; -import enums from '../enums'; -import util from '../util'; -import { - LiteralDataPacket, - CompressedDataPacket, - OnePassSignaturePacket, - SignaturePacket -} from '../packet'; - -/** - * Implementation of the Symmetrically Encrypted Data Packet (Tag 9) - * - * {@link https://tools.ietf.org/html/rfc4880#section-5.7|RFC4880 5.7}: - * The Symmetrically Encrypted Data packet contains data encrypted with a - * symmetric-key algorithm. When it has been decrypted, it contains other - * packets (usually a literal data packet or compressed data packet, but in - * theory other Symmetrically Encrypted Data packets or sequences of packets - * that form whole OpenPGP messages). - * @memberof module:packet - */ -class SymmetricallyEncryptedDataPacket { - constructor() { - /** - * Packet type - * @type {module:enums.packet} - */ - this.tag = enums.packet.symmetricallyEncryptedData; - /** - * Encrypted secret-key data - */ - this.encrypted = null; - /** - * Decrypted packets contained within. - * @type {PacketList} - */ - this.packets = null; - /** - * When true, decrypt fails if message is not integrity protected - * @see module:config.ignoreMdcError - */ - this.ignoreMdcError = config.ignoreMdcError; - } - - read(bytes) { - this.encrypted = bytes; - } - - write() { - return this.encrypted; - } - - /** - * Decrypt the symmetrically-encrypted packet data - * See {@link https://tools.ietf.org/html/rfc4880#section-9.2|RFC 4880 9.2} for algorithms. - * @param {module:enums.symmetric} sessionKeyAlgorithm Symmetric key algorithm to use - * @param {Uint8Array} key The key of cipher blocksize length to be used - * @throws {Error} if decryption was not successful - * @async - */ - async decrypt(sessionKeyAlgorithm, key, streaming) { - // If MDC errors are not being ignored, all missing MDC packets in symmetrically encrypted data should throw an error - if (!this.ignoreMdcError) { - throw new Error('Decryption failed due to missing MDC.'); - } - - this.encrypted = await stream.readToEnd(this.encrypted); - const decrypted = await crypto.cfb.decrypt(sessionKeyAlgorithm, key, - this.encrypted.subarray(crypto.cipher[sessionKeyAlgorithm].blockSize + 2), - this.encrypted.subarray(2, crypto.cipher[sessionKeyAlgorithm].blockSize + 2) - ); - - await this.packets.read(decrypted, { - LiteralDataPacket, - CompressedDataPacket, - OnePassSignaturePacket, - SignaturePacket - }, streaming); - } - - /** - * Encrypt the symmetrically-encrypted packet data - * See {@link https://tools.ietf.org/html/rfc4880#section-9.2|RFC 4880 9.2} for algorithms. - * @param {module:enums.symmetric} sessionKeyAlgorithm Symmetric key algorithm to use - * @param {Uint8Array} key The key of cipher blocksize length to be used - * @throws {Error} if encryption was not successful - * @async - */ - async encrypt(algo, key) { - const data = this.packets.write(); - - const prefix = await crypto.getPrefixRandom(algo); - const FRE = await crypto.cfb.encrypt(algo, key, prefix, new Uint8Array(crypto.cipher[algo].blockSize)); - const ciphertext = await crypto.cfb.encrypt(algo, key, data, FRE.subarray(2)); - this.encrypted = util.concat([FRE, ciphertext]); - } -} - -export default SymmetricallyEncryptedDataPacket; diff --git a/src/packet/trust.js b/src/packet/trust.js index 14226bd6..adc0148e 100644 --- a/src/packet/trust.js +++ b/src/packet/trust.js @@ -1,5 +1,3 @@ -/* eslint class-methods-use-this: ["error", { "exceptMethods": ["read"] }] */ - /** * @requires enums */ @@ -21,18 +19,17 @@ import enums from '../enums'; * transferred to other users, and they SHOULD be ignored on any input * other than local keyring files. * @memberof module:packet + * @constructor */ -class TrustPacket { - constructor() { - this.tag = enums.packet.trust; - } - - /** - * Parsing function for a trust packet (tag 12). - * Currently not implemented as we ignore trust packets - * @param {String} byptes payload of a tag 12 packet - */ - read() {} // TODO +function Trust() { + this.tag = enums.packet.trust; } -export default TrustPacket; +/** + * Parsing function for a trust packet (tag 12). + * Currently not implemented as we ignore trust packets + * @param {String} byptes payload of a tag 12 packet + */ +Trust.prototype.read = function () {}; // TODO + +export default Trust; diff --git a/src/packet/user_attribute.js b/src/packet/user_attribute.js index cba957d7..5485829a 100644 --- a/src/packet/user_attribute.js +++ b/src/packet/user_attribute.js @@ -21,7 +21,7 @@ * @requires util */ -import { readSimpleLength, writeSimpleLength } from './packet'; +import packet from './packet'; import enums from '../enums'; import util from '../util'; @@ -42,54 +42,53 @@ import util from '../util'; * User Attribute packet as a User ID packet with opaque contents, but * an implementation may use any method desired. * @memberof module:packet + * @constructor */ -class UserAttributePacket { - constructor() { - this.tag = enums.packet.userAttribute; - this.attributes = []; - } +function UserAttribute() { + this.tag = enums.packet.userAttribute; + this.attributes = []; +} - /** - * parsing function for a user attribute packet (tag 17). - * @param {Uint8Array} input payload of a tag 17 packet - */ - read(bytes) { - let i = 0; - while (i < bytes.length) { - const len = readSimpleLength(bytes.subarray(i, bytes.length)); - i += len.offset; +/** + * parsing function for a user attribute packet (tag 17). + * @param {Uint8Array} input payload of a tag 17 packet + */ +UserAttribute.prototype.read = function(bytes) { + let i = 0; + while (i < bytes.length) { + const len = packet.readSimpleLength(bytes.subarray(i, bytes.length)); + i += len.offset; - this.attributes.push(util.uint8ArrayToStr(bytes.subarray(i, i + len.len))); - i += len.len; - } + this.attributes.push(util.Uint8Array_to_str(bytes.subarray(i, i + len.len))); + i += len.len; } +}; - /** - * Creates a binary representation of the user attribute packet - * @returns {Uint8Array} string representation - */ - write() { - const arr = []; - for (let i = 0; i < this.attributes.length; i++) { - arr.push(writeSimpleLength(this.attributes[i].length)); - arr.push(util.strToUint8Array(this.attributes[i])); - } - return util.concatUint8Array(arr); +/** + * Creates a binary representation of the user attribute packet + * @returns {Uint8Array} string representation + */ +UserAttribute.prototype.write = function() { + const arr = []; + for (let i = 0; i < this.attributes.length; i++) { + arr.push(packet.writeSimpleLength(this.attributes[i].length)); + arr.push(util.str_to_Uint8Array(this.attributes[i])); } + return util.concatUint8Array(arr); +}; - /** - * Compare for equality - * @param {UserAttributePacket} usrAttr - * @returns {Boolean} true if equal - */ - equals(usrAttr) { - if (!usrAttr || !(usrAttr instanceof UserAttributePacket)) { - return false; - } - return this.attributes.every(function(attr, index) { - return attr === usrAttr.attributes[index]; - }); +/** + * Compare for equality + * @param {module:packet.UserAttribute} usrAttr + * @returns {Boolean} true if equal + */ +UserAttribute.prototype.equals = function(usrAttr) { + if (!usrAttr || !(usrAttr instanceof UserAttribute)) { + return false; } -} + return this.attributes.every(function(attr, index) { + return attr === usrAttr.attributes[index]; + }); +}; -export default UserAttributePacket; +export default UserAttribute; diff --git a/src/packet/userid.js b/src/packet/userid.js index d8082eca..89a5861e 100644 --- a/src/packet/userid.js +++ b/src/packet/userid.js @@ -19,11 +19,9 @@ * @requires enums * @requires util */ -import emailAddresses from 'email-addresses'; import enums from '../enums'; import util from '../util'; -import config from '../config'; /** * Implementation of the User ID Packet (Tag 13) @@ -34,69 +32,56 @@ import config from '../config'; * restrictions on its content. The packet length in the header * specifies the length of the User ID. * @memberof module:packet + * @constructor */ -class UserIDPacket { - constructor() { - this.tag = enums.packet.userID; - /** A string containing the user id. Usually in the form - * John Doe - * @type {String} - */ - this.userid = ''; +function Userid() { + this.tag = enums.packet.userid; + /** A string containing the user id. Usually in the form + * John Doe + * @type {String} + */ + this.userid = ''; - this.name = ''; - this.email = ''; - this.comment = ''; - } + this.name = ''; + this.email = ''; + this.comment = ''; +} - /** - * Create UserIDPacket instance from object - * @param {Object} userId object specifying userId name, email and comment - * @returns {module:userid.UserIDPacket} - * @static - */ - static fromObject(userId) { - if (util.isString(userId) || - (userId.name && !util.isString(userId.name)) || - (userId.email && !util.isEmailAddress(userId.email)) || - (userId.comment && !util.isString(userId.comment))) { - throw new Error('Invalid user ID format'); - } - const packet = new UserIDPacket(); - Object.assign(packet, userId); - const components = []; - if (packet.name) components.push(packet.name); - if (packet.comment) components.push(`(${packet.comment})`); - if (packet.email) components.push(`<${packet.email}>`); - packet.userid = components.join(' '); - return packet; - } +/** + * Parsing function for a user id packet (tag 13). + * @param {Uint8Array} input payload of a tag 13 packet + */ +Userid.prototype.read = function (bytes) { + this.parse(util.decode_utf8(bytes)); +}; - /** - * Parsing function for a user id packet (tag 13). - * @param {Uint8Array} input payload of a tag 13 packet - */ - read(bytes) { - const userid = util.decodeUtf8(bytes); - if (userid.length > config.maxUseridLength) { - throw new Error('User ID string is too long'); - } - try { - const { name, address: email, comments } = emailAddresses.parseOneAddress({ input: userid, atInDisplayName: true }); - this.comment = comments.replace(/^\(|\)$/g, ''); - this.name = name; - this.email = email; - } catch (e) {} - this.userid = userid; - } +/** + * Parse userid string, e.g. 'John Doe ' + */ +Userid.prototype.parse = function (userid) { + try { + Object.assign(this, util.parseUserId(userid)); + } catch (e) {} + this.userid = userid; +}; - /** - * Creates a binary representation of the user id packet - * @returns {Uint8Array} binary representation - */ - write() { - return util.encodeUtf8(this.userid); +/** + * Creates a binary representation of the user id packet + * @returns {Uint8Array} binary representation + */ +Userid.prototype.write = function () { + return util.encode_utf8(this.userid); +}; + +/** + * Set userid string from object, e.g. { name:'Phil Zimmermann', email:'phil@openpgp.org' } + */ +Userid.prototype.format = function (userid) { + if (util.isString(userid)) { + userid = util.parseUserId(userid); } -} + Object.assign(this, userid); + this.userid = util.formatUserId(userid); +}; -export default UserIDPacket; +export default Userid; diff --git a/src/polyfills.js b/src/polyfills.js index 1642ee31..27b88917 100644 --- a/src/polyfills.js +++ b/src/polyfills.js @@ -1,14 +1,64 @@ /** + * @fileoverview Old browser polyfills + * All are listed as dev dependencies because Node does not need them + * and for browser babel will take care of it + * @requires util * @module polyfills */ +import util from './util'; + +if (typeof global !== 'undefined') { + /******************************************************************** + * NOTE: This list is duplicated in Gruntfile.js, * + * so that these polyfills are only included in the compat bundle. * + ********************************************************************/ + + try { + if (typeof global.fetch === 'undefined') { + require('whatwg-fetch'); + } + if (typeof Array.prototype.fill === 'undefined') { + require('core-js/fn/array/fill'); + } + if (typeof Array.prototype.find === 'undefined') { + require('core-js/fn/array/find'); + } + if (typeof Array.prototype.includes === 'undefined') { + require('core-js/fn/array/includes'); + } + if (typeof Array.from === 'undefined') { + require('core-js/fn/array/from'); + } + + // No if-statement on Promise because of IE11. Otherwise Promise is undefined in the service worker. + require('core-js/fn/promise'); + + if (typeof Uint8Array.from === 'undefined') { + require('core-js/fn/typed/uint8-array'); + } + if (typeof String.prototype.repeat === 'undefined') { + require('core-js/fn/string/repeat'); + } + if (typeof Symbol === 'undefined') { + require('core-js/fn/symbol'); + } + if (typeof Object.assign === 'undefined') { + require('core-js/fn/object/assign'); + } + } catch (e) {} +} + +if (typeof TransformStream === 'undefined') { + require('@mattiasbuelens/web-streams-polyfill/es6'); +} if (typeof TextEncoder === 'undefined') { - const nodeUtil = require('util') || {}; - globalThis.TextEncoder = nodeUtil.TextEncoder; - globalThis.TextDecoder = nodeUtil.TextDecoder; + const nodeUtil = util.nodeRequire('util') || {}; + global.TextEncoder = nodeUtil.TextEncoder; + global.TextDecoder = nodeUtil.TextDecoder; } if (typeof TextEncoder === 'undefined') { const textEncoding = require('text-encoding-utf-8'); - globalThis.TextEncoder = textEncoding.TextEncoder; - globalThis.TextDecoder = textEncoding.TextDecoder; + global.TextEncoder = textEncoding.TextEncoder; + global.TextDecoder = textEncoding.TextDecoder; } diff --git a/src/signature.js b/src/signature.js index 39e0280d..a533eee5 100644 --- a/src/signature.js +++ b/src/signature.js @@ -22,37 +22,30 @@ * @module signature */ -import { armor, unarmor } from './encoding/armor'; -import { PacketList, SignaturePacket } from './packet'; +import armor from './encoding/armor'; +import packet from './packet'; import enums from './enums'; /** - * Class that represents an OpenPGP signature. + * @class + * @classdesc Class that represents an OpenPGP signature. + * @param {module:packet.List} packetlist The signature packets */ -export class Signature { - /** - * @param {PacketList} packetlist The signature packets - */ - constructor(packetlist) { - this.packets = packetlist || new PacketList(); +export function Signature(packetlist) { + if (!(this instanceof Signature)) { + return new Signature(packetlist); } + this.packets = packetlist || new packet.List(); +} - /** - * Returns binary encoded signature - * @returns {ReadableStream} binary signature - */ - write() { - return this.packets.write(); - } - /** - * Returns ASCII armored text of signature - * @returns {ReadableStream} ASCII armor - */ - armor() { - return armor(enums.armor.signature, this.write()); - } -} +/** + * Returns ASCII armored text of signature + * @returns {ReadableStream} ASCII armor + */ +Signature.prototype.armor = function() { + return armor.encode(enums.armor.signature, this.packets.write()); +}; /** * reads an OpenPGP armored signature and returns a signature object @@ -61,9 +54,9 @@ export class Signature { * @async * @static */ -export async function readArmoredSignature(armoredText) { - const input = await unarmor(armoredText); - return readSignature(input.data); +export async function readArmored(armoredText) { + const input = await armor.decode(armoredText); + return read(input.data); } /** @@ -73,8 +66,8 @@ export async function readArmoredSignature(armoredText) { * @async * @static */ -export async function readSignature(input) { - const packetlist = new PacketList(); - await packetlist.read(input, { SignaturePacket }); +export async function read(input) { + const packetlist = new packet.List(); + await packetlist.read(input); return new Signature(packetlist); } diff --git a/src/type/ecdh_symkey.js b/src/type/ecdh_symkey.js index 4ad26724..08c961f9 100644 --- a/src/type/ecdh_symkey.js +++ b/src/type/ecdh_symkey.js @@ -24,41 +24,46 @@ import util from '../util'; -class ECDHSymmetricKey { - constructor(data) { - if (typeof data === 'undefined') { - data = new Uint8Array([]); - } else if (util.isString(data)) { - data = util.strToUint8Array(data); - } else { - data = new Uint8Array(data); - } - this.data = data; +/** + * @constructor + */ +function ECDHSymmetricKey(data) { + if (typeof data === 'undefined') { + data = new Uint8Array([]); + } else if (util.isString(data)) { + data = util.str_to_Uint8Array(data); + } else { + data = new Uint8Array(data); } + this.data = data; +} - /** - * Read an ECDHSymmetricKey from an Uint8Array - * @param {Uint8Array} input Where to read the encoded symmetric key from - * @returns {Number} Number of read bytes - */ - read(input) { - if (input.length >= 1) { - const length = input[0]; - if (input.length >= 1 + length) { - this.data = input.subarray(1, 1 + length); - return 1 + this.data.length; - } +/** + * Read an ECDHSymmetricKey from an Uint8Array + * @param {Uint8Array} input Where to read the encoded symmetric key from + * @returns {Number} Number of read bytes + */ +ECDHSymmetricKey.prototype.read = function (input) { + if (input.length >= 1) { + const length = input[0]; + if (input.length >= 1 + length) { + this.data = input.subarray(1, 1 + length); + return 1 + this.data.length; } - throw new Error('Invalid symmetric key'); } + throw new Error('Invalid symmetric key'); +}; - /** - * Write an ECDHSymmetricKey as an Uint8Array - * @returns {Uint8Array} An array containing the value - */ - write() { - return util.concatUint8Array([new Uint8Array([this.data.length]), this.data]); - } -} +/** + * Write an ECDHSymmetricKey as an Uint8Array + * @returns {Uint8Array} An array containing the value + */ +ECDHSymmetricKey.prototype.write = function () { + return util.concatUint8Array([new Uint8Array([this.data.length]), this.data]); +}; + +ECDHSymmetricKey.fromClone = function (clone) { + return new ECDHSymmetricKey(clone.data); +}; export default ECDHSymmetricKey; diff --git a/src/type/kdf_params.js b/src/type/kdf_params.js index c46619dc..82253c90 100644 --- a/src/type/kdf_params.js +++ b/src/type/kdf_params.js @@ -27,43 +27,47 @@ * @module type/kdf_params */ -class KDFParams { - /** - * @param {enums.hash} hash Hash algorithm - * @param {enums.symmetric} cipher Symmetric algorithm - */ - constructor(data) { - if (data) { - const { hash, cipher } = data; - this.hash = hash; - this.cipher = cipher; - } else { - this.hash = null; - this.cipher = null; - } +/** + * @constructor + * @param {enums.hash} hash Hash algorithm + * @param {enums.symmetric} cipher Symmetric algorithm + */ +function KDFParams(data) { + if (data) { + const { hash, cipher } = data; + this.hash = hash; + this.cipher = cipher; + } else { + this.hash = null; + this.cipher = null; } +} - /** - * Read KDFParams from an Uint8Array - * @param {Uint8Array} input Where to read the KDFParams from - * @returns {Number} Number of read bytes - */ - read(input) { - if (input.length < 4 || input[0] !== 3 || input[1] !== 1) { - throw new Error('Cannot read KDFParams'); - } - this.hash = input[2]; - this.cipher = input[3]; - return 4; +/** + * Read KDFParams from an Uint8Array + * @param {Uint8Array} input Where to read the KDFParams from + * @returns {Number} Number of read bytes + */ +KDFParams.prototype.read = function (input) { + if (input.length < 4 || input[0] !== 3 || input[1] !== 1) { + throw new Error('Cannot read KDFParams'); } + this.hash = input[2]; + this.cipher = input[3]; + return 4; +}; - /** - * Write KDFParams to an Uint8Array - * @returns {Uint8Array} Array with the KDFParams value - */ - write() { - return new Uint8Array([3, 1, this.hash, this.cipher]); - } -} +/** + * Write KDFParams to an Uint8Array + * @returns {Uint8Array} Array with the KDFParams value + */ +KDFParams.prototype.write = function () { + return new Uint8Array([3, 1, this.hash, this.cipher]); +}; + +KDFParams.fromClone = function (clone) { + const { hash, cipher } = clone; + return new KDFParams({ hash, cipher }); +}; export default KDFParams; diff --git a/src/type/keyid.js b/src/type/keyid.js index 5b2719eb..382b8246 100644 --- a/src/type/keyid.js +++ b/src/type/keyid.js @@ -29,75 +29,82 @@ import util from '../util.js'; -class Keyid { - constructor() { - this.bytes = ''; - } +/** + * @constructor + */ +function Keyid() { + this.bytes = ''; +} - /** - * Parsing method for a key id - * @param {Uint8Array} bytes Input to read the key id from - */ - read(bytes) { - this.bytes = util.uint8ArrayToStr(bytes.subarray(0, 8)); - } +/** + * Parsing method for a key id + * @param {Uint8Array} input Input to read the key id from + */ +Keyid.prototype.read = function(bytes) { + this.bytes = util.Uint8Array_to_str(bytes.subarray(0, 8)); +}; - /** - * Serializes the Key ID - * @returns {Uint8Array} Key ID as a Uint8Array - */ - write() { - return util.strToUint8Array(this.bytes); - } +/** + * Serializes the Key ID + * @returns {Uint8Array} Key ID as a Uint8Array + */ +Keyid.prototype.write = function() { + return util.str_to_Uint8Array(this.bytes); +}; - /** - * Returns the Key ID represented as a hexadecimal string - * @returns {String} Key ID as a hexadecimal string - */ - toHex() { - return util.strToHex(this.bytes); - } +/** + * Returns the Key ID represented as a hexadecimal string + * @returns {String} Key ID as a hexadecimal string + */ +Keyid.prototype.toHex = function() { + return util.str_to_hex(this.bytes); +}; - /** - * Checks equality of Key ID's - * @param {Keyid} keyid - * @param {Boolean} matchWildcard Indicates whether to check if either keyid is a wildcard - */ - equals(keyid, matchWildcard = false) { - return (matchWildcard && (keyid.isWildcard() || this.isWildcard())) || this.bytes === keyid.bytes; - } +/** + * Checks equality of Key ID's + * @param {Keyid} keyid + * @param {Boolean} matchWildcard Indicates whether to check if either keyid is a wildcard + */ +Keyid.prototype.equals = function(keyid, matchWildcard = false) { + return (matchWildcard && (keyid.isWildcard() || this.isWildcard())) || this.bytes === keyid.bytes; +}; + +/** + * Checks to see if the Key ID is unset + * @returns {Boolean} true if the Key ID is null + */ +Keyid.prototype.isNull = function() { + return this.bytes === ''; +}; - /** - * Checks to see if the Key ID is unset - * @returns {Boolean} true if the Key ID is null - */ - isNull() { - return this.bytes === ''; - } +/** + * Checks to see if the Key ID is a "wildcard" Key ID (all zeros) + * @returns {Boolean} true if this is a wildcard Key ID + */ +Keyid.prototype.isWildcard = function() { + return /^0+$/.test(this.toHex()); +}; - /** - * Checks to see if the Key ID is a "wildcard" Key ID (all zeros) - * @returns {Boolean} true if this is a wildcard Key ID - */ - isWildcard() { - return /^0+$/.test(this.toHex()); - } +Keyid.mapToHex = function (keyId) { + return keyId.toHex(); +}; - static mapToHex(keyId) { - return keyId.toHex(); - } +Keyid.fromClone = function (clone) { + const keyid = new Keyid(); + keyid.bytes = clone.bytes; + return keyid; +}; - static fromId(hex) { - const keyid = new Keyid(); - keyid.read(util.hexToUint8Array(hex)); - return keyid; - } +Keyid.fromId = function (hex) { + const keyid = new Keyid(); + keyid.read(util.hex_to_Uint8Array(hex)); + return keyid; +}; - static wildcard() { - const keyid = new Keyid(); - keyid.read(new Uint8Array(8)); - return keyid; - } -} +Keyid.wildcard = function () { + const keyid = new Keyid(); + keyid.read(new Uint8Array(8)); + return keyid; +}; export default Keyid; diff --git a/src/type/mpi.js b/src/type/mpi.js new file mode 100644 index 00000000..e4d6c952 --- /dev/null +++ b/src/type/mpi.js @@ -0,0 +1,138 @@ +// GPG4Browsers - An OpenPGP implementation in javascript +// Copyright (C) 2011 Recurity Labs GmbH +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 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 + +// Hint: We hold our MPIs as an array of octets in big endian format preceding a two +// octet scalar: MPI: [a,b,c,d,e,f] +// - MPI size: (a << 8) | b +// - MPI = c | d << 8 | e << ((MPI.length -2)*8) | f ((MPI.length -2)*8) + +/** + * Implementation of type MPI ({@link https://tools.ietf.org/html/rfc4880#section-3.2|RFC4880 3.2}) + * Multiprecision integers (also called MPIs) are unsigned integers used + * to hold large integers such as the ones used in cryptographic + * calculations. + * An MPI consists of two pieces: a two-octet scalar that is the length + * of the MPI in bits followed by a string of octets that contain the + * actual integer. + * @requires bn.js + * @requires util + * @module type/mpi + */ + +import BN from 'bn.js'; +import util from '../util'; + +/** + * @constructor + */ +function MPI(data) { + /** An implementation dependent integer */ + if (data instanceof MPI) { + this.data = data.data; + } else if (BN.isBN(data)) { + this.fromBN(data); + } else if (util.isUint8Array(data)) { + this.fromUint8Array(data); + } else if (util.isString(data)) { + this.fromString(data); + } else { + this.data = null; + } +} + +/** + * Parsing function for a MPI ({@link https://tools.ietf.org/html/rfc4880#section-3.2|RFC 4880 3.2}). + * @param {Uint8Array} input Payload of MPI data + * @param {String} endian Endianness of the data; 'be' for big-endian or 'le' for little-endian + * @returns {Integer} Length of data read + */ +MPI.prototype.read = function (bytes, endian = 'be') { + if (util.isString(bytes)) { + bytes = util.str_to_Uint8Array(bytes); + } + + const bits = (bytes[0] << 8) | bytes[1]; + const bytelen = (bits + 7) >>> 3; + const payload = bytes.subarray(2, 2 + bytelen); + + this.fromUint8Array(payload, endian); + + return 2 + bytelen; +}; + +/** + * Converts the mpi object to a bytes as specified in + * {@link https://tools.ietf.org/html/rfc4880#section-3.2|RFC4880 3.2} + * @param {String} endian Endianness of the payload; 'be' for big-endian or 'le' for little-endian + * @param {Integer} length Length of the data part of the MPI + * @returns {Uint8Aray} mpi Byte representation + */ +MPI.prototype.write = function (endian, length) { + return util.Uint8Array_to_MPI(this.toUint8Array(endian, length)); +}; + +MPI.prototype.bitLength = function () { + return (this.data.length - 1) * 8 + util.nbits(this.data[0]); +}; + +MPI.prototype.byteLength = function () { + return this.data.length; +}; + +MPI.prototype.toUint8Array = function (endian, length) { + endian = endian || 'be'; + length = length || this.data.length; + + const payload = new Uint8Array(length); + const start = endian === 'le' ? 0 : length - this.data.length; + payload.set(this.data, start); + if (endian === 'le') { + payload.reverse(); + } + return payload; +}; + +MPI.prototype.fromUint8Array = function (bytes, endian = 'be') { + this.data = new Uint8Array(bytes.length); + this.data.set(bytes); + + if (endian === 'le') { + this.data.reverse(); + } +}; + +MPI.prototype.toString = function () { + return util.Uint8Array_to_str(this.toUint8Array()); +}; + +MPI.prototype.fromString = function (str, endian = 'be') { + this.fromUint8Array(util.str_to_Uint8Array(str), endian); +}; + +MPI.prototype.toBN = function () { + return new BN(this.toUint8Array()); +}; + +MPI.prototype.fromBN = function (bn) { + this.data = bn.toArrayLike(Uint8Array); +}; + +MPI.fromClone = function (clone) { + return new MPI(clone.data); +}; + +export default MPI; diff --git a/src/type/oid.js b/src/type/oid.js index 7316bd86..cc9efe99 100644 --- a/src/type/oid.js +++ b/src/type/oid.js @@ -37,69 +37,74 @@ import util from '../util'; import enums from '../enums'; -class OID { - constructor(oid) { - if (oid instanceof OID) { - this.oid = oid.oid; - } else if (util.isArray(oid) || - util.isUint8Array(oid)) { - oid = new Uint8Array(oid); - if (oid[0] === 0x06) { // DER encoded oid byte array - if (oid[1] !== oid.length - 2) { - throw new Error('Length mismatch in DER encoded oid'); - } - oid = oid.subarray(2); +/** + * @constructor + */ +function OID(oid) { + if (oid instanceof OID) { + this.oid = oid.oid; + } else if (util.isArray(oid) || + util.isUint8Array(oid)) { + oid = new Uint8Array(oid); + if (oid[0] === 0x06) { // DER encoded oid byte array + if (oid[1] !== oid.length - 2) { + throw new Error('Length mismatch in DER encoded oid'); } - this.oid = oid; - } else { - this.oid = ''; + oid = oid.subarray(2); } + this.oid = oid; + } else { + this.oid = ''; } +} - /** - * Method to read an OID object - * @param {Uint8Array} input Where to read the OID from - * @returns {Number} Number of read bytes - */ - read(input) { - if (input.length >= 1) { - const length = input[0]; - if (input.length >= 1 + length) { - this.oid = input.subarray(1, 1 + length); - return 1 + this.oid.length; - } +/** + * Method to read an OID object + * @param {Uint8Array} input Where to read the OID from + * @returns {Number} Number of read bytes + */ +OID.prototype.read = function (input) { + if (input.length >= 1) { + const length = input[0]; + if (input.length >= 1 + length) { + this.oid = input.subarray(1, 1 + length); + return 1 + this.oid.length; } - throw new Error('Invalid oid'); } + throw new Error('Invalid oid'); +}; - /** - * Serialize an OID object - * @returns {Uint8Array} Array with the serialized value the OID - */ - write() { - return util.concatUint8Array([new Uint8Array([this.oid.length]), this.oid]); - } +/** + * Serialize an OID object + * @returns {Uint8Array} Array with the serialized value the OID + */ +OID.prototype.write = function () { + return util.concatUint8Array([new Uint8Array([this.oid.length]), this.oid]); +}; - /** - * Serialize an OID object as a hex string - * @returns {string} String with the hex value of the OID - */ - toHex() { - return util.uint8ArrayToHex(this.oid); - } +/** + * Serialize an OID object as a hex string + * @returns {string} String with the hex value of the OID + */ +OID.prototype.toHex = function() { + return util.Uint8Array_to_hex(this.oid); +}; - /** - * If a known curve object identifier, return the canonical name of the curve - * @returns {string} String with the canonical name of the curve - */ - getName() { - const hex = this.toHex(); - if (enums.curve[hex]) { - return enums.write(enums.curve, hex); - } else { - throw new Error('Unknown curve object identifier.'); - } +/** + * If a known curve object identifier, return the canonical name of the curve + * @returns {string} String with the canonical name of the curve + */ +OID.prototype.getName = function() { + const hex = this.toHex(); + if (enums.curve[hex]) { + return enums.write(enums.curve, hex); + } else { + throw new Error('Unknown curve object identifier.'); } -} +}; + +OID.fromClone = function (clone) { + return new OID(clone.oid); +}; export default OID; diff --git a/src/type/s2k.js b/src/type/s2k.js index ce1dcd84..2b45d77e 100644 --- a/src/type/s2k.js +++ b/src/type/s2k.js @@ -36,157 +36,168 @@ import crypto from '../crypto'; import enums from '../enums.js'; import util from '../util.js'; -class S2K { - constructor() { - /** @type {module:enums.hash} */ - this.algorithm = 'sha256'; - /** @type {module:enums.s2k} */ - this.type = 'iterated'; - /** @type {Integer} */ - this.c = config.s2kIterationCountByte; - /** Eight bytes of salt in a binary string. - * @type {String} - */ - this.salt = null; - } - - get_count() { - // Exponent bias, defined in RFC4880 - const expbias = 6; - - return (16 + (this.c & 15)) << ((this.c >> 4) + expbias); - } - - /** - * Parsing function for a string-to-key specifier ({@link https://tools.ietf.org/html/rfc4880#section-3.7|RFC 4880 3.7}). - * @param {String} bytes Payload of string-to-key specifier - * @returns {Integer} Actual length of the object +/** + * @constructor + */ +function S2K() { + /** @type {module:enums.hash} */ + this.algorithm = 'sha256'; + /** @type {module:enums.s2k} */ + this.type = 'iterated'; + /** @type {Integer} */ + this.c = config.s2k_iteration_count_byte; + /** Eight bytes of salt in a binary string. + * @type {String} */ - read(bytes) { - let i = 0; - this.type = enums.read(enums.s2k, bytes[i++]); - this.algorithm = bytes[i++]; - if (this.type !== 'gnu') { - this.algorithm = enums.read(enums.hash, this.algorithm); - } - - switch (this.type) { - case 'simple': - break; + this.salt = null; +} - case 'salted': - this.salt = bytes.subarray(i, i + 8); - i += 8; - break; +S2K.prototype.get_count = function () { + // Exponent bias, defined in RFC4880 + const expbias = 6; - case 'iterated': - this.salt = bytes.subarray(i, i + 8); - i += 8; + return (16 + (this.c & 15)) << ((this.c >> 4) + expbias); +}; - // Octet 10: count, a one-octet, coded value - this.c = bytes[i++]; - break; +/** + * Parsing function for a string-to-key specifier ({@link https://tools.ietf.org/html/rfc4880#section-3.7|RFC 4880 3.7}). + * @param {String} input Payload of string-to-key specifier + * @returns {Integer} Actual length of the object + */ +S2K.prototype.read = function (bytes) { + let i = 0; + this.type = enums.read(enums.s2k, bytes[i++]); + this.algorithm = bytes[i++]; + if (this.type !== 'gnu') { + this.algorithm = enums.read(enums.hash, this.algorithm); + } - case 'gnu': - if (util.uint8ArrayToStr(bytes.subarray(i, i + 3)) === "GNU") { - i += 3; // GNU - const gnuExtType = 1000 + bytes[i++]; - if (gnuExtType === 1001) { - this.type = 'gnu-dummy'; - // GnuPG extension mode 1001 -- don't write secret key at all - } else { - throw new Error("Unknown s2k gnu protection mode."); - } + switch (this.type) { + case 'simple': + break; + + case 'salted': + this.salt = bytes.subarray(i, i + 8); + i += 8; + break; + + case 'iterated': + this.salt = bytes.subarray(i, i + 8); + i += 8; + + // Octet 10: count, a one-octet, coded value + this.c = bytes[i++]; + break; + + case 'gnu': + if (util.Uint8Array_to_str(bytes.subarray(i, i + 3)) === "GNU") { + i += 3; // GNU + const gnuExtType = 1000 + bytes[i++]; + if (gnuExtType === 1001) { + this.type = 'gnu-dummy'; + // GnuPG extension mode 1001 -- don't write secret key at all } else { - throw new Error("Unknown s2k type."); + throw new Error("Unknown s2k gnu protection mode."); } - break; - - default: + } else { throw new Error("Unknown s2k type."); - } + } + break; - return i; + default: + throw new Error("Unknown s2k type."); } - /** - * Serializes s2k information - * @returns {Uint8Array} binary representation of s2k - */ - write() { - if (this.type === 'gnu-dummy') { - return new Uint8Array([101, 0, ...util.strToUint8Array('GNU'), 1]); - } + return i; +}; + + +/** + * Serializes s2k information + * @returns {Uint8Array} binary representation of s2k + */ +S2K.prototype.write = function () { + if (this.type === 'gnu-dummy') { + return new Uint8Array([101, 0, ...util.str_to_Uint8Array('GNU'), 1]); + } + + const arr = [new Uint8Array([enums.write(enums.s2k, this.type), enums.write(enums.hash, this.algorithm)])]; + + switch (this.type) { + case 'simple': + break; + case 'salted': + arr.push(this.salt); + break; + case 'iterated': + arr.push(this.salt); + arr.push(new Uint8Array([this.c])); + break; + case 'gnu': + throw new Error("GNU s2k type not supported."); + default: + throw new Error("Unknown s2k type."); + } + + return util.concatUint8Array(arr); +}; + +/** + * Produces a key using the specified passphrase and the defined + * hashAlgorithm + * @param {String} passphrase Passphrase containing user input + * @returns {Uint8Array} Produced key with a length corresponding to + * hashAlgorithm hash length + */ +S2K.prototype.produce_key = async function (passphrase, numBytes) { + passphrase = util.encode_utf8(passphrase); + const algorithm = enums.write(enums.hash, this.algorithm); - const arr = [new Uint8Array([enums.write(enums.s2k, this.type), enums.write(enums.hash, this.algorithm)])]; + const arr = []; + let rlength = 0; + let prefixlen = 0; + while (rlength < numBytes) { + let toHash; switch (this.type) { case 'simple': + toHash = util.concatUint8Array([new Uint8Array(prefixlen), passphrase]); break; case 'salted': - arr.push(this.salt); + toHash = util.concatUint8Array([new Uint8Array(prefixlen), this.salt, passphrase]); break; - case 'iterated': - arr.push(this.salt); - arr.push(new Uint8Array([this.c])); + case 'iterated': { + const data = util.concatUint8Array([this.salt, passphrase]); + let datalen = data.length; + const count = Math.max(this.get_count(), datalen); + toHash = new Uint8Array(prefixlen + count); + toHash.set(data, prefixlen); + for (let pos = prefixlen + datalen; pos < count; pos += datalen, datalen *= 2) { + toHash.copyWithin(pos, prefixlen, pos); + } break; + } case 'gnu': throw new Error("GNU s2k type not supported."); default: throw new Error("Unknown s2k type."); } - - return util.concatUint8Array(arr); + const result = await crypto.hash.digest(algorithm, toHash); + arr.push(result); + rlength += result.length; + prefixlen++; } - /** - * Produces a key using the specified passphrase and the defined - * hashAlgorithm - * @param {String} passphrase Passphrase containing user input - * @returns {Uint8Array} Produced key with a length corresponding to - * hashAlgorithm hash length - */ - async produce_key(passphrase, numBytes) { - passphrase = util.encodeUtf8(passphrase); - const algorithm = enums.write(enums.hash, this.algorithm); - - const arr = []; - let rlength = 0; - - let prefixlen = 0; - while (rlength < numBytes) { - let toHash; - switch (this.type) { - case 'simple': - toHash = util.concatUint8Array([new Uint8Array(prefixlen), passphrase]); - break; - case 'salted': - toHash = util.concatUint8Array([new Uint8Array(prefixlen), this.salt, passphrase]); - break; - case 'iterated': { - const data = util.concatUint8Array([this.salt, passphrase]); - let datalen = data.length; - const count = Math.max(this.get_count(), datalen); - toHash = new Uint8Array(prefixlen + count); - toHash.set(data, prefixlen); - for (let pos = prefixlen + datalen; pos < count; pos += datalen, datalen *= 2) { - toHash.copyWithin(pos, prefixlen, pos); - } - break; - } - case 'gnu': - throw new Error("GNU s2k type not supported."); - default: - throw new Error("Unknown s2k type."); - } - const result = await crypto.hash.digest(algorithm, toHash); - arr.push(result); - rlength += result.length; - prefixlen++; - } - - return util.concatUint8Array(arr).subarray(0, numBytes); - } -} + return util.concatUint8Array(arr).subarray(0, numBytes); +}; + +S2K.fromClone = function (clone) { + const s2k = new S2K(); + s2k.algorithm = clone.algorithm; + s2k.type = clone.type; + s2k.c = clone.c; + s2k.salt = clone.salt; + return s2k; +}; export default S2K; diff --git a/src/util.js b/src/util.js index 1d5f97a1..a531db82 100644 --- a/src/util.js +++ b/src/util.js @@ -26,10 +26,11 @@ * @module util */ +import emailAddresses from 'email-addresses'; import stream from 'web-stream-tools'; import config from './config'; import util from './util'; // re-import module to access util functions -import { getBigInteger } from './biginteger'; +import b64 from './encoding/base64'; export default { isString: function(data) { @@ -40,57 +41,109 @@ export default { return Array.prototype.isPrototypeOf(data); }, - isBigInteger: function(data) { - return data !== null && typeof data === 'object' && data.value && - // eslint-disable-next-line valid-typeof - (typeof data.value === 'bigint' || this.isBN(data.value)); - }, + isUint8Array: stream.isUint8Array, + + isStream: stream.isStream, - isBN: function(data) { - return data !== null && typeof data === 'object' && - (data.constructor.name === 'BN' || - (data.constructor.wordSize === 26 && Array.isArray(data.words))); // taken from BN.isBN() + /** + * Get transferable objects to pass buffers with zero copy (similar to "pass by reference" in C++) + * See: https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage + * Also, convert ReadableStreams to MessagePorts + * @param {Object} obj the options object to be passed to the web worker + * @returns {Array} an array of binary data to be passed + */ + getTransferables: function(obj, zero_copy) { + const transferables = []; + util.collectTransferables(obj, transferables, zero_copy); + return transferables.length ? transferables : undefined; }, - isUint8Array: stream.isUint8Array, + collectTransferables: function(obj, collection, zero_copy) { + if (!obj) { + return; + } - isStream: stream.isStream, + if (util.isUint8Array(obj)) { + if (zero_copy && collection.indexOf(obj.buffer) === -1 && !( + navigator.userAgent.indexOf('Version/11.1') !== -1 || // Safari 11.1 + ((navigator.userAgent.match(/Chrome\/(\d+)/) || [])[1] < 56 && navigator.userAgent.indexOf('Edge') === -1) // Chrome < 56 + )) { + collection.push(obj.buffer); + } + return; + } + if (Object.prototype.isPrototypeOf(obj)) { + Object.entries(obj).forEach(([key, value]) => { // recursively search all children + if (util.isStream(value)) { + if (value.locked) { + obj[key] = null; + } else { + const transformed = stream.transformPair(value, async readable => { + const reader = stream.getReader(readable); + const { port1, port2 } = new MessageChannel(); + port1.onmessage = async function({ data: { action } }) { + if (action === 'read') { + try { + const result = await reader.read(); + port1.postMessage(result, util.getTransferables(result)); + } catch (e) { + port1.postMessage({ error: e.message }); + } + } else if (action === 'cancel') { + await transformed.cancel(); + port1.postMessage(); + } + }; + obj[key] = port2; + collection.push(port2); + }); + } + return; + } + if (Object.prototype.toString.call(value) === '[object MessagePort]') { + throw new Error("Can't transfer the same stream twice."); + } + util.collectTransferables(value, collection, zero_copy); + }); + } + }, /** * Convert MessagePorts back to ReadableStreams * @param {Object} obj * @returns {Object} */ - restoreStreams: function(obj, streaming) { - if (Object.prototype.toString.call(obj) === '[object MessagePort]') { - return new (streaming === 'web' ? globalThis.ReadableStream : stream.ReadableStream)({ - pull(controller) { - return new Promise(resolve => { - obj.onmessage = evt => { - const { done, value, error } = evt.data; - if (error) { - controller.error(new Error(error)); - } else if (!done) { - controller.enqueue(value); - } else { - controller.close(); - } - resolve(); - }; - obj.postMessage({ action: 'read' }); - }); - }, - cancel() { - return new Promise(resolve => { - obj.onmessage = resolve; - obj.postMessage({ action: 'cancel' }); - }); - } - }, { highWaterMark: 0 }); - } + restoreStreams: function(obj) { if (Object.prototype.isPrototypeOf(obj) && !Uint8Array.prototype.isPrototypeOf(obj)) { Object.entries(obj).forEach(([key, value]) => { // recursively search all children - obj[key] = util.restoreStreams(value, streaming); + if (Object.prototype.toString.call(value) === '[object MessagePort]') { + obj[key] = new ReadableStream({ + pull(controller) { + return new Promise(resolve => { + value.onmessage = evt => { + const { done, value, error } = evt.data; + if (error) { + controller.error(new Error(error)); + } else if (!done) { + controller.enqueue(value); + } else { + controller.close(); + } + resolve(); + }; + value.postMessage({ action: 'read' }); + }); + }, + cancel() { + return new Promise(resolve => { + value.onmessage = resolve; + value.postMessage({ action: 'cancel' }); + }); + } + }, { highWaterMark: 0 }); + return; + } + util.restoreStreams(value); }); } return obj; @@ -134,7 +187,7 @@ export default { * @param {String} str String to convert * @returns {String} String containing the hexadecimal values */ - strToHex: function (str) { + str_to_hex: function (str) { if (str === null) { return ""; } @@ -157,7 +210,7 @@ export default { * @param {String} str Hex string to convert * @returns {String} */ - hexToStr: function (hex) { + hex_to_str: function (hex) { let str = ''; for (let i = 0; i < hex.length; i += 2) { str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); @@ -166,44 +219,42 @@ export default { }, /** - * Read one MPI from bytes in input - * @param {Uint8Array} bytes input data to parse - * @returns {Uint8Array} parsed MPI + * Convert a Uint8Array to an MPI-formatted Uint8Array. + * Note: the output is **not** an MPI object. + * @see {@link module:type/mpi/MPI.fromUint8Array} + * @see {@link module:type/mpi/MPI.toUint8Array} + * @param {Uint8Array} bin An array of 8-bit integers to convert + * @returns {Uint8Array} MPI-formatted Uint8Array */ - readMPI: function (bytes) { - const bits = (bytes[0] << 8) | bytes[1]; - const bytelen = (bits + 7) >>> 3; - return bytes.subarray(2, 2 + bytelen); + Uint8Array_to_MPI: function (bin) { + const size = (bin.length - 1) * 8 + util.nbits(bin[0]); + const prefix = Uint8Array.from([(size & 0xFF00) >> 8, size & 0xFF]); + return util.concatUint8Array([prefix, bin]); }, /** - * Left-pad Uint8Array to length by adding 0x0 bytes - * @param {Uint8Array} bytes data to pad - * @param {Number} length padded length - * @return {Uint8Array} padded bytes + * Convert a Base-64 encoded string an array of 8-bit integer + * + * Note: accepts both Radix-64 and URL-safe strings + * @param {String} base64 Base-64 encoded string to convert + * @returns {Uint8Array} An array of 8-bit integers */ - leftPad(bytes, length) { - const padded = new Uint8Array(length); - const offset = length - bytes.length; - padded.set(bytes, offset); - return padded; + b64_to_Uint8Array: function (base64) { + return b64.decode(base64.replace(/-/g, '+').replace(/_/g, '/')); }, /** - * Convert a Uint8Array to an MPI-formatted Uint8Array. - * @param {Uint8Array} bin An array of 8-bit integers to convert - * @returns {Uint8Array} MPI-formatted Uint8Array + * Convert an array of 8-bit integer to a Base-64 encoded string + * @param {Uint8Array} bytes An array of 8-bit integers to convert + * @param {bool} url If true, output is URL-safe + * @returns {String} Base-64 encoded string */ - uint8ArrayToMpi: function (bin) { - let i; // index of leading non-zero byte - for (i = 0; i < bin.length; i++) if (bin[i] !== 0) break; - if (i === bin.length) { - throw new Error('Zero MPI'); + Uint8Array_to_b64: function (bytes, url) { + let encoded = b64.encode(bytes).replace(/[\r\n]/g, ''); + if (url) { + encoded = encoded.replace(/[+]/g, '-').replace(/[/]/g, '_').replace(/[=]/g, ''); } - const stripped = bin.subarray(i); - const size = (stripped.length - 1) * 8 + util.nbits(stripped[0]); - const prefix = Uint8Array.from([(size & 0xFF00) >> 8, size & 0xFF]); - return util.concatUint8Array([prefix, stripped]); + return encoded; }, /** @@ -211,7 +262,7 @@ export default { * @param {String} hex A hex string to convert * @returns {Uint8Array} An array of 8-bit integers */ - hexToUint8Array: function (hex) { + hex_to_Uint8Array: function (hex) { const result = new Uint8Array(hex.length >> 1); for (let k = 0; k < hex.length >> 1; k++) { result[k] = parseInt(hex.substr(k << 1, 2), 16); @@ -224,7 +275,7 @@ export default { * @param {Uint8Array} bytes Array of 8-bit integers to convert * @returns {String} Hexadecimal representation of the array */ - uint8ArrayToHex: function (bytes) { + Uint8Array_to_hex: function (bytes) { const r = []; const e = bytes.length; let c = 0; @@ -244,10 +295,10 @@ export default { * @param {String} str String to convert * @returns {Uint8Array} An array of 8-bit integers */ - strToUint8Array: function (str) { + str_to_Uint8Array: function (str) { return stream.transform(str, str => { if (!util.isString(str)) { - throw new Error('strToUint8Array: Data must be in the form of a string'); + throw new Error('str_to_Uint8Array: Data must be in the form of a string'); } const result = new Uint8Array(str.length); @@ -263,7 +314,7 @@ export default { * @param {Uint8Array} bytes An array of 8-bit integers to convert * @returns {String} String representation of the array */ - uint8ArrayToStr: function (bytes) { + Uint8Array_to_str: function (bytes) { bytes = new Uint8Array(bytes); const result = []; const bs = 1 << 14; @@ -280,7 +331,7 @@ export default { * @param {String|ReadableStream} str The string to convert * @returns {Uint8Array|ReadableStream} A valid squence of utf8 bytes */ - encodeUtf8: function (str) { + encode_utf8: function (str) { const encoder = new TextEncoder('utf-8'); // eslint-disable-next-line no-inner-declarations function process(value, lastChunk = false) { @@ -294,7 +345,7 @@ export default { * @param {Uint8Array|ReadableStream} utf8 A valid squence of utf8 bytes * @returns {String|ReadableStream} A native javascript string */ - decodeUtf8: function (utf8) { + decode_utf8: function (utf8) { const decoder = new TextDecoder('utf-8'); // eslint-disable-next-line no-inner-declarations function process(value, lastChunk = false) { @@ -320,8 +371,8 @@ export default { /** * Check Uint8Array equality - * @param {Uint8Array} array1 first array - * @param {Uint8Array} array2 second array + * @param {Uint8Array} first array + * @param {Uint8Array} second array * @returns {Boolean} equality */ equalsUint8Array: function (array1, array2) { @@ -347,7 +398,7 @@ export default { * @param {Uint8Array} Uint8Array to create a sum of * @returns {Uint8Array} 2 bytes containing the sum of all charcodes % 65535 */ - writeChecksum: function (text) { + write_checksum: function (text) { let s = 0; for (let i = 0; i < text.length; i++) { s = (s + text[i]) & 0xFFFF; @@ -361,7 +412,7 @@ export default { * @link module:config/config.debug is set to true. * @param {String} str String of the debug message */ - printDebug: function (str) { + print_debug: function (str) { if (config.debug) { console.log(str); } @@ -371,12 +422,12 @@ export default { * Helper function to print a debug message. Debug * messages are only printed if * @link module:config/config.debug is set to true. - * Different than print_debug because will call Uint8ArrayToHex iff necessary. + * Different than print_debug because will call Uint8Array_to_hex iff necessary. * @param {String} str String of the debug message */ - printDebugHexArrayDump: function (str, arrToHex) { + print_debug_hexarray_dump: function (str, arrToHex) { if (config.debug) { - str += ': ' + util.uint8ArrayToHex(arrToHex); + str += ': ' + util.Uint8Array_to_hex(arrToHex); console.log(str); } }, @@ -385,12 +436,12 @@ export default { * Helper function to print a debug message. Debug * messages are only printed if * @link module:config/config.debug is set to true. - * Different than print_debug because will call strToHex iff necessary. + * Different than print_debug because will call str_to_hex iff necessary. * @param {String} str String of the debug message */ - printDebugHexStrDump: function (str, strToHex) { + print_debug_hexstr_dump: function (str, strToHex) { if (config.debug) { - str += util.strToHex(strToHex); + str += util.str_to_hex(strToHex); console.log(str); } }, @@ -401,7 +452,7 @@ export default { * @link module:config/config.debug is set to true. * @param {String} str String of the debug message */ - printDebugError: function (error) { + print_debug_error: function (error) { if (config.debug) { console.error(error); } @@ -413,7 +464,7 @@ export default { * @param {ReadableStream|Uint8array|String} input Stream to print * @param {Function} concat Function to concatenate chunks of the stream (defaults to util.concat). */ - printEntireStream: function (str, input, concat) { + print_entire_stream: function (str, input, concat) { stream.readToEnd(stream.clone(input), concat).then(result => { console.log(str + ': ', result); }); @@ -491,35 +542,35 @@ export default { /** * 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.useNative + * be deactivated with config.use_native * @returns {Object} The SubtleCrypto api or 'undefined' */ getWebCrypto: function() { - if (!config.useNative) { + if (!config.use_native) { return; } - return typeof globalThis !== 'undefined' && globalThis.crypto && globalThis.crypto.subtle; + return typeof global !== 'undefined' && global.crypto && global.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.useNative + * with config.use_native * @returns {Object} The SubtleCrypto api or 'undefined' */ getWebCryptoAll: function() { - if (!config.useNative) { + if (!config.use_native) { return; } - if (typeof globalThis !== 'undefined') { - if (globalThis.crypto) { - return globalThis.crypto.subtle || globalThis.crypto.webkitSubtle; + if (typeof global !== 'undefined') { + if (global.crypto) { + return global.crypto.subtle || global.crypto.webkitSubtle; } - if (globalThis.msCrypto) { - return globalThis.msCrypto.subtle; + if (global.msCrypto) { + return global.msCrypto.subtle; } } }, @@ -528,43 +579,45 @@ export default { * Detect Node.js runtime. */ detectNode: function() { - return typeof globalThis.process === 'object' && - typeof globalThis.process.versions === 'object'; + return typeof global.process === 'object' && + typeof global.process.versions === 'object'; }, /** - * Detect native BigInt support + * Get native Node.js module + * @param {String} The module to require + * @returns {Object} The required module or 'undefined' */ - detectBigInt: () => typeof BigInt !== 'undefined', + nodeRequire: function(module) { + if (!util.detectNode()) { + return; + } - /** - * Get BigInteger class - * It wraps the native BigInt type if it's available - * Otherwise it relies on bn.js - * @returns {BigInteger} - * @async - */ - getBigInteger, + // Requiring the module dynamically allows us to access the native node module. + // otherwise, it gets replaced with the browserified version + // eslint-disable-next-line import/no-dynamic-require + return require(module); + }, /** * 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 * @returns {Object} The crypto module or 'undefined' */ getNodeCrypto: function() { - if (!config.useNative) { + if (!config.use_native) { return; } - return require('crypto'); + return util.nodeRequire('crypto'); }, getNodeZlib: function() { - if (!config.useNative) { + if (!config.use_native) { return; } - return require('zlib'); + return util.nodeRequire('zlib'); }, /** @@ -573,16 +626,16 @@ export default { * @returns {Function} The Buffer constructor or 'undefined' */ getNodeBuffer: function() { - return (require('buffer') || {}).Buffer; + return (util.nodeRequire('buffer') || {}).Buffer; }, getNodeStream: function() { - return (require('stream') || {}).Readable; + return (util.nodeRequire('stream') || {}).Readable; }, getHardwareConcurrency: function() { if (util.detectNode()) { - const os = require('os'); + const os = util.nodeRequire('os'); return os.cpus().length; } @@ -597,6 +650,44 @@ export default { return re.test(data); }, + /** + * Format user id for internal use. + */ + formatUserId: function(id) { + // name, email address and comment can be empty but must be of the correct type + if ((id.name && !util.isString(id.name)) || + (id.email && !util.isEmailAddress(id.email)) || + (id.comment && !util.isString(id.comment))) { + throw new Error('Invalid user id format'); + } + const components = []; + if (id.name) { + components.push(id.name); + } + if (id.comment) { + components.push(`(${id.comment})`); + } + if (id.email) { + components.push(`<${id.email}>`); + } + return components.join(' '); + }, + + /** + * Parse user id. + */ + parseUserId: function(userid) { + if (userid.length > config.max_userid_length) { + throw new Error('User id string is too long'); + } + try { + const { name, address: email, comments } = emailAddresses.parseOneAddress({ input: userid, atInDisplayName: true }); + return { name, email, comment: comments.replace(/^\(|\)$/g, '') }; + } catch (e) { + throw new Error('Invalid user id format'); + } + }, + /** * Normalize line endings to * Support any encoding where CR=0x0D, LF=0x0A diff --git a/src/wkd.js b/src/wkd.js index 1a672862..9a161ebd 100644 --- a/src/wkd.js +++ b/src/wkd.js @@ -24,62 +24,61 @@ import util from './util'; import crypto from './crypto'; -import { readKeys } from './key'; +import * as keyMod from './key'; -class WKD { - /** - * Initialize the WKD client - */ - constructor() { - this._fetch = typeof globalThis.fetch === 'function' ? globalThis.fetch : require('node-fetch'); - } +/** + * Initialize the WKD client + * @constructor + */ +function WKD() { + this._fetch = typeof global.fetch === 'function' ? global.fetch : require('node-fetch'); +} - /** - * Search for a public key using Web Key Directory protocol. - * @param {String} options.email User's email. - * @param {Boolean} options.rawBytes Returns Uint8Array instead of parsed key. - * @returns {Promise, - * err: (Array|null)}>} The public key. - * @async - */ - async lookup(options) { - const fetch = this._fetch; +/** + * Search for a public key using Web Key Directory protocol. + * @param {String} options.email User's email. + * @param {Boolean} options.rawBytes Returns Uint8Array instead of parsed key. + * @returns {Promise, + * err: (Array|null)}>} The public key. + * @async + */ +WKD.prototype.lookup = async function(options) { + const fetch = this._fetch; - if (!options.email) { - throw new Error('You must provide an email parameter!'); - } + if (!options.email) { + throw new Error('You must provide an email parameter!'); + } - if (!util.isEmailAddress(options.email)) { - throw new Error('Invalid e-mail address.'); - } + if (!util.isEmailAddress(options.email)) { + throw new Error('Invalid e-mail address.'); + } - const [, localPart, domain] = /(.*)@(.*)/.exec(options.email); - const localEncoded = util.encodeZBase32(await crypto.hash.sha1(util.strToUint8Array(localPart.toLowerCase()))); + const [, localPart, domain] = /(.*)@(.*)/.exec(options.email); + const localEncoded = util.encodeZBase32(await crypto.hash.sha1(util.str_to_Uint8Array(localPart.toLowerCase()))); - const urlAdvanced = `https://openpgpkey.${domain}/.well-known/openpgpkey/${domain}/hu/${localEncoded}`; - const urlDirect = `https://${domain}/.well-known/openpgpkey/hu/${localEncoded}`; + const urlAdvanced = `https://openpgpkey.${domain}/.well-known/openpgpkey/${domain}/hu/${localEncoded}`; + const urlDirect = `https://${domain}/.well-known/openpgpkey/hu/${localEncoded}`; - let response; - try { - response = await fetch(urlAdvanced); - if (response.status !== 200) { - throw new Error('Advanced WKD lookup failed: ' + response.statusText); - } - } catch (err) { - util.printDebugError(err); - response = await fetch(urlDirect); - if (response.status !== 200) { - throw new Error('Direct WKD lookup failed: ' + response.statusText); - } + let response; + try { + response = await fetch(urlAdvanced); + if (response.status !== 200) { + throw new Error('Advanced WKD lookup failed: ' + response.statusText); } - - const rawBytes = new Uint8Array(await response.arrayBuffer()); - if (options.rawBytes) { - return rawBytes; + } catch (err) { + util.print_debug_error(err); + response = await fetch(urlDirect); + if (response.status !== 200) { + throw new Error('Direct WKD lookup failed: ' + response.statusText); } - return readKeys(rawBytes); } -} + + const rawBytes = new Uint8Array(await response.arrayBuffer()); + if (options.rawBytes) { + return rawBytes; + } + return keyMod.read(rawBytes); +}; export default WKD; diff --git a/src/worker/async_proxy.js b/src/worker/async_proxy.js new file mode 100644 index 00000000..059f794b --- /dev/null +++ b/src/worker/async_proxy.js @@ -0,0 +1,190 @@ +// GPG4Browsers - An OpenPGP implementation in javascript +// Copyright (C) 2011 Recurity Labs GmbH +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 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 Provides functions for maintaining browser workers + * @see module:openpgp.initWorker + * @see module:openpgp.getWorker + * @see module:openpgp.destroyWorker + * @see module:worker/worker + * @requires util + * @requires config + * @requires crypto + * @requires packet + * @module worker/async_proxy + */ + +import util from '../util.js'; +import config from '../config'; +import crypto from '../crypto'; +import packet from '../packet'; + + +/** + * Initializes a new proxy and loads the web worker + * @param {String} path The path to the worker or 'openpgp.worker.js' by default + * @param {Number} n number of workers to initialize if path given + * @param {Object} config config The worker configuration + * @param {Array} worker alternative to path parameter: web worker initialized with 'openpgp.worker.js' + * @constructor + */ +function AsyncProxy({ path = 'openpgp.worker.js', n = 1, workers = [], config } = {}) { + /** + * Message handling + */ + const handleMessage = workerId => event => { + const msg = event.data; + switch (msg.event) { + case 'loaded': + this.workers[workerId].loadedResolve(true); + break; + case 'method-return': + if (msg.err) { + // fail + const err = new Error(msg.err); + // add worker stack + err.workerStack = msg.stack; + this.tasks[msg.id].reject(err); + } else { + // success + this.tasks[msg.id].resolve(msg.data); + } + delete this.tasks[msg.id]; + this.workers[workerId].requests--; + break; + case 'request-seed': + this.seedRandom(workerId, msg.amount); + break; + default: + throw new Error('Unknown Worker Event.'); + } + }; + + if (workers.length) { + this.workers = workers; + } + else { + this.workers = []; + while (this.workers.length < n) { + this.workers.push(new Worker(path)); + } + } + + let workerId = 0; + this.workers.forEach(worker => { + worker.loadedPromise = new Promise(resolve => { + worker.loadedResolve = resolve; + }); + worker.requests = 0; + worker.onmessage = handleMessage(workerId++); + worker.onerror = e => { + worker.loadedResolve(false); + // eslint-disable-next-line no-console + console.error('Unhandled error in openpgp worker: ' + e.message + ' (' + e.filename + ':' + e.lineno + ')'); + return false; + }; + + if (config) { + worker.postMessage({ event:'configure', config }); + } + }); + + // Cannot rely on task order being maintained, use object keyed by request ID to track tasks + this.tasks = {}; + this.currentID = 0; +} + +/** + * Returns a promise that resolves when all workers have finished loading + * @returns {Promise} Resolves to true if all workers have loaded succesfully; false otherwise +*/ +AsyncProxy.prototype.loaded = async function() { + const loaded = await Promise.all(this.workers.map(worker => worker.loadedPromise)); + return loaded.every(Boolean); +}; + +/** + * Get new request ID + * @returns {integer} New unique request ID +*/ +AsyncProxy.prototype.getID = function() { + return this.currentID++; +}; + +/** + * Send message to worker with random data + * @param {Integer} size Number of bytes to send + * @async + */ +AsyncProxy.prototype.seedRandom = async function(workerId, size) { + const buf = await crypto.random.getRandomBytes(size); + this.workers[workerId].postMessage({ event:'seed-random', buf }, util.getTransferables(buf, true)); +}; + +/** + * Clear key caches + * @async + */ +AsyncProxy.prototype.clearKeyCache = async function() { + await Promise.all(this.workers.map(worker => new Promise((resolve, reject) => { + const id = this.getID(); + + worker.postMessage({ id, event: 'clear-key-cache' }); + + this.tasks[id] = { resolve, reject }; + }))); +}; + +/** + * Terminates the workers + */ +AsyncProxy.prototype.terminate = function() { + this.workers.forEach(worker => { + worker.terminate(); + }); +}; + +/** + * Generic proxy function that handles all commands from the public api. + * @param {String} method the public api function to be delegated to the worker thread + * @param {Object} options the api function's options + * @returns {Promise} see the corresponding public api functions for their return types + * @async + */ +AsyncProxy.prototype.delegate = function(method, options) { + + const id = this.getID(); + const requests = this.workers.map(worker => worker.requests); + const minRequests = Math.min(...requests); + let workerId = 0; + for (; workerId < this.workers.length; workerId++) { + if (this.workers[workerId].requests === minRequests) { + break; + } + } + + return new Promise((resolve, reject) => { + // clone packets (for web worker structured cloning algorithm) + this.workers[workerId].postMessage({ id:id, event:method, options:packet.clone.clonePackets(options) }, util.getTransferables(options, config.zero_copy)); + this.workers[workerId].requests++; + + // remember to handle parsing cloned packets from worker + this.tasks[id] = { resolve: data => resolve(packet.clone.parseClonedPackets(util.restoreStreams(data), method)), reject }; + }); +}; + +export default AsyncProxy; diff --git a/src/worker/worker.js b/src/worker/worker.js new file mode 100644 index 00000000..f1a576e0 --- /dev/null +++ b/src/worker/worker.js @@ -0,0 +1,167 @@ +// GPG4Browsers - An OpenPGP implementation in javascript +// Copyright (C) 2011 Recurity Labs GmbH +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 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 + +/* eslint-disable no-restricted-globals */ +/* eslint-disable no-var */ +/* eslint-disable vars-on-top */ + +/** + * @fileoverview Provides functions for communicating with workers + * @see module:openpgp.initWorker + * @see module:openpgp.getWorker + * @see module:openpgp.destroyWorker + * @see module:worker/async_proxy + * @module worker/worker + */ + +importScripts('openpgp.js'); +var openpgp = global.openpgp; + +var randomQueue = []; +var MAX_SIZE_RANDOM_BUFFER = 60000; + +/** + * Handle random buffer exhaustion by requesting more random bytes from the main window + * @returns {Promise} Empty promise whose resolution indicates that the buffer has been refilled + */ +function randomCallback() { + + if (!randomQueue.length) { + self.postMessage({ event: 'request-seed', amount: MAX_SIZE_RANDOM_BUFFER }); + } + + return new Promise(function(resolve) { + randomQueue.push(resolve); + }); +} + +openpgp.crypto.random.randomBuffer.init(MAX_SIZE_RANDOM_BUFFER, randomCallback); + +/** + * Handle messages from the main window. + * @param {Object} event Contains event type and data + */ +self.onmessage = function(event) { + var msg = event.data || {}; + + switch (msg.event) { + case 'configure': + configure(msg.config); + break; + + case 'seed-random': + seedRandom(msg.buf); + + var queueCopy = randomQueue; + randomQueue = []; + for (var i = 0; i < queueCopy.length; i++) { + queueCopy[i](); + } + + break; + + default: + delegate(msg.id, msg.event, msg.options || {}); + } +}; + +/** + * Set config from main context to worker context. + * @param {Object} config The openpgp configuration + */ +function configure(config) { + Object.keys(config).forEach(function(key) { + openpgp.config[key] = config[key]; + }); +} + +/** + * Seed the library with entropy gathered global.crypto.getRandomValues + * as this api is only avalible in the main window. + * @param {ArrayBuffer} buffer Some random bytes + */ +function seedRandom(buffer) { + if (!(buffer instanceof Uint8Array)) { + buffer = new Uint8Array(buffer); + } + openpgp.crypto.random.randomBuffer.set(buffer); +} + +const keyCache = new Map(); +function getCachedKey(key) { + const armor = key.armor(); + if (keyCache.has(armor)) { + return keyCache.get(armor); + } + keyCache.set(armor, key); + return key; +} + +/** + * Generic proxy function that handles all commands from the public api. + * @param {String} method The public api function to be delegated to the worker thread + * @param {Object} options The api function's options + */ +function delegate(id, method, options) { + if (method === 'clear-key-cache') { + Array.from(keyCache.values()).forEach(key => { + if (key.isPrivate()) { + key.clearPrivateParams(); + } + }); + keyCache.clear(); + response({ id, event: 'method-return' }); + return; + } + if (typeof openpgp[method] !== 'function') { + response({ id:id, event:'method-return', err:'Unknown Worker Event' }); + return; + } + // construct ReadableStreams from MessagePorts + openpgp.util.restoreStreams(options); + // parse cloned packets + options = openpgp.packet.clone.parseClonedPackets(options, method); + // cache keys by armor, so that we don't have to repeatedly verify self-signatures + if (options.publicKeys) { + options.publicKeys = options.publicKeys.map(getCachedKey); + } + if (options.privateKeys) { + options.privateKeys = options.privateKeys.map(getCachedKey); + } + openpgp[method](options).then(function(data) { + // clone packets (for web worker structured cloning algorithm) + response({ id:id, event:'method-return', data:openpgp.packet.clone.clonePackets(data) }); + }).catch(function(e) { + openpgp.util.print_debug_error(e); + response({ + id:id, event:'method-return', err:e.message, stack:e.stack + }); + }); +} + +/** + * Respond to the main window. + * @param {Object} event Contains event type and data + */ +function response(event) { + self.postMessage(event, openpgp.util.getTransferables(event.data, openpgp.config.zero_copy)); +} + +/** + * Let the main window know the worker has loaded. + */ +postMessage({ event: 'loaded' }); diff --git a/test/crypto/aes_kw.js b/test/crypto/aes_kw.js index dcf7f689..4e048dcf 100644 --- a/test/crypto/aes_kw.js +++ b/test/crypto/aes_kw.js @@ -1,9 +1,8 @@ -const aes_kw = require('../../src/crypto/aes_kw'); -const util = require('../../src/util'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); const expect = require('chai').expect; -module.exports = () => describe('AES Key Wrap and Unwrap', function () { +describe('AES Key Wrap and Unwrap', function () { const test_vectors = [ [ "128 bits of Key Data with a 128-bit KEK", @@ -45,13 +44,13 @@ module.exports = () => describe('AES Key Wrap and Unwrap', function () { test_vectors.forEach(function(test) { it(test[0], function(done) { - const kek = util.hexToUint8Array(test[1]); + const kek = openpgp.util.hex_to_Uint8Array(test[1]); const input = test[2].replace(/\s/g, ""); - const input_bin = util.hexToStr(input); + const input_bin = openpgp.util.hex_to_str(input); const output = test[3].replace(/\s/g, ""); - const output_bin = util.hexToStr(output); - expect(util.uint8ArrayToHex(aes_kw.wrap(kek, input_bin)).toUpperCase()).to.equal(output); - expect(util.uint8ArrayToHex(aes_kw.unwrap(kek, output_bin)).toUpperCase()).to.equal(input); + const output_bin = openpgp.util.hex_to_str(output); + expect(openpgp.util.Uint8Array_to_hex(openpgp.crypto.aes_kw.wrap(kek, input_bin)).toUpperCase()).to.equal(output); + expect(openpgp.util.Uint8Array_to_hex(openpgp.crypto.aes_kw.unwrap(kek, output_bin)).toUpperCase()).to.equal(input); done(); }); }); diff --git a/test/crypto/cipher/aes.js b/test/crypto/cipher/aes.js index b8ebb5fb..5c6b1f53 100644 --- a/test/crypto/cipher/aes.js +++ b/test/crypto/cipher/aes.js @@ -1,12 +1,12 @@ -const { aes128: AES128 } = require('../../../src/crypto/cipher'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../../dist/openpgp'); const chai = require('chai'); const { expect } = chai; -module.exports = () => describe('AES Rijndael cipher test with test vectors from ecb_tbl.txt', function() { +describe('AES Rijndael cipher test with test vectors from ecb_tbl.txt', function() { function test_aes(input, key, output) { - const aes = new AES128(new Uint8Array(key)); + const aes = new openpgp.crypto.cipher.aes128(new Uint8Array(key)); const encrypted = aes.encrypt(new Uint8Array(input)); expect(encrypted).to.deep.equal(new Uint8Array(output)); diff --git a/test/crypto/cipher/blowfish.js b/test/crypto/cipher/blowfish.js index 1d2dc3b0..db4bc300 100644 --- a/test/crypto/cipher/blowfish.js +++ b/test/crypto/cipher/blowfish.js @@ -1,16 +1,16 @@ -const BF = require('../../../src/crypto/cipher/blowfish'); -const util = require('../../../src/util'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../../dist/openpgp'); const chai = require('chai'); +const { util } = openpgp; const { expect } = chai; -module.exports = () => it('Blowfish cipher test with test vectors from https://www.schneier.com/code/vectors.txt', function(done) { +it('Blowfish cipher test with test vectors from https://www.schneier.com/code/vectors.txt', function(done) { function test_bf(input, key, output) { - const blowfish = new BF(util.uint8ArrayToStr(key)); - const result = util.uint8ArrayToStr(blowfish.encrypt(input)); + const blowfish = new openpgp.crypto.cipher.blowfish(util.Uint8Array_to_str(key)); + const result = util.Uint8Array_to_str(blowfish.encrypt(input)); - return (util.strToHex(result) === util.strToHex(util.uint8ArrayToStr(output))); + return (util.str_to_hex(result) === util.str_to_hex(util.Uint8Array_to_str(output))); } const testvectors = [[[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],[0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],[0x4E,0xF9,0x97,0x45,0x61,0x98,0xDD,0x78]], @@ -50,9 +50,9 @@ module.exports = () => it('Blowfish cipher test with test vectors from https://w for (let i = 0; i < testvectors.length; i++) { const res = test_bf(testvectors[i][1],testvectors[i][0],testvectors[i][2]); - expect(res, 'vector ' + i + '" with block ' + util.uint8ArrayToHex(testvectors[i][0]) + - ' and key ' + util.uint8ArrayToHex(testvectors[i][1]) + - ' should be ' + util.uint8ArrayToHex(testvectors[i][2]), false); + expect(res, 'vector ' + i + '" with block ' + util.Uint8Array_to_hex(testvectors[i][0]) + + ' and key ' + util.Uint8Array_to_hex(testvectors[i][1]) + + ' should be ' + util.Uint8Array_to_hex(testvectors[i][2]), false); } done(); }); diff --git a/test/crypto/cipher/cast5.js b/test/crypto/cipher/cast5.js index 513af642..37437903 100644 --- a/test/crypto/cipher/cast5.js +++ b/test/crypto/cipher/cast5.js @@ -1,25 +1,25 @@ -const Cast5 = require('../../../src/crypto/cipher/cast5'); -const util = require('../../../src/util'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../../dist/openpgp'); const chai = require('chai'); +const { util } = openpgp; const { expect } = chai; -module.exports = () => it('CAST-128 cipher test with test vectors from RFC2144', function (done) { +it('CAST-128 cipher test with test vectors from RFC2144', function (done) { function test_cast(input, key, output) { - const cast5 = new Cast5(key); - const result = util.uint8ArrayToStr(cast5.encrypt(input)); + const cast5 = new openpgp.crypto.cipher.cast5(key); + const result = util.Uint8Array_to_str(cast5.encrypt(input)); - return util.strToHex(result) === util.strToHex(util.uint8ArrayToStr(output)); + return util.str_to_hex(result) === util.str_to_hex(util.Uint8Array_to_str(output)); } const testvectors = [[[0x01,0x23,0x45,0x67,0x12,0x34,0x56,0x78,0x23,0x45,0x67,0x89,0x34,0x56,0x78,0x9A],[0x01,0x23,0x45,0x67,0x89,0xAB,0xCD,0xEF],[0x23,0x8B,0x4F,0xE5,0x84,0x7E,0x44,0xB2]]]; for (let i = 0; i < testvectors.length; i++) { const res = test_cast(testvectors[i][1],testvectors[i][0],testvectors[i][2]); - expect(res, 'vector with block ' + util.uint8ArrayToHex(testvectors[i][0]) + - ' and key ' + util.uint8ArrayToHex(testvectors[i][1]) + - ' should be ' + util.uint8ArrayToHex(testvectors[i][2])).to.be.true; + expect(res, 'vector with block ' + util.Uint8Array_to_hex(testvectors[i][0]) + + ' and key ' + util.Uint8Array_to_hex(testvectors[i][1]) + + ' should be ' + util.Uint8Array_to_hex(testvectors[i][2])).to.be.true; } done(); }); diff --git a/test/crypto/cipher/des.js b/test/crypto/cipher/des.js index 9bcbb20f..512b7df9 100644 --- a/test/crypto/cipher/des.js +++ b/test/crypto/cipher/des.js @@ -1,11 +1,11 @@ -const { DES, TripleDES } = require('../../../src/crypto/cipher/des'); -const util = require('../../../src/util'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../../dist/openpgp'); const chai = require('chai'); +const { util } = openpgp; const { expect } = chai; -module.exports = () => describe('TripleDES (EDE) cipher test with test vectors from NIST SP 800-20', function() { +describe('TripleDES (EDE) cipher test with test vectors from NIST SP 800-20', function() { // see https://csrc.nist.gov/publications/nistpubs/800-20/800-20.pdf const key = new Uint8Array([1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]); const testvectors = [[[0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00],[0x95,0xF8,0xA5,0xE5,0xDD,0x31,0xD9,0x00]], @@ -75,14 +75,14 @@ module.exports = () => describe('TripleDES (EDE) cipher test with test vectors f it('3DES EDE test vectors', function (done) { for (let i = 0; i < testvectors.length; i++) { - const des = new TripleDES(key); + const des = new openpgp.crypto.cipher.tripledes(key); - const encr = util.uint8ArrayToStr(des.encrypt(testvectors[i][0], key)); + const encr = util.Uint8Array_to_str(des.encrypt(testvectors[i][0], key)); - expect(encr, 'vector with block ' + util.uint8ArrayToHex(testvectors[i][0]) + - ' and key ' + util.strToHex(util.uint8ArrayToStr(key)) + - ' should be ' + util.uint8ArrayToHex(testvectors[i][1]) + - ' != ' + util.uint8ArrayToHex(encr)).to.be.equal(util.uint8ArrayToStr(testvectors[i][1])); + expect(encr, 'vector with block ' + util.Uint8Array_to_hex(testvectors[i][0]) + + ' and key ' + util.str_to_hex(util.Uint8Array_to_str(key)) + + ' should be ' + util.Uint8Array_to_hex(testvectors[i][1]) + + ' != ' + util.Uint8Array_to_hex(encr)).to.be.equal(util.Uint8Array_to_str(testvectors[i][1])); } done(); }); @@ -115,7 +115,7 @@ module.exports = () => describe('TripleDES (EDE) cipher test with test vectors f [[0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D], [0xCA, 0x59, 0x61, 0x3A, 0x83, 0x23, 0x26, 0xDD]], [[0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F], [0x83, 0x25, 0x79, 0x06, 0x54, 0xA4, 0x44, 0xD9]]]; - const des = new DES(key); + const des = new openpgp.crypto.cipher.des(key); for (let padding = 0; padding < 3; padding++) { const thisVectorSet = testvectors[padding]; @@ -124,18 +124,18 @@ module.exports = () => describe('TripleDES (EDE) cipher test with test vectors f const encrypted = des.encrypt(thisVectorSet[i][0], padding); const decrypted = des.decrypt(encrypted, padding); - expect(util.uint8ArrayToStr(encrypted), 'vector with block [' + util.uint8ArrayToHex(thisVectorSet[i][0]) + - '] and key [' + util.strToHex(util.uint8ArrayToStr(key)) + + expect(util.Uint8Array_to_str(encrypted), 'vector with block [' + util.Uint8Array_to_hex(thisVectorSet[i][0]) + + '] and key [' + util.str_to_hex(util.Uint8Array_to_str(key)) + '] and padding [' + padding + - '] should be ' + util.uint8ArrayToHex(thisVectorSet[i][1]) + - ' - Actually [' + util.uint8ArrayToHex(encrypted) + - ']').to.equal(util.uint8ArrayToStr(thisVectorSet[i][1])); - expect(util.uint8ArrayToStr(decrypted), 'vector with block [' + util.uint8ArrayToHex(thisVectorSet[i][0]) + - '] and key [' + util.strToHex(util.uint8ArrayToStr(key)) + + '] should be ' + util.Uint8Array_to_hex(thisVectorSet[i][1]) + + ' - Actually [' + util.Uint8Array_to_hex(encrypted) + + ']').to.equal(util.Uint8Array_to_str(thisVectorSet[i][1])); + expect(util.Uint8Array_to_str(decrypted), 'vector with block [' + util.Uint8Array_to_hex(thisVectorSet[i][0]) + + '] and key [' + util.str_to_hex(util.Uint8Array_to_str(key)) + '] and padding [' + padding + - '] should be ' + util.uint8ArrayToHex(thisVectorSet[i][0]) + - ' - Actually [' + util.uint8ArrayToHex(decrypted) + - ']').to.equal(util.uint8ArrayToStr(thisVectorSet[i][0])); + '] should be ' + util.Uint8Array_to_hex(thisVectorSet[i][0]) + + ' - Actually [' + util.Uint8Array_to_hex(decrypted) + + ']').to.equal(util.Uint8Array_to_str(thisVectorSet[i][0])); } } done(); diff --git a/test/crypto/cipher/index.js b/test/crypto/cipher/index.js index fbd22ace..6737cca1 100644 --- a/test/crypto/cipher/index.js +++ b/test/crypto/cipher/index.js @@ -1,7 +1,7 @@ -module.exports = () => describe('Cipher', function () { - require('./aes.js')(); - require('./blowfish.js')(); - require('./cast5.js')(); - require('./des.js')(); - require('./twofish.js')(); +describe('Cipher', function () { + require('./aes.js'); + require('./blowfish.js'); + require('./cast5.js'); + require('./des.js'); + require('./twofish.js'); }); diff --git a/test/crypto/cipher/twofish.js b/test/crypto/cipher/twofish.js index 838c37f4..f2b4ce8c 100644 --- a/test/crypto/cipher/twofish.js +++ b/test/crypto/cipher/twofish.js @@ -1,13 +1,13 @@ -const TF = require('../../../src/crypto/cipher/twofish'); -const util = require('../../../src/util'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../../dist/openpgp'); const chai = require('chai'); +const { util } = openpgp; const { expect } = chai; -module.exports = () => it('Twofish with test vectors from https://www.schneier.com/code/ecb_ival.txt', function(done) { +it('Twofish with test vectors from https://www.schneier.com/code/ecb_ival.txt', function(done) { function tfencrypt(block, key) { - const tf = new TF(util.strToUint8Array(key)); + const tf = new openpgp.crypto.cipher.twofish(util.str_to_Uint8Array(key)); return tf.encrypt(block); } @@ -36,36 +36,36 @@ module.exports = () => it('Twofish with test vectors from https://www.schneier.c if (i === 0) { blk = start_short; - key = util.uint8ArrayToStr(start); + key = util.Uint8Array_to_str(start); ct = testvectors[0]; - res = util.uint8ArrayToStr(tfencrypt(blk,key)); - exp = util.uint8ArrayToStr(ct); + res = util.Uint8Array_to_str(tfencrypt(blk,key)); + exp = util.Uint8Array_to_str(ct); } else if (i === 1) { blk = testvectors[0]; - key = util.uint8ArrayToStr(start); + key = util.Uint8Array_to_str(start); ct = testvectors[1]; - res = util.uint8ArrayToStr(tfencrypt(blk,key)); - exp = util.uint8ArrayToStr(ct); + res = util.Uint8Array_to_str(tfencrypt(blk,key)); + exp = util.Uint8Array_to_str(ct); } else if (i === 2) { blk = testvectors[i - 1]; - key = util.uint8ArrayToStr(testvectors[i - 2].concat(start_short)); + key = util.Uint8Array_to_str(testvectors[i - 2].concat(start_short)); ct = testvectors[i]; - res = util.uint8ArrayToStr(tfencrypt(blk,key)); - exp = util.uint8ArrayToStr(ct); + res = util.Uint8Array_to_str(tfencrypt(blk,key)); + exp = util.Uint8Array_to_str(ct); } else if (i < 10 || i > 46) { blk = testvectors[i - 1]; - key = util.uint8ArrayToStr(testvectors[i - 2].concat(testvectors[i - 3])); + key = util.Uint8Array_to_str(testvectors[i - 2].concat(testvectors[i - 3])); ct = testvectors[i]; - res = util.uint8ArrayToStr(tfencrypt(blk,key)); - exp = util.uint8ArrayToStr(ct); + res = util.Uint8Array_to_str(tfencrypt(blk,key)); + exp = util.Uint8Array_to_str(ct); } else { - testvectors[i] = tfencrypt(testvectors[i - 1],util.uint8ArrayToStr(testvectors[i - 2].concat(testvectors[i - 3]))); + testvectors[i] = tfencrypt(testvectors[i - 1],util.Uint8Array_to_str(testvectors[i - 2].concat(testvectors[i - 3]))); continue; } - expect(res, 'vector with block ' + util.uint8ArrayToHex(blk) + - ' with key ' + util.strToHex(key) + - ' should be ' + util.uint8ArrayToHex(ct) + - ' but is ' + util.uint8ArrayToHex(tfencrypt(blk,key))).to.equal(exp); + expect(res, 'vector with block ' + util.Uint8Array_to_hex(blk) + + ' with key ' + util.str_to_hex(key) + + ' should be ' + util.Uint8Array_to_hex(ct) + + ' but is ' + util.Uint8Array_to_hex(tfencrypt(blk,key))).to.equal(exp); } done(); }); diff --git a/test/crypto/crypto.js b/test/crypto/crypto.js index 4cc9c674..33a4ba3a 100644 --- a/test/crypto/crypto.js +++ b/test/crypto/crypto.js @@ -1,14 +1,14 @@ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); -const crypto = require('../../src/crypto'); -const util = require('../../src/util'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); const chai = require('chai'); chai.use(require('chai-as-promised')); const expect = chai.expect; -module.exports = () => describe('API functional testing', function() { - const RSAPublicKeyMaterial = util.concatUint8Array([ +describe('API functional testing', function() { + const util = openpgp.util; + const crypto = openpgp.crypto; + const RSApubMPIstrs = [ new Uint8Array([0x08,0x00,0xac,0x15,0xb3,0xd6,0xd2,0x0f,0xf0,0x7a,0xdd,0x21,0xb7, 0xbf,0x61,0xfa,0xca,0x93,0x86,0xc8,0x55,0x5a,0x4b,0xa6,0xa4,0x1a, 0x60,0xa2,0x3a,0x37,0x06,0x08,0xd8,0x15,0x8e,0x85,0x45,0xaa,0xb7, @@ -30,8 +30,8 @@ module.exports = () => describe('API functional testing', function() { 0xee,0xc9,0xa4,0xcd,0x15,0xdc,0x1b,0x8d,0x64,0xc1,0x36,0x17,0xc4, 0x8d,0x5e,0x99,0x7a,0x5b,0x9f,0x39,0xd0,0x00,0x6e,0xf9]), new Uint8Array([0x00,0x11,0x01,0x00,0x01]) - ]); - const RSAPrivateKeyMaterial = util.concatUint8Array([ + ]; + const RSAsecMPIstrs = [ new Uint8Array([0x07,0xfe,0x23,0xff,0xce,0x45,0x6c,0x60,0x65,0x40,0x6e,0xae,0x35, 0x10,0x56,0x60,0xee,0xab,0xfa,0x10,0x42,0xba,0xc7,0x04,0xaf,0x63, 0xcd,0x3f,0x62,0xca,0x4b,0xfa,0xe1,0xa9,0x70,0xcd,0x34,0x8b,0xc8, @@ -82,9 +82,9 @@ module.exports = () => 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]) - ]); + ]; - const DSAPublicKeyMaterial = util.concatUint8Array([ + const 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, 0x44,0xe9,0xc1,0x6b,0x69,0xf7,0x10,0x76,0x49,0xa7,0x25,0x85,0xf4,0x1b,0xed, @@ -142,14 +142,14 @@ module.exports = () => describe('API functional testing', function() { 0x67,0x8d,0x9d,0x14,0xb6,0x9d,0x32,0x82,0xd0,0xb5,0xc6,0x57,0xf0,0x91,0xd9, 0xc3,0x26,0xae,0x9f,0xa9,0x67,0x49,0x96,0x5c,0x07,0x3e,0x47,0x5c,0xed,0x60, 0x07,0xac,0x6a]) - ]); - const DSAPrivateKeyMaterial = util.concatUint8Array([ + ]; + const DSAsecMPIstrs = [ 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]) - ]); + ]; - const elGamalPublicKeyMaterial = util.concatUint8Array([ + const 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, 0x73,0xe6,0x69,0xbc,0x1d,0x33,0xe3,0x2d,0x91,0x23,0x69,0x95,0x98,0xd7,0x18, @@ -187,48 +187,87 @@ module.exports = () => describe('API functional testing', function() { 0xda,0xba,0x19,0xf3,0xcb,0x10,0xa0,0x6b,0xd0,0x2d,0xbe,0x40,0x42,0x7b,0x9b, 0x15,0xa4,0x2d,0xec,0xcf,0x09,0xd6,0xe3,0x92,0xc3,0x8d,0x65,0x6b,0x60,0x97, 0xda,0x6b,0xca]) - ]); + ]; - const elGamalPrivateKeyMaterial = util.concatUint8Array([ + const ElgamalsecMPIstrs = [ new Uint8Array([0x01,0x52,0x02,0x80,0x87,0xf6,0xe4,0x49,0xd7,0x2e,0x3e,0xfe,0x60,0xb9,0xa3, 0x2a,0xf0,0x67,0x58,0xe9,0xf6,0x47,0x83,0xde,0x7e,0xfb,0xbb,0xbd,0xdf,0x48, 0x12,0x1b,0x06,0x7d,0x13,0xbc,0x3b,0x49,0xf9,0x86,0xd4,0x53,0xed,0x2d,0x68]) - ]); + ]; - const algoRSA = openpgp.enums.publicKey.rsaEncryptSign; - const RSAPublicParams = crypto.parsePublicKeyParams(algoRSA, RSAPublicKeyMaterial).publicParams; - const RSAPrivateParams = crypto.parsePrivateKeyParams(algoRSA, RSAPrivateKeyMaterial).privateParams; + const RSApubMPIs = []; + let i; + for (i = 0; i < 2; i++) { + RSApubMPIs[i] = new openpgp.MPI(); + RSApubMPIs[i].read(RSApubMPIstrs[i]); + } - const algoDSA = openpgp.enums.publicKey.dsa; - const DSAPublicParams = crypto.parsePublicKeyParams(algoDSA, DSAPublicKeyMaterial).publicParams; - const DSAPrivateParams = crypto.parsePrivateKeyParams(algoDSA, DSAPrivateKeyMaterial).privateParams; + const RSAsecMPIs = []; + for (i = 0; i < 4; i++) { + RSAsecMPIs[i] = new openpgp.MPI(); + RSAsecMPIs[i].read(RSAsecMPIstrs[i]); + } - const algoElGamal = openpgp.enums.publicKey.elgamal; - const elGamalPublicParams = crypto.parsePublicKeyParams(algoElGamal, elGamalPublicKeyMaterial).publicParams; - const elGamalPrivateParams = crypto.parsePrivateKeyParams(algoElGamal, elGamalPrivateKeyMaterial).privateParams; + const DSAsecMPIs = []; + for (i = 0; i < 1; i++) { + DSAsecMPIs[i] = new openpgp.MPI(); + DSAsecMPIs[i].read(DSAsecMPIstrs[i]); + } - const data = util.strToUint8Array("foobar"); + const DSApubMPIs = []; + for (i = 0; i < 4; i++) { + DSApubMPIs[i] = new openpgp.MPI(); + DSApubMPIs[i].read(DSApubMPIstrs[i]); + } + const ElgamalsecMPIs = []; + for (i = 0; i < 1; i++) { + ElgamalsecMPIs[i] = new openpgp.MPI(); + ElgamalsecMPIs[i].read(ElgamalsecMPIstrs[i]); + } + + const ElgamalpubMPIs = []; + for (i = 0; i < 3; i++) { + ElgamalpubMPIs[i] = new openpgp.MPI(); + ElgamalpubMPIs[i].read(ElgamalpubMPIstrs[i]); + } + + const data = util.str_to_Uint8Array("foobar"); describe('Sign and verify', function () { it('RSA', async function () { - const RSAsignedData = await crypto.signature.sign( - openpgp.enums.publicKey.rsaEncryptSign, openpgp.enums.hash.sha1, RSAPublicParams, RSAPrivateParams, data, await crypto.hash.digest(2, data) - ); - const success = await crypto.signature.verify( - openpgp.enums.publicKey.rsaEncryptSign, openpgp.enums.hash.sha1, RSAsignedData, RSAPublicParams, data, await crypto.hash.digest(2, data) - ); - return expect(success).to.be.true; + // FIXME + //Originally we passed public and secret MPI separately, now they are joined. Is this what we want to do long term? + // RSA + return crypto.signature.sign( + 1, 2, RSApubMPIs.concat(RSAsecMPIs), data, await crypto.hash.digest(2, data) + ).then(async RSAsignedData => { + const RSAsignedDataMPI = new openpgp.MPI(); + RSAsignedDataMPI.read(RSAsignedData); + return crypto.signature.verify( + 1, 2, [RSAsignedDataMPI], RSApubMPIs, data, await crypto.hash.digest(2, data) + ).then(success => { + return expect(success).to.be.true; + }); + }); }); it('DSA', async function () { - const DSAsignedData = await crypto.signature.sign( - openpgp.enums.publicKey.dsa, openpgp.enums.hash.sha1, DSAPublicParams, DSAPrivateParams, data, await crypto.hash.digest(2, data) - ); - const success = await crypto.signature.verify( - openpgp.enums.publicKey.dsa, openpgp.enums.hash.sha1, DSAsignedData, DSAPublicParams, data, await crypto.hash.digest(2, data) - ); - - return expect(success).to.be.true; + // DSA + return crypto.signature.sign( + 17, 2, DSApubMPIs.concat(DSAsecMPIs), data, await crypto.hash.digest(2, data) + ).then(async DSAsignedData => { + DSAsignedData = util.Uint8Array_to_str(DSAsignedData); + const DSAmsgMPIs = []; + DSAmsgMPIs[0] = new openpgp.MPI(); + DSAmsgMPIs[1] = new openpgp.MPI(); + DSAmsgMPIs[0].read(DSAsignedData.substring(0,34)); + DSAmsgMPIs[1].read(DSAsignedData.substring(34,68)); + return crypto.signature.verify( + 17, 2, DSAmsgMPIs, DSApubMPIs, data, await crypto.hash.digest(2, data) + ).then(success => { + return expect(success).to.be.true; + }); + }); }); }); @@ -242,8 +281,8 @@ module.exports = () => describe('API functional testing', function() { await Promise.all(symmAlgos.map(async function(algo) { const symmKey = await crypto.generateSessionKey(algo); const IV = new Uint8Array(crypto.cipher[algo].blockSize); - const symmencData = await crypto.cfb.encrypt(algo, symmKey, util.strToUint8Array(plaintext), IV); - const text = util.uint8ArrayToStr(await crypto.cfb.decrypt(algo, symmKey, symmencData, new Uint8Array(crypto.cipher[algo].blockSize))); + const symmencData = await crypto.cfb.encrypt(algo, symmKey, util.str_to_Uint8Array(plaintext), IV); + const text = util.Uint8Array_to_str(await crypto.cfb.decrypt(algo, symmKey, symmencData, new Uint8Array(crypto.cipher[algo].blockSize))); expect(text).to.equal(plaintext); })); } @@ -256,13 +295,13 @@ module.exports = () => describe('API functional testing', function() { const iv = await crypto.random.getRandomBytes(crypto.gcm.ivLength); let modeInstance = await crypto.gcm(algo, key); - const ciphertext = await modeInstance.encrypt(util.strToUint8Array(plaintext), iv); + const ciphertext = await modeInstance.encrypt(util.str_to_Uint8Array(plaintext), iv); - openpgp.config.useNative = nativeDecrypt; + openpgp.config.use_native = nativeDecrypt; modeInstance = await crypto.gcm(algo, key); - const decrypted = await modeInstance.decrypt(util.strToUint8Array(util.uint8ArrayToStr(ciphertext)), iv); - const decryptedStr = util.uint8ArrayToStr(decrypted); + const decrypted = await modeInstance.decrypt(util.str_to_Uint8Array(util.Uint8Array_to_str(ciphertext)), iv); + const decryptedStr = util.Uint8Array_to_str(decrypted); expect(decryptedStr).to.equal(plaintext); }); } @@ -277,62 +316,66 @@ module.exports = () => describe('API functional testing', function() { }); describe('Symmetric AES-GCM (native)', function() { - let useNativeVal; + let use_nativeVal; beforeEach(function() { - useNativeVal = openpgp.config.useNative; - openpgp.config.useNative = true; + use_nativeVal = openpgp.config.use_native; + openpgp.config.use_native = true; }); afterEach(function() { - openpgp.config.useNative = useNativeVal; + openpgp.config.use_native = use_nativeVal; }); testAESGCM("12345678901234567890123456789012345678901234567890", true); }); describe('Symmetric AES-GCM (asm.js fallback)', function() { - let useNativeVal; + let use_nativeVal; beforeEach(function() { - useNativeVal = openpgp.config.useNative; - openpgp.config.useNative = false; + use_nativeVal = openpgp.config.use_native; + openpgp.config.use_native = false; }); afterEach(function() { - openpgp.config.useNative = useNativeVal; + openpgp.config.use_native = use_nativeVal; }); testAESGCM("12345678901234567890123456789012345678901234567890", false); }); describe('Symmetric AES-GCM (native encrypt, asm.js decrypt)', function() { - let useNativeVal; + let use_nativeVal; beforeEach(function() { - useNativeVal = openpgp.config.useNative; - openpgp.config.useNative = true; + use_nativeVal = openpgp.config.use_native; + openpgp.config.use_native = true; }); afterEach(function() { - openpgp.config.useNative = useNativeVal; + openpgp.config.use_native = use_nativeVal; }); testAESGCM("12345678901234567890123456789012345678901234567890", false); }); - it('Asymmetric using RSA with eme_pkcs1 padding', async function () { - const symmKey = await crypto.generateSessionKey('aes256'); - return crypto.publicKeyEncrypt(algoRSA, RSAPublicParams, symmKey).then(RSAEncryptedData => { + it('Asymmetric using RSA with eme_pkcs1 padding', function () { + const symmKey = util.Uint8Array_to_str(crypto.generateSessionKey('aes256')); + crypto.publicKeyEncrypt(1, RSApubMPIs, symmKey).then(RSAEncryptedData => { return crypto.publicKeyDecrypt( - algoRSA, RSAPublicParams, RSAPrivateParams, RSAEncryptedData + 1, RSApubMPIs.concat(RSAsecMPIs), RSAEncryptedData ).then(data => { - expect(data).to.deep.equal(symmKey); + data = new openpgp.MPI(data).write(); + data = util.Uint8Array_to_str(data.subarray(2, data.length)); + expect(data).to.equal(symmKey); }); }); }); - it('Asymmetric using Elgamal with eme_pkcs1 padding', async function () { - const symmKey = await crypto.generateSessionKey('aes256'); - return crypto.publicKeyEncrypt(algoElGamal, elGamalPublicParams, symmKey).then(ElgamalEncryptedData => { + it('Asymmetric using Elgamal with eme_pkcs1 padding', function () { + const symmKey = util.Uint8Array_to_str(crypto.generateSessionKey('aes256')); + crypto.publicKeyEncrypt(16, ElgamalpubMPIs, symmKey).then(ElgamalEncryptedData => { return crypto.publicKeyDecrypt( - algoElGamal, elGamalPublicParams, elGamalPrivateParams, ElgamalEncryptedData + 16, ElgamalpubMPIs.concat(ElgamalsecMPIs), ElgamalEncryptedData ).then(data => { - expect(data).to.deep.equal(symmKey); + data = new openpgp.MPI(data).write(); + data = util.Uint8Array_to_str(data.subarray(2, data.length)); + expect(data).to.equal(symmKey); }); }); }); diff --git a/test/crypto/eax.js b/test/crypto/eax.js index 9094000a..46ac3000 100644 --- a/test/crypto/eax.js +++ b/test/crypto/eax.js @@ -2,9 +2,7 @@ // Adapted from https://github.com/artjomb/cryptojs-extension/blob/8c61d159/test/eax.js -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); -const EAX = require('../../src/crypto/eax'); -const util = require('../../src/util'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); const chai = require('chai'); chai.use(require('chai-as-promised')); @@ -90,21 +88,21 @@ function testAESEAX() { const cipher = 'aes128'; await Promise.all(vectors.map(async vec => { - const keyBytes = util.hexToUint8Array(vec.key); - const msgBytes = util.hexToUint8Array(vec.msg); - const nonceBytes = util.hexToUint8Array(vec.nonce); - const headerBytes = util.hexToUint8Array(vec.header); - const ctBytes = util.hexToUint8Array(vec.ct); + const keyBytes = openpgp.util.hex_to_Uint8Array(vec.key); + const msgBytes = openpgp.util.hex_to_Uint8Array(vec.msg); + const nonceBytes = openpgp.util.hex_to_Uint8Array(vec.nonce); + const headerBytes = openpgp.util.hex_to_Uint8Array(vec.header); + const ctBytes = openpgp.util.hex_to_Uint8Array(vec.ct); - const eax = await EAX(cipher, keyBytes); + const eax = await openpgp.crypto.eax(cipher, keyBytes); // encryption test let ct = await eax.encrypt(msgBytes, nonceBytes, headerBytes); - expect(util.uint8ArrayToHex(ct)).to.equal(vec.ct.toLowerCase()); + expect(openpgp.util.Uint8Array_to_hex(ct)).to.equal(vec.ct.toLowerCase()); // decryption test with verification let pt = await eax.decrypt(ctBytes, nonceBytes, headerBytes); - expect(util.uint8ArrayToHex(pt)).to.equal(vec.msg.toLowerCase()); + expect(openpgp.util.Uint8Array_to_hex(pt)).to.equal(vec.msg.toLowerCase()); // tampering detection test ct = await eax.encrypt(msgBytes, nonceBytes, headerBytes); @@ -115,40 +113,38 @@ function testAESEAX() { // testing without additional data ct = await eax.encrypt(msgBytes, nonceBytes, new Uint8Array()); pt = await eax.decrypt(ct, nonceBytes, new Uint8Array()); - expect(util.uint8ArrayToHex(pt)).to.equal(vec.msg.toLowerCase()); + expect(openpgp.util.Uint8Array_to_hex(pt)).to.equal(vec.msg.toLowerCase()); // testing with multiple additional data - ct = await eax.encrypt(msgBytes, nonceBytes, util.concatUint8Array([headerBytes, headerBytes, headerBytes])); - pt = await eax.decrypt(ct, nonceBytes, util.concatUint8Array([headerBytes, headerBytes, headerBytes])); - expect(util.uint8ArrayToHex(pt)).to.equal(vec.msg.toLowerCase()); + ct = await eax.encrypt(msgBytes, nonceBytes, openpgp.util.concatUint8Array([headerBytes, headerBytes, headerBytes])); + pt = await eax.decrypt(ct, nonceBytes, openpgp.util.concatUint8Array([headerBytes, headerBytes, headerBytes])); + expect(openpgp.util.Uint8Array_to_hex(pt)).to.equal(vec.msg.toLowerCase()); })); }); } -module.exports = () => { - describe('Symmetric AES-EAX (native)', function() { - let useNativeVal; - beforeEach(function() { - useNativeVal = openpgp.config.useNative; - openpgp.config.useNative = true; - }); - afterEach(function() { - openpgp.config.useNative = useNativeVal; - }); - - testAESEAX(); +describe('Symmetric AES-EAX (native)', function() { + let use_nativeVal; + beforeEach(function() { + use_nativeVal = openpgp.config.use_native; + openpgp.config.use_native = true; }); + afterEach(function() { + openpgp.config.use_native = use_nativeVal; + }); + + testAESEAX(); +}); - describe('Symmetric AES-EAX (asm.js fallback)', function() { - let useNativeVal; - beforeEach(function() { - useNativeVal = openpgp.config.useNative; - openpgp.config.useNative = false; - }); - afterEach(function() { - openpgp.config.useNative = useNativeVal; - }); - - testAESEAX(); +describe('Symmetric AES-EAX (asm.js fallback)', function() { + let use_nativeVal; + beforeEach(function() { + use_nativeVal = openpgp.config.use_native; + openpgp.config.use_native = false; }); -}; + afterEach(function() { + openpgp.config.use_native = use_nativeVal; + }); + + testAESEAX(); +}); diff --git a/test/crypto/ecdh.js b/test/crypto/ecdh.js index ea400eae..75394fd6 100644 --- a/test/crypto/ecdh.js +++ b/test/crypto/ecdh.js @@ -1,9 +1,4 @@ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); -const OID = require('../../src/type/oid'); -const KDFParams = require('../../src/type/kdf_params'); -const elliptic_curves = require('../../src/crypto/public_key/elliptic'); -const util = require('../../src/util'); - +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); const chai = require('chai'); const elliptic_data = require('./elliptic_data'); @@ -12,18 +7,19 @@ chai.use(require('chai-as-promised')); const expect = chai.expect; const key_data = elliptic_data.key_data; /* eslint-disable no-invalid-this */ -module.exports = () => describe('ECDH key exchange @lightweight', function () { +describe('ECDH key exchange @lightweight', function () { + const elliptic_curves = openpgp.crypto.publicKey.elliptic; const decrypt_message = function (oid, hash, cipher, priv, pub, ephemeral, data, fingerprint) { - if (util.isString(data)) { - data = util.strToUint8Array(data); + if (openpgp.util.isString(data)) { + data = openpgp.util.str_to_Uint8Array(data); } else { data = new Uint8Array(data); } return Promise.resolve().then(() => { const curve = new elliptic_curves.Curve(oid); return elliptic_curves.ecdh.decrypt( - new OID(curve.oid), - new KDFParams({ cipher, hash }), + new openpgp.OID(curve.oid), + new openpgp.KDFParams({ cipher, hash }), new Uint8Array(ephemeral), data, new Uint8Array(pub), @@ -71,7 +67,7 @@ module.exports = () => describe('ECDH key exchange @lightweight', function () { )).to.be.rejectedWith(Error, /Not valid curve/).notify(done); }); it('Invalid ephemeral key', function (done) { - if (!openpgp.config.useIndutnyElliptic && !util.getNodeCrypto()) { + if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) { this.skip(); } expect(decrypt_message( @@ -79,7 +75,7 @@ module.exports = () => describe('ECDH key exchange @lightweight', function () { )).to.be.rejectedWith(Error, /Private key is not valid for specified curve|Unknown point format/).notify(done); }); it('Invalid elliptic public key', function (done) { - if (!openpgp.config.useIndutnyElliptic && !util.getNodeCrypto()) { + if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) { this.skip(); } expect(decrypt_message( @@ -87,7 +83,7 @@ module.exports = () => describe('ECDH key exchange @lightweight', function () { )).to.be.rejectedWith(Error, /Public key is not valid for specified curve|Failed to translate Buffer to a EC_POINT|Invalid elliptic public key/).notify(done); }); it('Invalid key data integrity', function (done) { - if (!openpgp.config.useIndutnyElliptic && !util.getNodeCrypto()) { + if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) { this.skip(); } expect(decrypt_message( @@ -124,141 +120,165 @@ module.exports = () => describe('ECDH key exchange @lightweight', function () { const fingerprint1 = new Uint8Array([ 177, 183, 116, 123, 76, 133, 245, 212, 151, 243, 236, - 71, 245, 86, 3, 168, 101, 56, 209, 105 + 71, 245, 86, 3, 168, 101, 74, 209, 105 ]); const fingerprint2 = new Uint8Array([ 177, 83, 123, 123, 76, 133, 245, 212, 151, 243, 236, 71, 245, 86, 3, 168, 101, 74, 209, 105 ]); + async function genPublicEphemeralKey(curve, Q, fingerprint) { + const curveObj = new openpgp.crypto.publicKey.elliptic.Curve(curve); + const oid = new openpgp.OID(curveObj.oid); + const { publicKey: V, sharedKey } = await openpgp.crypto.publicKey.elliptic.ecdh.genPublicEphemeralKey( + curveObj, Q + ); + let cipher_algo = curveObj.cipher; + const hash_algo = curveObj.hash; + const kdfParams = new openpgp.KDFParams({ cipher: cipher_algo, hash: hash_algo }); + const param = openpgp.crypto.publicKey.elliptic.ecdh.buildEcdhParam( + openpgp.enums.publicKey.ecdh, oid, kdfParams, fingerprint + ); + cipher_algo = openpgp.enums.read(openpgp.enums.symmetric, cipher_algo); + const Z = await openpgp.crypto.publicKey.elliptic.ecdh.kdf( + hash_algo, sharedKey, openpgp.crypto.cipher[cipher_algo].keySize, param, curveObj, false + ); + return { V, Z }; + } + + async function genPrivateEphemeralKey(curve, V, Q, d, fingerprint) { + const curveObj = new openpgp.crypto.publicKey.elliptic.Curve(curve); + const oid = new openpgp.OID(curveObj.oid); + const { sharedKey } = await openpgp.crypto.publicKey.elliptic.ecdh.genPrivateEphemeralKey( + curveObj, V, Q, d + ); + let cipher_algo = curveObj.cipher; + const hash_algo = curveObj.hash; + const kdfParams = new openpgp.KDFParams({ cipher: cipher_algo, hash: hash_algo }); + const param = openpgp.crypto.publicKey.elliptic.ecdh.buildEcdhParam( + openpgp.enums.publicKey.ecdh, oid, kdfParams, fingerprint + ); + cipher_algo = openpgp.enums.read(openpgp.enums.symmetric, cipher_algo); + const Z = await openpgp.crypto.publicKey.elliptic.ecdh.kdf( + hash_algo, sharedKey, openpgp.crypto.cipher[cipher_algo].keySize, param, curveObj, false + ); + return Z; + } + + async function genPrivateEphemeralKeySpecific(fun, curve, V, Q, d, fingerprint) { + const curveObj = new openpgp.crypto.publicKey.elliptic.Curve(curve); + const oid = new openpgp.OID(curveObj.oid); + let result; + switch (fun) { + case 'webPrivateEphemeralKey': { + result = await openpgp.crypto.publicKey.elliptic.ecdh[fun]( + curveObj, V, Q, d + ); + break; + } + case 'nodePrivateEphemeralKey': + case 'ellipticPrivateEphemeralKey': { + result = await openpgp.crypto.publicKey.elliptic.ecdh[fun]( + curveObj, V, d + ); + break; + } + } + const sharedKey = result.sharedKey; + let cipher_algo = curveObj.cipher; + const hash_algo = curveObj.hash; + const kdfParams = new openpgp.KDFParams({ cipher: cipher_algo, hash: hash_algo }); + const param = openpgp.crypto.publicKey.elliptic.ecdh.buildEcdhParam( + openpgp.enums.publicKey.ecdh, oid, kdfParams, fingerprint + ); + cipher_algo = openpgp.enums.read(openpgp.enums.symmetric, cipher_algo); + const Z = await openpgp.crypto.publicKey.elliptic.ecdh.kdf( + hash_algo, sharedKey, openpgp.crypto.cipher[cipher_algo].keySize, param, curveObj, false + ); + return Z; + } describe('ECDHE key generation', function () { - it('Invalid curve', async function () { - if (!openpgp.config.useIndutnyElliptic && !util.getNodeCrypto()) { + it('Invalid curve', function (done) { + if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) { this.skip(); } - const { key: publicKey } = await openpgp.generateKey({ curve: "secp256k1", userIds: [{ name: 'Test' }] }); - publicKey.subKeys[0].keyPacket.publicParams.Q = Q1; - publicKey.subKeys[0].keyPacket.fingerprint = fingerprint1; - await expect( - openpgp.encrypt({ publicKeys: [publicKey], message: openpgp.Message.fromText('test') }) - ).to.be.rejectedWith(Error, /Public key is not valid for specified curve|Failed to translate Buffer to a EC_POINT|Unknown point format/); + expect(genPublicEphemeralKey("secp256k1", Q1, fingerprint1) + ).to.be.rejectedWith(Error, /Public key is not valid for specified curve|Failed to translate Buffer to a EC_POINT|Unknown point format/).notify(done); }); it('Invalid public part of ephemeral key and private key', async function () { - const { key: publicKey } = await openpgp.generateKey({ curve: "curve25519", userIds: [{ name: 'Test' }] }); - publicKey.subKeys[0].keyPacket.publicParams.Q = Q1; - publicKey.subKeys[0].keyPacket.fingerprint = fingerprint1; - const { key: privateKey } = await openpgp.generateKey({ curve: "curve25519", userIds: [{ name: 'Test' }] }); - privateKey.subKeys[0].keyPacket.publicParams.Q = Q2; - privateKey.subKeys[0].keyPacket.privateParams.d = d2; - privateKey.subKeys[0].keyPacket.fingerprint = fingerprint1; - const message = await openpgp.encrypt({ publicKeys: [publicKey], message: openpgp.Message.fromText('test') }); - await expect( - openpgp.decrypt({ privateKeys: [privateKey], message: await openpgp.readArmoredMessage(message) }) - ).to.be.rejectedWith('Error decrypting message: Key Data Integrity failed'); + const ECDHE_VZ1 = await genPublicEphemeralKey("curve25519", Q1, fingerprint1); + const ECDHE_Z12 = await genPrivateEphemeralKey("curve25519", ECDHE_VZ1.V, Q2, d2, fingerprint1); + expect(Array.from(ECDHE_Z12).join(' ') === Array.from(ECDHE_VZ1.Z).join(' ')).to.be.false; }); it('Invalid fingerprint', async function () { - const { key: publicKey } = await openpgp.generateKey({ curve: "curve25519", userIds: [{ name: 'Test' }] }); - publicKey.subKeys[0].keyPacket.publicParams.Q = Q1; - publicKey.subKeys[0].keyPacket.fingerprint = fingerprint1; - const { key: privateKey } = await openpgp.generateKey({ curve: "curve25519", userIds: [{ name: 'Test' }] }); - privateKey.subKeys[0].keyPacket.publicParams.Q = Q2; - privateKey.subKeys[0].keyPacket.privateParams.d = d2; - privateKey.subKeys[0].keyPacket.fingerprint = fingerprint2; - const message = await openpgp.encrypt({ publicKeys: [publicKey], message: openpgp.Message.fromText('test') }); - await expect( - openpgp.decrypt({ privateKeys: [privateKey], message: await openpgp.readArmoredMessage(message) }) - ).to.be.rejectedWith('Error decrypting message: Session key decryption failed'); + const ECDHE_VZ2 = await genPublicEphemeralKey("curve25519", Q2, fingerprint1); + const ECDHE_Z2 = await genPrivateEphemeralKey("curve25519", ECDHE_VZ2.V, Q2, d2, fingerprint2); + expect(Array.from(ECDHE_Z2).join(' ') === Array.from(ECDHE_VZ2.Z).join(' ')).to.be.false; }); it('Different keys', async function () { - const { key: publicKey } = await openpgp.generateKey({ curve: "curve25519", userIds: [{ name: 'Test' }] }); - publicKey.subKeys[0].keyPacket.publicParams.Q = Q2; - publicKey.subKeys[0].keyPacket.fingerprint = fingerprint1; - const { key: privateKey } = await openpgp.generateKey({ curve: "curve25519", userIds: [{ name: 'Test' }] }); - privateKey.subKeys[0].keyPacket.publicParams.Q = Q1; - privateKey.subKeys[0].keyPacket.privateParams.d = d1; - privateKey.subKeys[0].keyPacket.fingerprint = fingerprint1; - const message = await openpgp.encrypt({ publicKeys: [publicKey], message: openpgp.Message.fromText('test') }); - await expect( - openpgp.decrypt({ privateKeys: [privateKey], message: await openpgp.readArmoredMessage(message) }) - ).to.be.rejectedWith('Error decrypting message: Key Data Integrity failed'); + const ECDHE_VZ1 = await genPublicEphemeralKey("curve25519", Q1, fingerprint1); + const ECDHE_VZ2 = await genPublicEphemeralKey("curve25519", Q2, fingerprint1); + const ECDHE_Z1 = await genPrivateEphemeralKey("curve25519", ECDHE_VZ1.V, Q1, d1, fingerprint1); + expect(Array.from(ECDHE_Z1).join(' ') === Array.from(ECDHE_VZ2.Z).join(' ')).to.be.false; }); it('Successful exchange curve25519', async function () { - const { key: publicKey } = await openpgp.generateKey({ curve: "curve25519", userIds: [{ name: 'Test' }] }); - publicKey.subKeys[0].keyPacket.publicParams.Q = Q1; - publicKey.subKeys[0].keyPacket.fingerprint = fingerprint1; - const { key: privateKey } = await openpgp.generateKey({ curve: "curve25519", userIds: [{ name: 'Test' }] }); - privateKey.subKeys[0].keyPacket.publicParams.Q = Q1; - privateKey.subKeys[0].keyPacket.privateParams.d = d1; - privateKey.subKeys[0].keyPacket.fingerprint = fingerprint1; - const message = await openpgp.encrypt({ publicKeys: [publicKey], message: openpgp.Message.fromText('test') }); - expect(( - await openpgp.decrypt({ privateKeys: [privateKey], message: await openpgp.readArmoredMessage(message) }) - ).data).to.equal('test'); + const ECDHE_VZ1 = await genPublicEphemeralKey("curve25519", Q1, fingerprint1); + const ECDHE_Z1 = await genPrivateEphemeralKey("curve25519", ECDHE_VZ1.V, Q1, d1, fingerprint1); + expect(Array.from(ECDHE_Z1).join(' ') === Array.from(ECDHE_VZ1.Z).join(' ')).to.be.true; }); it('Successful exchange NIST P256', async function () { - const { key: publicKey } = await openpgp.generateKey({ curve: "p256", userIds: [{ name: 'Test' }] }); - publicKey.subKeys[0].keyPacket.publicParams.Q = key_data.p256.pub; - publicKey.subKeys[0].keyPacket.fingerprint = fingerprint1; - const { key: privateKey } = await openpgp.generateKey({ curve: "p256", userIds: [{ name: 'Test' }] }); - privateKey.subKeys[0].keyPacket.publicParams.Q = key_data.p256.pub; - privateKey.subKeys[0].keyPacket.privateParams.d = key_data.p256.priv; - privateKey.subKeys[0].keyPacket.fingerprint = fingerprint1; - const message = await openpgp.encrypt({ publicKeys: [publicKey], message: openpgp.Message.fromText('test') }); - expect(( - await openpgp.decrypt({ privateKeys: [privateKey], message: await openpgp.readArmoredMessage(message) }) - ).data).to.equal('test'); + const ECDHE_VZ1 = await genPublicEphemeralKey("p256", key_data.p256.pub, fingerprint1); + const ECDHE_Z1 = await genPrivateEphemeralKey("p256", ECDHE_VZ1.V, key_data.p256.pub, key_data.p256.priv, fingerprint1); + expect(Array.from(ECDHE_Z1).join(' ') === Array.from(ECDHE_VZ1.Z).join(' ')).to.be.true; }); it('Successful exchange NIST P384', async function () { - const { key: publicKey } = await openpgp.generateKey({ curve: "p384", userIds: [{ name: 'Test' }] }); - publicKey.subKeys[0].keyPacket.publicParams.Q = key_data.p384.pub; - publicKey.subKeys[0].keyPacket.fingerprint = fingerprint1; - const { key: privateKey } = await openpgp.generateKey({ curve: "p384", userIds: [{ name: 'Test' }] }); - privateKey.subKeys[0].keyPacket.publicParams.Q = key_data.p384.pub; - privateKey.subKeys[0].keyPacket.privateParams.d = key_data.p384.priv; - privateKey.subKeys[0].keyPacket.fingerprint = fingerprint1; - const message = await openpgp.encrypt({ publicKeys: [publicKey], message: openpgp.Message.fromText('test') }); - expect(( - await openpgp.decrypt({ privateKeys: [privateKey], message: await openpgp.readArmoredMessage(message) }) - ).data).to.equal('test'); + const ECDHE_VZ1 = await genPublicEphemeralKey("p384", key_data.p384.pub, fingerprint1); + const ECDHE_Z1 = await genPrivateEphemeralKey("p384", ECDHE_VZ1.V, key_data.p384.pub, key_data.p384.priv, fingerprint1); + expect(Array.from(ECDHE_Z1).join(' ') === Array.from(ECDHE_VZ1.Z).join(' ')).to.be.true; }); it('Successful exchange NIST P521', async function () { - const { key: publicKey } = await openpgp.generateKey({ curve: "p521", userIds: [{ name: 'Test' }] }); - publicKey.subKeys[0].keyPacket.publicParams.Q = key_data.p521.pub; - publicKey.subKeys[0].keyPacket.fingerprint = fingerprint1; - const { key: privateKey } = await openpgp.generateKey({ curve: "p521", userIds: [{ name: 'Test' }] }); - privateKey.subKeys[0].keyPacket.publicParams.Q = key_data.p521.pub; - privateKey.subKeys[0].keyPacket.privateParams.d = key_data.p521.priv; - privateKey.subKeys[0].keyPacket.fingerprint = fingerprint1; - const message = await openpgp.encrypt({ publicKeys: [publicKey], message: openpgp.Message.fromText('test') }); - expect(( - await openpgp.decrypt({ privateKeys: [privateKey], message: await openpgp.readArmoredMessage(message) }) - ).data).to.equal('test'); + const ECDHE_VZ1 = await genPublicEphemeralKey("p521", key_data.p521.pub, fingerprint1); + const ECDHE_Z1 = await genPrivateEphemeralKey("p521", ECDHE_VZ1.V, key_data.p521.pub, key_data.p521.priv, fingerprint1); + expect(Array.from(ECDHE_Z1).join(' ') === Array.from(ECDHE_VZ1.Z).join(' ')).to.be.true; }); - it('Comparing decrypting with useNative = true and false', async function () { + it('Comparing keys derived using webCrypto and elliptic', async function () { const names = ["p256", "p384", "p521"]; + if (!openpgp.util.getWebCrypto() || !openpgp.config.use_indutny_elliptic) { + // eslint-disable-next-line no-invalid-this + this.skip(); + } return Promise.all(names.map(async function (name) { - const { key: publicKey } = await openpgp.generateKey({ curve: name, userIds: [{ name: 'Test' }] }); - publicKey.subKeys[0].keyPacket.publicParams.Q = key_data[name].pub; - publicKey.subKeys[0].keyPacket.fingerprint = fingerprint1; - const { key: privateKey } = await openpgp.generateKey({ curve: name, userIds: [{ name: 'Test' }] }); - privateKey.subKeys[0].keyPacket.publicParams.Q = key_data[name].pub; - privateKey.subKeys[0].keyPacket.privateParams.d = key_data[name].priv; - privateKey.subKeys[0].keyPacket.fingerprint = fingerprint1; - const message = await openpgp.encrypt({ publicKeys: [publicKey], message: openpgp.Message.fromText('test') }); - expect(( - await openpgp.decrypt({ privateKeys: [privateKey], message: await openpgp.readArmoredMessage(message) }) - ).data).to.equal('test'); - const useNative = openpgp.config.useNative; - openpgp.config.useNative = !useNative; + const curve = new elliptic_curves.Curve(name); try { - expect(( - await openpgp.decrypt({ privateKeys: [privateKey], message: await openpgp.readArmoredMessage(message) }) - ).data).to.equal('test'); - } finally { - openpgp.config.useNative = useNative; + await window.crypto.subtle.generateKey({ + name: "ECDSA", + namedCurve: curve.web.web + }, false, ["sign", "verify"]); + } catch (err) { + openpgp.util.print_debug_error(err); + return; } + const ECDHE_VZ1 = await genPublicEphemeralKey(name, key_data[name].pub, fingerprint1); + const ECDHE_Z1 = await genPrivateEphemeralKeySpecific('ellipticPrivateEphemeralKey', name, ECDHE_VZ1.V, key_data[name].pub, key_data[name].priv, fingerprint1); + const ECDHE_Z2 = await genPrivateEphemeralKeySpecific('webPrivateEphemeralKey', name, ECDHE_VZ1.V, key_data[name].pub, key_data[name].priv, fingerprint1); + expect(Array.from(ECDHE_Z1).join(' ') === Array.from(ECDHE_VZ1.Z).join(' ')).to.be.true; + expect(Array.from(ECDHE_Z1).join(' ') === Array.from(ECDHE_Z2).join(' ')).to.be.true; + })); + }); + it('Comparing keys derived using nodeCrypto and elliptic', async function () { + const names = ["p256", "p384", "p521"]; + if (!openpgp.util.getNodeCrypto() || !openpgp.config.use_indutny_elliptic) { + // eslint-disable-next-line no-invalid-this + this.skip(); + } + return Promise.all(names.map(async function (name) { + const ECDHE_VZ1 = await genPublicEphemeralKey(name, key_data[name].pub, fingerprint1); + const ECDHE_Z1 = await genPrivateEphemeralKeySpecific('ellipticPrivateEphemeralKey', name, ECDHE_VZ1.V, key_data[name].pub, key_data[name].priv, fingerprint1); + const ECDHE_Z2 = await genPrivateEphemeralKeySpecific('nodePrivateEphemeralKey', name, ECDHE_VZ1.V, key_data[name].pub, key_data[name].priv, fingerprint1); + expect(Array.from(ECDHE_Z1).join(' ') === Array.from(ECDHE_VZ1.Z).join(' ')).to.be.true; + expect(Array.from(ECDHE_Z1).join(' ') === Array.from(ECDHE_Z2).join(' ')).to.be.true; })); }); }); diff --git a/test/crypto/elliptic.js b/test/crypto/elliptic.js index 54d23632..ba620445 100644 --- a/test/crypto/elliptic.js +++ b/test/crypto/elliptic.js @@ -1,9 +1,4 @@ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); -const elliptic_curves = require('../../src/crypto/public_key/elliptic'); -const hashMod = require('../../src/crypto/hash'); -const config = require('../../src/config'); -const util = require('../../src/util'); - +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); const chai = require('chai'); const elliptic_data = require('./elliptic_data'); @@ -13,7 +8,9 @@ chai.use(require('chai-as-promised')); const expect = chai.expect; const key_data = elliptic_data.key_data; /* eslint-disable no-invalid-this */ -module.exports = () => describe('Elliptic Curve Cryptography @lightweight', function () { +describe('Elliptic Curve Cryptography @lightweight', function () { + const elliptic_curves = openpgp.crypto.publicKey.elliptic; + const signature_data = { priv: new Uint8Array([ 0x14, 0x2B, 0xE2, 0xB7, 0x4D, 0xBD, 0x1B, 0x22, @@ -68,10 +65,10 @@ module.exports = () => describe('Elliptic Curve Cryptography @lightweight', func done(); }); it('Creating KeyPair', function () { - if (!config.useIndutnyElliptic && !util.getNodeCrypto()) { + if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) { this.skip(); } - const names = config.useIndutnyElliptic ? ['p256', 'p384', 'p521', 'secp256k1', 'curve25519', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1'] : + const names = openpgp.config.use_indutny_elliptic ? ['p256', 'p384', 'p521', 'secp256k1', 'curve25519', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1'] : ['p256', 'p384', 'p521', 'curve25519']; return Promise.all(names.map(function (name) { const curve = new elliptic_curves.Curve(name); @@ -97,17 +94,34 @@ module.exports = () => describe('Elliptic Curve Cryptography @lightweight', func ).to.eventually.be.true; }); }); + it('Shared secret generation', async function () { + const curve = new elliptic_curves.Curve('p256'); + const { sharedKey: shared1 } = await elliptic_curves.ecdh.genPrivateEphemeralKey(curve, signature_data.pub, key_data.p256.pub, key_data.p256.priv); + const { sharedKey: shared2 } = await elliptic_curves.ecdh.genPrivateEphemeralKey(curve, key_data.p256.pub, signature_data.pub, signature_data.priv); + expect(shared1).to.deep.equal(shared2); + }); }); describe('ECDSA signature', function () { const verify_signature = async function (oid, hash, r, s, message, pub) { - if (util.isString(message)) { - message = util.strToUint8Array(message); - } else if (!util.isUint8Array(message)) { + if (openpgp.util.isString(message)) { + message = openpgp.util.str_to_Uint8Array(message); + } else if (!openpgp.util.isUint8Array(message)) { message = new Uint8Array(message); } const ecdsa = elliptic_curves.ecdsa; return ecdsa.verify( - oid, hash, { r: new Uint8Array(r), s: new Uint8Array(s) }, message, new Uint8Array(pub), await hashMod.digest(hash, message) + oid, hash, { r: new Uint8Array(r), s: new Uint8Array(s) }, message, new Uint8Array(pub), await openpgp.crypto.hash.digest(hash, message) + ); + }; + const verify_signature_elliptic = async function (oid, hash, r, s, message, pub) { + if (openpgp.util.isString(message)) { + message = openpgp.util.str_to_Uint8Array(message); + } else if (!openpgp.util.isUint8Array(message)) { + message = new Uint8Array(message); + } + const ecdsa = elliptic_curves.ecdsa; + return ecdsa.ellipticVerify( + new elliptic_curves.Curve(oid), { r: new Uint8Array(r), s: new Uint8Array(s) }, await openpgp.crypto.hash.digest(hash, message), new Uint8Array(pub) ); }; const secp256k1_point = new Uint8Array([ @@ -150,10 +164,10 @@ module.exports = () => describe('Elliptic Curve Cryptography @lightweight', func ]); }); it('Invalid public key', async function () { - if (!config.useIndutnyElliptic && !util.getNodeCrypto()) { + if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) { this.skip(); } - if (util.getNodeCrypto()) { + if (openpgp.util.getNodeCrypto()) { await expect(verify_signature( 'secp256k1', 8, [], [], [], [] )).to.eventually.be.false; @@ -161,46 +175,34 @@ module.exports = () => describe('Elliptic Curve Cryptography @lightweight', func 'secp256k1', 8, [], [], [], secp256k1_invalid_point_format )).to.eventually.be.false; } - if (config.useIndutnyElliptic) { - const useNative = config.useNative; - config.useNative = false; - try { - await Promise.all([ - expect(verify_signature( - 'secp256k1', 8, [], [], [], [] - )).to.be.rejectedWith(Error, /Unknown point format/), - expect(verify_signature( - 'secp256k1', 8, [], [], [], secp256k1_invalid_point_format - )).to.be.rejectedWith(Error, /Unknown point format/) - ]); - } finally { - config.useNative = useNative; - } + if (openpgp.config.use_indutny_elliptic) { + return Promise.all([ + expect(verify_signature_elliptic( + 'secp256k1', 8, [], [], [], [] + )).to.be.rejectedWith(Error, /Unknown point format/), + expect(verify_signature_elliptic( + 'secp256k1', 8, [], [], [], secp256k1_invalid_point_format + )).to.be.rejectedWith(Error, /Unknown point format/) + ]); } }); - it('Invalid point', async function () { - if (!config.useIndutnyElliptic && !util.getNodeCrypto()) { + it('Invalid point', function () { + if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) { this.skip(); } - if (util.getNodeCrypto()) { - await expect(verify_signature( + if (openpgp.util.getNodeCrypto()) { + expect(verify_signature( 'secp256k1', 8, [], [], [], secp256k1_invalid_point )).to.eventually.be.false; } - if (config.useIndutnyElliptic) { - const useNative = config.useNative; - config.useNative = false; - try { - await expect(verify_signature( - 'secp256k1', 8, [], [], [], secp256k1_invalid_point - )).to.be.rejectedWith(Error, /Invalid elliptic public key/); - } finally { - config.useNative = useNative; - } + if (openpgp.config.use_indutny_elliptic) { + expect(verify_signature_elliptic( + 'secp256k1', 8, [], [], [], secp256k1_invalid_point + )).to.be.rejectedWith(Error, /Invalid elliptic public key/); } }); it('Invalid signature', function (done) { - if (!config.useIndutnyElliptic && !util.getNodeCrypto()) { + if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) { this.skip(); } expect(verify_signature( @@ -239,8 +241,8 @@ module.exports = () => describe('Elliptic Curve Cryptography @lightweight', func const keyPrivate = new Uint8Array(keyPair.privateKey); const oid = curve.oid; const message = p384_message; - return elliptic_curves.ecdsa.sign(oid, 10, message, keyPublic, keyPrivate, await hashMod.digest(10, message)).then(async signature => { - await expect(elliptic_curves.ecdsa.verify(oid, 10, signature, message, keyPublic, await hashMod.digest(10, message))) + return elliptic_curves.ecdsa.sign(oid, 10, message, keyPublic, keyPrivate, await openpgp.crypto.hash.digest(10, message)).then(async signature => { + await expect(elliptic_curves.ecdsa.verify(oid, 10, signature, message, keyPublic, await openpgp.crypto.hash.digest(10, message))) .to.eventually.be.true; }); }); diff --git a/test/crypto/hash/index.js b/test/crypto/hash/index.js index 45ecf5cd..7c5527ee 100644 --- a/test/crypto/hash/index.js +++ b/test/crypto/hash/index.js @@ -1,5 +1,5 @@ -module.exports = () => describe('Hash', function () { - require('./md5.js')(); - require('./ripemd.js')(); - require('./sha.js')(); +describe('Hash', function () { + require('./md5.js'); + require('./ripemd.js'); + require('./sha.js'); }); diff --git a/test/crypto/hash/md5.js b/test/crypto/hash/md5.js index e75a7e43..5225c03f 100644 --- a/test/crypto/hash/md5.js +++ b/test/crypto/hash/md5.js @@ -1,15 +1,16 @@ -const md5 = require('../../../src/crypto/hash/md5'); -const util = require('../../../src/util'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../../dist/openpgp'); const chai = require('chai'); +const { util } = openpgp; +const md5 = openpgp.crypto.hash.md5; const { expect } = chai; -module.exports = () => it('MD5 with test vectors from RFC 1321', async function() { - expect(util.strToHex(util.uint8ArrayToStr(await md5(util.strToUint8Array(''))), 'MD5("") = d41d8cd98f00b204e9800998ecf8427e')).to.equal('d41d8cd98f00b204e9800998ecf8427e'); - expect(util.strToHex(util.uint8ArrayToStr(await md5(util.strToUint8Array('abc'))), 'MD5("a") = 0cc175b9c0f1b6a831c399e269772661')).to.equal('900150983cd24fb0d6963f7d28e17f72'); - expect(util.strToHex(util.uint8ArrayToStr(await md5(util.strToUint8Array('message digest'))), 'MD5("message digest") = f96b697d7cb7938d525a2f31aaf161d0')).to.equal('f96b697d7cb7938d525a2f31aaf161d0'); - expect(util.strToHex(util.uint8ArrayToStr(await md5(util.strToUint8Array('abcdefghijklmnopqrstuvwxyz'))), 'MD5("abcdefghijklmnopqrstuvwxyz") = c3fcd3d76192e4007dfb496cca67e13b')).to.equal('c3fcd3d76192e4007dfb496cca67e13b'); - expect(util.strToHex(util.uint8ArrayToStr(await md5(util.strToUint8Array('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'))), 'MD5("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") = d174ab98d277d9f5a5611c2c9f419d9f')).to.equal('d174ab98d277d9f5a5611c2c9f419d9f'); - expect(util.strToHex(util.uint8ArrayToStr(await md5(util.strToUint8Array('12345678901234567890123456789012345678901234567890123456789012345678901234567890'))), 'MD5("12345678901234567890123456789012345678901234567890123456789012345678901234567890") = 57edf4a22be3c955ac49da2e2107b67a')).to.equal('57edf4a22be3c955ac49da2e2107b67a'); +it('MD5 with test vectors from RFC 1321', async function() { + expect(util.str_to_hex(util.Uint8Array_to_str(await md5(util.str_to_Uint8Array(''))), 'MD5("") = d41d8cd98f00b204e9800998ecf8427e')).to.equal('d41d8cd98f00b204e9800998ecf8427e'); + expect(util.str_to_hex(util.Uint8Array_to_str(await md5(util.str_to_Uint8Array('abc'))), 'MD5("a") = 0cc175b9c0f1b6a831c399e269772661')).to.equal('900150983cd24fb0d6963f7d28e17f72'); + expect(util.str_to_hex(util.Uint8Array_to_str(await md5(util.str_to_Uint8Array('message digest'))), 'MD5("message digest") = f96b697d7cb7938d525a2f31aaf161d0')).to.equal('f96b697d7cb7938d525a2f31aaf161d0'); + expect(util.str_to_hex(util.Uint8Array_to_str(await md5(util.str_to_Uint8Array('abcdefghijklmnopqrstuvwxyz'))), 'MD5("abcdefghijklmnopqrstuvwxyz") = c3fcd3d76192e4007dfb496cca67e13b')).to.equal('c3fcd3d76192e4007dfb496cca67e13b'); + expect(util.str_to_hex(util.Uint8Array_to_str(await md5(util.str_to_Uint8Array('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'))), 'MD5("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") = d174ab98d277d9f5a5611c2c9f419d9f')).to.equal('d174ab98d277d9f5a5611c2c9f419d9f'); + expect(util.str_to_hex(util.Uint8Array_to_str(await md5(util.str_to_Uint8Array('12345678901234567890123456789012345678901234567890123456789012345678901234567890'))), 'MD5("12345678901234567890123456789012345678901234567890123456789012345678901234567890") = 57edf4a22be3c955ac49da2e2107b67a')).to.equal('57edf4a22be3c955ac49da2e2107b67a'); }); diff --git a/test/crypto/hash/ripemd.js b/test/crypto/hash/ripemd.js index 6f56367d..f43c68a7 100644 --- a/test/crypto/hash/ripemd.js +++ b/test/crypto/hash/ripemd.js @@ -1,13 +1,14 @@ -const { ripemd: rmdString } = require('../../../src/crypto/hash'); -const util = require('../../../src/util'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../../dist/openpgp'); const chai = require('chai'); +const { util } = openpgp; +const rmdString = openpgp.crypto.hash.ripemd; const { expect } = chai; -module.exports = () => it("RIPE-MD 160 bits with test vectors from https://homes.esat.kuleuven.be/~bosselae/ripemd160.html", async function() { - expect(util.strToHex(util.uint8ArrayToStr(await rmdString(util.strToUint8Array(''))), 'RMDstring("") = 9c1185a5c5e9fc54612808977ee8f548b2258d31')).to.equal('9c1185a5c5e9fc54612808977ee8f548b2258d31'); - expect(util.strToHex(util.uint8ArrayToStr(await rmdString(util.strToUint8Array('a'))), 'RMDstring("a") = 0bdc9d2d256b3ee9daae347be6f4dc835a467ffe')).to.equal('0bdc9d2d256b3ee9daae347be6f4dc835a467ffe'); - expect(util.strToHex(util.uint8ArrayToStr(await rmdString(util.strToUint8Array('abc'))), 'RMDstring("abc") = 8eb208f7e05d987a9b044a8e98c6b087f15a0bfc')).to.equal('8eb208f7e05d987a9b044a8e98c6b087f15a0bfc'); - expect(util.strToHex(util.uint8ArrayToStr(await rmdString(util.strToUint8Array('message digest'))), 'RMDstring("message digest") = 5d0689ef49d2fae572b881b123a85ffa21595f36')).to.equal('5d0689ef49d2fae572b881b123a85ffa21595f36'); +it("RIPE-MD 160 bits with test vectors from https://homes.esat.kuleuven.be/~bosselae/ripemd160.html", async function() { + expect(util.str_to_hex(util.Uint8Array_to_str(await rmdString(util.str_to_Uint8Array(''))), 'RMDstring("") = 9c1185a5c5e9fc54612808977ee8f548b2258d31')).to.equal('9c1185a5c5e9fc54612808977ee8f548b2258d31'); + expect(util.str_to_hex(util.Uint8Array_to_str(await rmdString(util.str_to_Uint8Array('a'))), 'RMDstring("a") = 0bdc9d2d256b3ee9daae347be6f4dc835a467ffe')).to.equal('0bdc9d2d256b3ee9daae347be6f4dc835a467ffe'); + expect(util.str_to_hex(util.Uint8Array_to_str(await rmdString(util.str_to_Uint8Array('abc'))), 'RMDstring("abc") = 8eb208f7e05d987a9b044a8e98c6b087f15a0bfc')).to.equal('8eb208f7e05d987a9b044a8e98c6b087f15a0bfc'); + expect(util.str_to_hex(util.Uint8Array_to_str(await rmdString(util.str_to_Uint8Array('message digest'))), 'RMDstring("message digest") = 5d0689ef49d2fae572b881b123a85ffa21595f36')).to.equal('5d0689ef49d2fae572b881b123a85ffa21595f36'); }); diff --git a/test/crypto/hash/sha.js b/test/crypto/hash/sha.js index 597f917b..ebbdf7bb 100644 --- a/test/crypto/hash/sha.js +++ b/test/crypto/hash/sha.js @@ -1,19 +1,20 @@ -const hash = require('../../../src/crypto/hash'); -const util = require('../../../src/util'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../../dist/openpgp'); const chai = require('chai'); +const { util } = openpgp; +const { hash } = openpgp.crypto; const { expect } = chai; -module.exports = () => it('SHA* with test vectors from NIST FIPS 180-2', async function() { - expect(util.strToHex(util.uint8ArrayToStr(await hash.sha1(util.strToUint8Array('abc'))), 'hash.sha1("abc") = a9993e364706816aba3e25717850c26c9cd0d89d')).to.equal('a9993e364706816aba3e25717850c26c9cd0d89d'); - expect(util.strToHex(util.uint8ArrayToStr(await hash.sha1(util.strToUint8Array('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'))), 'hash.sha1("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") = 84983e441c3bd26ebaae4aa1f95129e5e54670f1')).to.equal('84983e441c3bd26ebaae4aa1f95129e5e54670f1'); - expect(util.strToHex(util.uint8ArrayToStr(await hash.sha224(util.strToUint8Array('abc'))), 'hash.sha224("abc") = 23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7')).to.equal('23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7'); - expect(util.strToHex(util.uint8ArrayToStr(await hash.sha224(util.strToUint8Array('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'))), 'hash.sha224("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") = 75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525')).to.equal('75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525'); - expect(util.strToHex(util.uint8ArrayToStr(await hash.sha256(util.strToUint8Array('abc'))), 'hash.sha256("abc") = ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad')).to.equal('ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'); - expect(util.strToHex(util.uint8ArrayToStr(await hash.sha256(util.strToUint8Array('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'))), 'hash.sha256("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") = 248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1')).to.equal('248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1'); - expect(util.strToHex(util.uint8ArrayToStr(await hash.sha384(util.strToUint8Array('abc'))), 'hash.sha384("abc") = cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7')).to.equal('cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7'); - expect(util.strToHex(util.uint8ArrayToStr(await hash.sha384(util.strToUint8Array('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'))), 'hash.sha384("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") = 3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b')).to.equal('3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b'); - expect(util.strToHex(util.uint8ArrayToStr(await hash.sha512(util.strToUint8Array('abc'))), 'hash.sha512("abc") = ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f')).to.equal('ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f'); - expect(util.strToHex(util.uint8ArrayToStr(await hash.sha512(util.strToUint8Array('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'))), 'hash.sha512("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") = 204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445')).to.equal('204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445'); +it('SHA* with test vectors from NIST FIPS 180-2', async function() { + expect(util.str_to_hex(util.Uint8Array_to_str(await hash.sha1(util.str_to_Uint8Array('abc'))), 'hash.sha1("abc") = a9993e364706816aba3e25717850c26c9cd0d89d')).to.equal('a9993e364706816aba3e25717850c26c9cd0d89d'); + expect(util.str_to_hex(util.Uint8Array_to_str(await hash.sha1(util.str_to_Uint8Array('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'))), 'hash.sha1("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") = 84983e441c3bd26ebaae4aa1f95129e5e54670f1')).to.equal('84983e441c3bd26ebaae4aa1f95129e5e54670f1'); + expect(util.str_to_hex(util.Uint8Array_to_str(await hash.sha224(util.str_to_Uint8Array('abc'))), 'hash.sha224("abc") = 23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7')).to.equal('23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7'); + expect(util.str_to_hex(util.Uint8Array_to_str(await hash.sha224(util.str_to_Uint8Array('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'))), 'hash.sha224("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") = 75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525')).to.equal('75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525'); + expect(util.str_to_hex(util.Uint8Array_to_str(await hash.sha256(util.str_to_Uint8Array('abc'))), 'hash.sha256("abc") = ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad')).to.equal('ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'); + expect(util.str_to_hex(util.Uint8Array_to_str(await hash.sha256(util.str_to_Uint8Array('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'))), 'hash.sha256("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") = 248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1')).to.equal('248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1'); + expect(util.str_to_hex(util.Uint8Array_to_str(await hash.sha384(util.str_to_Uint8Array('abc'))), 'hash.sha384("abc") = cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7')).to.equal('cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7'); + expect(util.str_to_hex(util.Uint8Array_to_str(await hash.sha384(util.str_to_Uint8Array('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'))), 'hash.sha384("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") = 3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b')).to.equal('3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b'); + expect(util.str_to_hex(util.Uint8Array_to_str(await hash.sha512(util.str_to_Uint8Array('abc'))), 'hash.sha512("abc") = ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f')).to.equal('ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f'); + expect(util.str_to_hex(util.Uint8Array_to_str(await hash.sha512(util.str_to_Uint8Array('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq'))), 'hash.sha512("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") = 204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445')).to.equal('204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445'); }); diff --git a/test/crypto/index.js b/test/crypto/index.js index a897e1c2..4f737fc2 100644 --- a/test/crypto/index.js +++ b/test/crypto/index.js @@ -1,14 +1,14 @@ -module.exports = () => describe('Crypto', function () { - require('./cipher')(); - require('./hash')(); - require('./random.js')(); - require('./crypto.js')(); - require('./elliptic.js')(); - require('./ecdh.js')(); - require('./pkcs5.js')(); - require('./aes_kw.js')(); - require('./eax.js')(); - require('./ocb.js')(); - require('./rsa.js')(); - require('./validate.js')(); +describe('Crypto', function () { + require('./cipher'); + require('./hash'); + require('./random.js'); + require('./crypto.js'); + require('./elliptic.js'); + require('./ecdh.js'); + require('./pkcs5.js'); + require('./aes_kw.js'); + require('./eax.js'); + require('./ocb.js'); + require('./rsa.js'); + require('./validate.js'); }); diff --git a/test/crypto/ocb.js b/test/crypto/ocb.js index e2c7f89f..2e824c9f 100644 --- a/test/crypto/ocb.js +++ b/test/crypto/ocb.js @@ -2,18 +2,17 @@ // Adapted from https://github.com/artjomb/cryptojs-extension/blob/8c61d159/test/eax.js -const OCB = require('../../src/crypto/ocb'); -const util = require('../../src/util'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); const chai = require('chai'); chai.use(require('chai-as-promised')); const expect = chai.expect; -module.exports = () => describe('Symmetric AES-OCB', function() { +describe('Symmetric AES-OCB', function() { it('Passes all test vectors', async function() { const K = '000102030405060708090A0B0C0D0E0F'; - const keyBytes = util.hexToUint8Array(K); + const keyBytes = openpgp.util.hex_to_Uint8Array(K); const vectors = [ // From https://tools.ietf.org/html/rfc7253#appendix-A @@ -118,20 +117,20 @@ module.exports = () => describe('Symmetric AES-OCB', function() { const cipher = 'aes128'; await Promise.all(vectors.map(async vec => { - const msgBytes = util.hexToUint8Array(vec.P); - const nonceBytes = util.hexToUint8Array(vec.N); - const headerBytes = util.hexToUint8Array(vec.A); - const ctBytes = util.hexToUint8Array(vec.C); + const msgBytes = openpgp.util.hex_to_Uint8Array(vec.P); + const nonceBytes = openpgp.util.hex_to_Uint8Array(vec.N); + const headerBytes = openpgp.util.hex_to_Uint8Array(vec.A); + const ctBytes = openpgp.util.hex_to_Uint8Array(vec.C); - const ocb = await OCB(cipher, keyBytes); + const ocb = await openpgp.crypto.ocb(cipher, keyBytes); // encryption test let ct = await ocb.encrypt(msgBytes, nonceBytes, headerBytes); - expect(util.uint8ArrayToHex(ct)).to.equal(vec.C.toLowerCase()); + expect(openpgp.util.Uint8Array_to_hex(ct)).to.equal(vec.C.toLowerCase()); // decryption test with verification let pt = await ocb.decrypt(ctBytes, nonceBytes, headerBytes); - expect(util.uint8ArrayToHex(pt)).to.equal(vec.P.toLowerCase()); + expect(openpgp.util.Uint8Array_to_hex(pt)).to.equal(vec.P.toLowerCase()); // tampering detection test ct = await ocb.encrypt(msgBytes, nonceBytes, headerBytes); @@ -142,12 +141,12 @@ module.exports = () => describe('Symmetric AES-OCB', function() { // testing without additional data ct = await ocb.encrypt(msgBytes, nonceBytes, new Uint8Array()); pt = await ocb.decrypt(ct, nonceBytes, new Uint8Array()); - expect(util.uint8ArrayToHex(pt)).to.equal(vec.P.toLowerCase()); + expect(openpgp.util.Uint8Array_to_hex(pt)).to.equal(vec.P.toLowerCase()); // testing with multiple additional data - ct = await ocb.encrypt(msgBytes, nonceBytes, util.concatUint8Array([headerBytes, headerBytes, headerBytes])); - pt = await ocb.decrypt(ct, nonceBytes, util.concatUint8Array([headerBytes, headerBytes, headerBytes])); - expect(util.uint8ArrayToHex(pt)).to.equal(vec.P.toLowerCase()); + ct = await ocb.encrypt(msgBytes, nonceBytes, openpgp.util.concatUint8Array([headerBytes, headerBytes, headerBytes])); + pt = await ocb.decrypt(ct, nonceBytes, openpgp.util.concatUint8Array([headerBytes, headerBytes, headerBytes])); + expect(openpgp.util.Uint8Array_to_hex(pt)).to.equal(vec.P.toLowerCase()); })); }); @@ -163,22 +162,22 @@ module.exports = () => describe('Symmetric AES-OCB', function() { const k = new Uint8Array(keylen / 8); k[k.length - 1] = taglen; - const ocb = await OCB('aes' + keylen, k); + const ocb = await openpgp.crypto.ocb('aes' + keylen, k); const c = []; let n; for (let i = 0; i < 128; i++) { const s = new Uint8Array(i); - n = util.concatUint8Array([new Uint8Array(8), util.writeNumber(3 * i + 1, 4)]); + n = openpgp.util.concatUint8Array([new Uint8Array(8), openpgp.util.writeNumber(3 * i + 1, 4)]); c.push(await ocb.encrypt(s, n, s)); - n = util.concatUint8Array([new Uint8Array(8), util.writeNumber(3 * i + 2, 4)]); + n = openpgp.util.concatUint8Array([new Uint8Array(8), openpgp.util.writeNumber(3 * i + 2, 4)]); c.push(await ocb.encrypt(s, n, new Uint8Array())); - n = util.concatUint8Array([new Uint8Array(8), util.writeNumber(3 * i + 3, 4)]); + n = openpgp.util.concatUint8Array([new Uint8Array(8), openpgp.util.writeNumber(3 * i + 3, 4)]); c.push(await ocb.encrypt(new Uint8Array(), n, s)); } - n = util.concatUint8Array([new Uint8Array(8), util.writeNumber(385, 4)]); - const output = await ocb.encrypt(new Uint8Array(), n, util.concatUint8Array(c)); - expect(util.uint8ArrayToHex(output)).to.equal(outputs[keylen].toLowerCase()); + n = openpgp.util.concatUint8Array([new Uint8Array(8), openpgp.util.writeNumber(385, 4)]); + const output = await ocb.encrypt(new Uint8Array(), n, openpgp.util.concatUint8Array(c)); + expect(openpgp.util.Uint8Array_to_hex(output)).to.equal(outputs[keylen].toLowerCase()); })); }); }); diff --git a/test/crypto/pkcs5.js b/test/crypto/pkcs5.js index ea649554..42779acd 100644 --- a/test/crypto/pkcs5.js +++ b/test/crypto/pkcs5.js @@ -1,14 +1,39 @@ -const pkcs5 = require('../../src/crypto/pkcs5'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); const expect = require('chai').expect; -module.exports = () => describe('PKCS5 padding', function() { - it('Add and remove padding', function () { - const m = new Uint8Array([0,1,2,3,4,5,6,7,8]); - const padded = pkcs5.encode(m); - const unpadded = pkcs5.decode(padded); - expect(padded[padded.length - 1]).to.equal(7); - expect(padded.length % 8).to.equal(0); - expect(unpadded).to.deep.equal(m); +describe('PKCS5 padding', function() { + function repeat(pattern, count) { + let result = ''; + for (let k = 0; k < count; ++k) { + result += pattern; + } + return result; + } + const pkcs5 = openpgp.crypto.pkcs5; + it('Add padding', function () { + let s = ''; + while (s.length < 16) { + const r = pkcs5.encode(s); + // 0..7 -> 8, 8..15 -> 16 + const l = Math.ceil((s.length + 1) / 8) * 8; + const c = l - s.length; + expect(r.length).to.equal(l); + expect(c).is.at.least(1).is.at.most(8); + expect(r.substr(-1)).to.equal(String.fromCharCode(c)); + s += ' '; + } + }); + it('Remove padding', function () { + for (let k = 1; k <= 8; ++k) { + const s = repeat(' ', 8 - k); + const r = s + repeat(String.fromCharCode(k), k); + const t = pkcs5.decode(r); + expect(t).to.equal(s); + } + }); + it('Invalid padding', function () { + expect(function () { pkcs5.decode(' '); }).to.throw(Error, /Invalid padding/); + expect(function () { pkcs5.decode(''); }).to.throw(Error, /Invalid padding/); }); }); diff --git a/test/crypto/random.js b/test/crypto/random.js index 2b76ce45..4c8056d7 100644 --- a/test/crypto/random.js +++ b/test/crypto/random.js @@ -1,14 +1,14 @@ -const random = require('../../src/crypto/random'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); const chai = require('chai'); const { expect } = chai; -module.exports = () => describe('Random Buffer', function() { +describe('Random Buffer', function() { let randomBuffer; before(function() { - randomBuffer = new random.randomBuffer.constructor(); + randomBuffer = new openpgp.crypto.random.randomBuffer.constructor(); expect(randomBuffer).to.exist; }); diff --git a/test/crypto/rsa.js b/test/crypto/rsa.js index 71f8a4f2..31666e6f 100644 --- a/test/crypto/rsa.js +++ b/test/crypto/rsa.js @@ -1,8 +1,4 @@ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); -const crypto = require('../../src/crypto'); -const random = require('../../src/crypto/random'); -const util = require('../../src/util'); - +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); const chai = require('chai'); chai.use(require('chai-as-promised')); @@ -11,11 +7,11 @@ const expect = chai.expect; /* eslint-disable no-unused-expressions */ /* eslint-disable no-invalid-this */ -const native = util.getWebCrypto() || util.getNodeCrypto(); -module.exports = () => (!native ? describe.skip : describe)('basic RSA cryptography with native crypto', function () { +const native = openpgp.util.getWebCrypto() || openpgp.util.getNodeCrypto(); +(!native ? describe.skip : describe)('basic RSA cryptography with native crypto', function () { it('generate rsa key', async function() { - const bits = 1024; - const keyObject = await crypto.publicKey.rsa.generate(bits, 65537); + const bits = openpgp.util.getWebCryptoAll() ? 2048 : 1024; + const keyObject = await openpgp.crypto.publicKey.rsa.generate(bits, "10001"); expect(keyObject.n).to.exist; expect(keyObject.e).to.exist; expect(keyObject.d).to.exist; @@ -25,104 +21,160 @@ module.exports = () => (!native ? describe.skip : describe)('basic RSA cryptogra }); it('sign and verify using generated key params', async function() { - const bits = 1024; - const { publicParams, privateParams } = await crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); - const message = await random.getRandomBytes(64); + const bits = openpgp.util.getWebCryptoAll() ? 2048 : 1024; + const keyParams = await openpgp.crypto.generateParams(openpgp.enums.publicKey.rsa_sign, bits); + const message = await openpgp.crypto.random.getRandomBytes(64); const hash_algo = openpgp.enums.write(openpgp.enums.hash, 'sha256'); - const hashed = await crypto.hash.digest(hash_algo, message); - const { n, e, d, p, q, u } = { ...publicParams, ...privateParams }; - const signature = await crypto.publicKey.rsa.sign(hash_algo, message, n, e, d, p, q, u, hashed); + const hashed = await openpgp.crypto.hash.digest(hash_algo, message); + const n = keyParams[0].toUint8Array(); + const e = keyParams[1].toUint8Array(); + const d = keyParams[2].toUint8Array(); + const p = keyParams[3].toUint8Array(); + const q = keyParams[4].toUint8Array(); + const u = keyParams[5].toUint8Array(); + const signature = await openpgp.crypto.publicKey.rsa.sign(hash_algo, message, n, e, d, p, q, u, hashed); expect(signature).to.exist; - const verify = await crypto.publicKey.rsa.verify(hash_algo, message, signature, n, e, hashed); + const verify = await openpgp.crypto.publicKey.rsa.verify(hash_algo, message, signature, n, e, hashed); expect(verify).to.be.true; }); it('encrypt and decrypt using generated key params', async function() { - const bits = 1024; - const { publicParams, privateParams } = await crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); - const { n, e, d, p, q, u } = { ...publicParams, ...privateParams }; - const message = await crypto.generateSessionKey('aes256'); - const encrypted = await crypto.publicKey.rsa.encrypt(message, n, e); - const decrypted = await crypto.publicKey.rsa.decrypt(encrypted, n, e, d, p, q, u); - expect(decrypted).to.deep.equal(message); + const bits = openpgp.util.getWebCryptoAll() ? 2048 : 1024; + const keyParams = await openpgp.crypto.generateParams(openpgp.enums.publicKey.rsa_sign, bits); + const n = keyParams[0].toUint8Array(); + const e = keyParams[1].toUint8Array(); + const d = keyParams[2].toUint8Array(); + const p = keyParams[3].toUint8Array(); + const q = keyParams[4].toUint8Array(); + const u = keyParams[5].toUint8Array(); + const message = openpgp.util.Uint8Array_to_str(await openpgp.crypto.generateSessionKey('aes256')); + const encrypted = await openpgp.crypto.publicKey.rsa.encrypt(openpgp.util.str_to_Uint8Array(message), n, e); + const result = new openpgp.MPI(encrypted); + const decrypted = await openpgp.crypto.publicKey.rsa.decrypt(result.toUint8Array(), n, e, d, p, q, u); + expect(decrypted).to.be.equal(message); }); it('decrypt nodeCrypto by bnCrypto and vice versa', async function() { - if (!util.getNodeCrypto()) { + if (!openpgp.util.getNodeCrypto()) { this.skip(); } const bits = 1024; - const { publicParams, privateParams } = await crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); - const { n, e, d, p, q, u } = { ...publicParams, ...privateParams }; - const message = await crypto.generateSessionKey('aes256'); - const useNative = openpgp.config.useNative; - try { - openpgp.config.useNative = false; - const encryptedBn = await crypto.publicKey.rsa.encrypt(message, n, e); - openpgp.config.useNative = true; - const decrypted1 = await crypto.publicKey.rsa.decrypt(encryptedBn, n, e, d, p, q, u); - expect(decrypted1).to.deep.equal(message); - const encryptedNode = await crypto.publicKey.rsa.encrypt(message, n, e); - openpgp.config.useNative = false; - const decrypted2 = await crypto.publicKey.rsa.decrypt(encryptedNode, n, e, d, p, q, u); - expect(decrypted2).to.deep.equal(message); - } finally { - openpgp.config.useNative = useNative; - } + const keyParams = await openpgp.crypto.generateParams(openpgp.enums.publicKey.rsa_sign, bits); + const n = keyParams[0].toUint8Array(); + const e = keyParams[1].toUint8Array(); + const d = keyParams[2].toUint8Array(); + const p = keyParams[3].toUint8Array(); + const q = keyParams[4].toUint8Array(); + const u = keyParams[5].toUint8Array(); + const message = openpgp.util.Uint8Array_to_str(await openpgp.crypto.generateSessionKey('aes256')); + const encryptedBn = await openpgp.crypto.publicKey.rsa.bnEncrypt(openpgp.util.str_to_Uint8Array(message), n, e); + const resultBN = new openpgp.MPI(encryptedBn); + const decrypted1 = await openpgp.crypto.publicKey.rsa.nodeDecrypt(resultBN.toUint8Array(), n, e, d, p, q, u); + expect(decrypted1).to.be.equal(message); + const encryptedNode = await openpgp.crypto.publicKey.rsa.nodeEncrypt(openpgp.util.str_to_Uint8Array(message), n, e); + const resultNode = new openpgp.MPI(encryptedNode); + const decrypted2 = await openpgp.crypto.publicKey.rsa.bnDecrypt(resultNode.toUint8Array(), n, e, d, p, q, u); + expect(decrypted2).to.be.equal(message); }); - it('compare native crypto and bn math sign', async function() { - const bits = 1024; - const { publicParams, privateParams } = await crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); - const { n, e, d, p, q, u } = { ...publicParams, ...privateParams }; - const message = await random.getRandomBytes(64); + it('compare webCrypto and bn math sign', async function() { + if (!openpgp.util.getWebCrypto()) { + this.skip(); + } + const bits = openpgp.util.getWebCrypto() ? 2048 : 1024; + const keyParams = await openpgp.crypto.generateParams(openpgp.enums.publicKey.rsa_sign, bits); + const n = keyParams[0].toUint8Array(); + const e = keyParams[1].toUint8Array(); + const d = keyParams[2].toUint8Array(); + const p = keyParams[3].toUint8Array(); + const q = keyParams[4].toUint8Array(); + const u = keyParams[5].toUint8Array(); + const message = await openpgp.crypto.random.getRandomBytes(64); const hashName = 'sha256'; const hash_algo = openpgp.enums.write(openpgp.enums.hash, hashName); - const hashed = await crypto.hash.digest(hash_algo, message); - const useNative = openpgp.config.useNative; + const hashed = await openpgp.crypto.hash.digest(hash_algo, message); + let signatureWeb; try { - openpgp.config.useNative = true; - let signatureWeb; - try { - signatureWeb = await crypto.publicKey.rsa.sign(hash_algo, message, n, e, d, p, q, u, hashed); - } catch (error) { - util.printDebugError('web crypto error'); - this.skip(); - } - openpgp.config.useNative = false; - const signatureBN = await crypto.publicKey.rsa.sign(hash_algo, message, n, e, d, p, q, u, hashed); - expect(util.uint8ArrayToHex(signatureWeb)).to.be.equal(util.uint8ArrayToHex(signatureBN)); - } finally { - openpgp.config.useNative = useNative; + signatureWeb = await openpgp.crypto.publicKey.rsa.webSign('SHA-256', message, n, e, d, p, q, u, hashed); + } catch (error) { + openpgp.util.print_debug_error('web crypto error'); + this.skip(); } + const signatureBN = await openpgp.crypto.publicKey.rsa.bnSign(hash_algo, n, d, hashed); + expect(openpgp.util.Uint8Array_to_hex(signatureWeb)).to.be.equal(openpgp.util.Uint8Array_to_hex(signatureBN)); }); - it('compare native crypto and bn math verify', async function() { - const bits = 1024; - const { publicParams, privateParams } = await crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); - const { n, e, d, p, q, u } = { ...publicParams, ...privateParams }; - const message = await random.getRandomBytes(64); + it('compare webCrypto and bn math verify', async function() { + if (!openpgp.util.getWebCrypto()) { + this.skip(); + } + const bits = openpgp.util.getWebCrypto() ? 2048 : 1024; + const keyParams = await openpgp.crypto.generateParams(openpgp.enums.publicKey.rsa_sign, bits); + const n = keyParams[0].toUint8Array(); + const e = keyParams[1].toUint8Array(); + const d = keyParams[2].toUint8Array(); + const p = keyParams[3].toUint8Array(); + const q = keyParams[4].toUint8Array(); + const u = keyParams[5].toUint8Array(); + const message = await openpgp.crypto.random.getRandomBytes(64); const hashName = 'sha256'; const hash_algo = openpgp.enums.write(openpgp.enums.hash, hashName); - const hashed = await crypto.hash.digest(hash_algo, message); + const hashed = await openpgp.crypto.hash.digest(hash_algo, message); let verifyWeb; let signature; - const useNative = openpgp.config.useNative; try { - openpgp.config.useNative = true; - try { - signature = await crypto.publicKey.rsa.sign(hash_algo, message, n, e, d, p, q, u, hashed); - verifyWeb = await crypto.publicKey.rsa.verify(hash_algo, message, signature, n, e); - } catch (error) { - util.printDebugError('web crypto error'); - this.skip(); - } - openpgp.config.useNative = false; - const verifyBN = await crypto.publicKey.rsa.verify(hash_algo, message, signature, n, e, hashed); - expect(verifyWeb).to.be.true; - expect(verifyBN).to.be.true; - } finally { - openpgp.config.useNative = useNative; + signature = await openpgp.crypto.publicKey.rsa.webSign('SHA-256', message, n, e, d, p, q, u, hashed); + verifyWeb = await openpgp.crypto.publicKey.rsa.webVerify('SHA-256', message, signature, n, e); + } catch (error) { + openpgp.util.print_debug_error('web crypto error'); + this.skip(); } + const verifyBN = await openpgp.crypto.publicKey.rsa.bnVerify(hash_algo, signature, n, e, hashed); + expect(verifyWeb).to.be.true; + expect(verifyBN).to.be.true; + }); + + it('compare nodeCrypto and bn math sign', async function() { + if (!openpgp.util.getNodeCrypto()) { + this.skip(); + } + const bits = 1024; + const keyParams = await openpgp.crypto.generateParams(openpgp.enums.publicKey.rsa_sign, bits); + const n = keyParams[0].toUint8Array(); + const e = keyParams[1].toUint8Array(); + const d = keyParams[2].toUint8Array(); + const p = keyParams[3].toUint8Array(); + const q = keyParams[4].toUint8Array(); + const u = keyParams[5].toUint8Array(); + const message = await openpgp.crypto.random.getRandomBytes(64); + const hashName = 'sha256'; + const hash_algo = openpgp.enums.write(openpgp.enums.hash, hashName); + const hashed = await openpgp.crypto.hash.digest(hash_algo, message); + const signatureNode = await openpgp.crypto.publicKey.rsa.nodeSign(hash_algo, message, n, e, d, p, q, u); + const signatureBN = await openpgp.crypto.publicKey.rsa.bnSign(hash_algo, n, d, hashed); + expect(openpgp.util.Uint8Array_to_hex(signatureNode)).to.be.equal(openpgp.util.Uint8Array_to_hex(signatureBN)); + }); + + it('compare nodeCrypto and bn math verify', async function() { + if (!openpgp.util.getNodeCrypto()) { + this.skip(); + } + const bits = openpgp.util.getWebCrypto() ? 2048 : 1024; + const keyParams = await openpgp.crypto.generateParams(openpgp.enums.publicKey.rsa_sign, bits); + const n = keyParams[0].toUint8Array(); + const e = keyParams[1].toUint8Array(); + const d = keyParams[2].toUint8Array(); + const p = keyParams[3].toUint8Array(); + const q = keyParams[4].toUint8Array(); + const u = keyParams[5].toUint8Array(); + const message = await openpgp.crypto.random.getRandomBytes(64); + const hashName = 'sha256'; + const hash_algo = openpgp.enums.write(openpgp.enums.hash, hashName); + const hashed = await openpgp.crypto.hash.digest(hash_algo, message); + const signatureNode = await openpgp.crypto.publicKey.rsa.nodeSign(hash_algo, message, n, e, d, p, q, u); + const verifyNode = await openpgp.crypto.publicKey.rsa.nodeVerify(hash_algo, message, signatureNode, n, e); + const verifyBN = await openpgp.crypto.publicKey.rsa.bnVerify(hash_algo, signatureNode, n, e, hashed); + expect(verifyNode).to.be.true; + expect(verifyBN).to.be.true; }); }); diff --git a/test/crypto/validate.js b/test/crypto/validate.js index 46e61e16..9ba8fae0 100644 --- a/test/crypto/validate.js +++ b/test/crypto/validate.js @@ -1,4 +1,4 @@ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); const chai = require('chai'); const BN = require('bn.js'); @@ -74,281 +74,314 @@ vqBGKJzmO5q3cECw =X9kJ -----END PGP PRIVATE KEY BLOCK-----`; -function cloneKeyPacket(key) { - const keyPacket = new openpgp.SecretKeyPacket(); - keyPacket.read(key.keyPacket.write()); - return keyPacket; -} - -/* eslint-disable no-invalid-this */ -module.exports = () => { - describe('EdDSA parameter validation', function() { - let eddsaKey; - before(async () => { - eddsaKey = (await openpgp.generateKey({ curve: 'ed25519', userIds: [{ name: 'Test', email: 'test@test.com' }] })).key; - }); +describe('EdDSA parameter validation', function() { + let keyParams; + before(async () => { + keyParams = await openpgp.crypto.generateParams(openpgp.enums.publicKey.eddsa, null, 'ed25519'); + }); - it('EdDSA params should be valid', async function() { - await expect(eddsaKey.keyPacket.validate()).to.not.be.rejected; - }); + it('EdDSA params should be valid', async function() { + const { oid, Q, seed } = openpgp.crypto.publicKey.elliptic.eddsa.parseParams(keyParams); + const valid = await openpgp.crypto.publicKey.elliptic.eddsa.validateParams(oid, Q, seed); + expect(valid).to.be.true; + }); - it('detect invalid edDSA Q', async function() { - const eddsaKeyPacket = cloneKeyPacket(eddsaKey); - const Q = eddsaKeyPacket.publicParams.Q; - Q[0]++; - await expect(eddsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid'); + it('detect invalid edDSA Q', async function() { + const { oid, Q, seed } = openpgp.crypto.publicKey.elliptic.eddsa.parseParams(keyParams); - const infQ = new Uint8Array(Q.length); - eddsaKeyPacket.publicParams.Q = infQ; - await expect(eddsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid'); - }); - }); - describe('ECC curve validation', function() { - let eddsaKey; - let ecdhKey; - let ecdsaKey; - before(async () => { - eddsaKey = (await openpgp.generateKey({ curve: 'ed25519', userIds: [{ name: 'Test', email: 'test@test.com' }] })).key; - ecdhKey = eddsaKey.subKeys[0]; - ecdsaKey = (await openpgp.generateKey({ curve: 'p256', userIds: [{ name: 'Test', email: 'test@test.com' }] })).key; - }); + Q[0]++; + let valid = await openpgp.crypto.publicKey.elliptic.eddsa.validateParams(oid, Q, seed); + expect(valid).to.be.false; - it('EdDSA params are not valid for ECDH', async function() { - const { oid, Q } = eddsaKey.keyPacket.publicParams; - const { seed } = eddsaKey.keyPacket.privateParams; + const infQ = new Uint8Array(Q.length); + valid = await openpgp.crypto.publicKey.elliptic.eddsa.validateParams(oid, infQ, seed); + expect(valid).to.be.false; + }); +}); + +describe('ECC curve validation', function() { + it('EdDSA params are not valid for ECDH', async function() { + const keyParams = await openpgp.crypto.generateParams( + openpgp.enums.publicKey.eddsa, + null, + 'ed25519' + ); + const { oid, Q, seed } = openpgp.crypto.publicKey.elliptic.eddsa.parseParams(keyParams); + const valid = await openpgp.crypto.publicKey.elliptic.ecdh.validateParams(oid, Q, seed); + expect(valid).to.be.false; + }); - const ecdhKeyPacket = cloneKeyPacket(ecdhKey); - const ecdhOID = ecdhKeyPacket.publicParams.oid; + it('EdDSA params are not valid for EcDSA', async function() { + const keyParams = await openpgp.crypto.generateParams( + openpgp.enums.publicKey.eddsa, + null, + 'ed25519' + ); + const { oid, Q, seed } = openpgp.crypto.publicKey.elliptic.eddsa.parseParams(keyParams); + const valid = await openpgp.crypto.publicKey.elliptic.ecdsa.validateParams(oid, Q, seed); + expect(valid).to.be.false; + }); - ecdhKeyPacket.publicParams.oid = oid; - await expect(ecdhKeyPacket.validate()).to.be.rejectedWith('Key is invalid'); + it('x25519 params are not valid for EcDSA', async function() { + const keyParams = await openpgp.crypto.generateParams( + openpgp.enums.publicKey.ecdsa, + null, + 'curve25519' + ); + const { oid, Q, d } = openpgp.crypto.publicKey.elliptic.ecdsa.parseParams(keyParams); + const valid = await openpgp.crypto.publicKey.elliptic.ecdsa.validateParams(oid, Q, d); + expect(valid).to.be.false; + }); - ecdhKeyPacket.publicParams.oid = ecdhOID; - ecdhKeyPacket.publicParams.Q = Q; - ecdhKeyPacket.privateParams.d = seed; - await expect(ecdhKeyPacket.validate()).to.be.rejectedWith('Key is invalid'); - }); + it('EcDSA params are not valid for EdDSA', async function() { + const keyParams = await openpgp.crypto.generateParams( + openpgp.enums.publicKey.ecdsa, null, 'p256' + ); + const { oid, Q, d } = openpgp.crypto.publicKey.elliptic.ecdsa.parseParams(keyParams); + const valid = await openpgp.crypto.publicKey.elliptic.eddsa.validateParams(oid, Q, d); + expect(valid).to.be.false; + }); - it('EdDSA params are not valid for EcDSA', async function() { - const { oid, Q } = eddsaKey.keyPacket.publicParams; - const { seed } = eddsaKey.keyPacket.privateParams; + it('x25519 params are not valid for EdDSA', async function() { + const keyParams = await openpgp.crypto.generateParams( + openpgp.enums.publicKey.ecdsa, null, 'curve25519' + ); + const { oid, Q, d } = openpgp.crypto.publicKey.elliptic.ecdsa.parseParams(keyParams); + const valid = await openpgp.crypto.publicKey.elliptic.eddsa.validateParams(oid, Q, d); + expect(valid).to.be.false; + }); +}); - const ecdsaKeyPacket = cloneKeyPacket(ecdsaKey); - const ecdsaOID = ecdsaKeyPacket.publicParams.oid; - ecdsaKeyPacket.publicParams.oid = oid; - await expect(ecdsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid'); - ecdsaKeyPacket.publicParams.oid = ecdsaOID; - ecdsaKeyPacket.publicParams.Q = Q; - ecdsaKeyPacket.privateParams.d = seed; - await expect(ecdsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid'); +const curves = ['curve25519', 'p256', 'p384', 'p521', 'secp256k1', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1']; +curves.forEach(curve => { + describe(`ECC ${curve} parameter validation`, () => { + let keyParams; + before(async () => { + // we generate also ecdh params as ecdsa ones since we do not need the kdf params + keyParams = await openpgp.crypto.generateParams( + openpgp.enums.publicKey.ecdsa, null, curve + ); }); - it('ECDH x25519 params are not valid for EcDSA', async function() { - const { oid, Q } = ecdhKey.keyPacket.publicParams; - const { d } = ecdhKey.keyPacket.privateParams; + if (curve !== 'curve25519') { + it(`EcDSA ${curve} params should be valid`, async function() { + const { oid, Q, d } = openpgp.crypto.publicKey.elliptic.ecdsa.parseParams(keyParams); + const valid = await openpgp.crypto.publicKey.elliptic.ecdsa.validateParams(oid, Q, d); + expect(valid).to.be.true; + }); - const ecdsaKeyPacket = cloneKeyPacket(ecdsaKey); - ecdsaKeyPacket.publicParams.oid = oid; - ecdsaKeyPacket.publicParams.Q = Q; - ecdsaKeyPacket.privateParams.d = d; - await expect(ecdsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid'); - }); + it('detect invalid EcDSA Q', async function() { + const { oid, Q, d } = openpgp.crypto.publicKey.elliptic.ecdsa.parseParams(keyParams); - it('EcDSA params are not valid for EdDSA', async function() { - const { oid, Q } = ecdsaKey.keyPacket.publicParams; - const { d } = ecdsaKey.keyPacket.privateParams; + Q[16]++; + let valid = await openpgp.crypto.publicKey.elliptic.ecdsa.validateParams(oid, Q, d); + expect(valid).to.be.false; - const eddsaKeyPacket = cloneKeyPacket(eddsaKey); - const eddsaOID = eddsaKeyPacket.publicParams.oid; - eddsaKeyPacket.publicParams.oid = oid; - await expect(eddsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid'); + const infQ = new Uint8Array(Q.length); + valid = await openpgp.crypto.publicKey.elliptic.ecdsa.validateParams(oid, infQ, d); + expect(valid).to.be.false; + }); + } - eddsaKeyPacket.publicParams.oid = eddsaOID; - eddsaKeyPacket.publicParams.Q = Q; - eddsaKeyPacket.privateParams.seed = d; - await expect(eddsaKeyPacket.validate()).to.be.rejected; + it(`ECDH ${curve} params should be valid`, async function() { + const { oid, Q, d } = openpgp.crypto.publicKey.elliptic.ecdsa.parseParams(keyParams); + const valid = await openpgp.crypto.publicKey.elliptic.ecdh.validateParams(oid, Q, d); + expect(valid).to.be.true; }); - it('ECDH x25519 params are not valid for EdDSA', async function() { - const { oid, Q } = ecdhKey.keyPacket.publicParams; - const { d } = ecdhKey.keyPacket.privateParams; + it('detect invalid ECDH Q', async function() { + const { oid, Q, d } = openpgp.crypto.publicKey.elliptic.ecdsa.parseParams(keyParams); - const eddsaKeyPacket = cloneKeyPacket(eddsaKey); - const eddsaOID = eddsaKeyPacket.publicParams.oid; - eddsaKeyPacket.publicParams.oid = oid; - await expect(eddsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid'); + Q[16]++; + let valid = await openpgp.crypto.publicKey.elliptic.ecdh.validateParams(oid, Q, d); + expect(valid).to.be.false; - eddsaKeyPacket.publicParams.oid = eddsaOID; - eddsaKeyPacket.publicParams.Q = Q; - eddsaKeyPacket.privateParams.seed = d; - await expect(eddsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid'); + const infQ = new Uint8Array(Q.length); + valid = await openpgp.crypto.publicKey.elliptic.ecdh.validateParams(oid, infQ, d); + expect(valid).to.be.false; }); }); +}); - const curves = ['curve25519', 'p256', 'p384', 'p521', 'secp256k1', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1']; - curves.forEach(curve => { - describe(`ECC ${curve} parameter validation`, () => { - let ecdsaKey; - let ecdhKey; - before(async () => { - if (curve !== 'curve25519') { - ecdsaKey = (await openpgp.generateKey({ curve, userIds: [{ name: 'Test', email: 'test@test.com' }] })).key; - ecdhKey = ecdsaKey.subKeys[0]; - } else { - const eddsaKey = (await openpgp.generateKey({ curve: 'ed25519', userIds: [{ name: 'Test', email: 'test@test.com' }] })).key; - ecdhKey = eddsaKey.subKeys[0]; - } - }); - - it(`EcDSA ${curve} params should be valid`, async function() { - if (!ecdsaKey) { - this.skip(); - } - await expect(ecdsaKey.keyPacket.validate()).to.not.be.rejected; - }); +describe('RSA parameter validation', function() { + let keyParams; + before(async () => { + keyParams = await openpgp.crypto.generateParams(openpgp.enums.publicKey.rsa_sign, 2048); + }); - it(`ECDSA ${curve} - detect invalid Q`, async function() { - if (!ecdsaKey) { - this.skip(); - } - const keyPacket = cloneKeyPacket(ecdsaKey); - const Q = keyPacket.publicParams.Q; - Q[16]++; - await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); - const infQ = new Uint8Array(Q.length); - infQ[0] = 4; - keyPacket.publicParams.Q = infQ; - await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); - }); + it('generated RSA params are valid', async function() { + const n = keyParams[0].toUint8Array(); + const e = keyParams[1].toUint8Array(); + const d = keyParams[2].toUint8Array(); + const p = keyParams[3].toUint8Array(); + const q = keyParams[4].toUint8Array(); + const u = keyParams[5].toUint8Array(); + const valid = await openpgp.crypto.publicKey.rsa.validateParams(n, e, d, p, q, u); + expect(valid).to.be.true; + }); - it(`ECDH ${curve} params should be valid`, async function() { - await expect(ecdhKey.keyPacket.validate()).to.not.be.rejected; - }); + it('detect invalid RSA n', async function() { + const n = keyParams[0].toUint8Array(); + const e = keyParams[1].toUint8Array(); + const d = keyParams[2].toUint8Array(); + const p = keyParams[3].toUint8Array(); + const q = keyParams[4].toUint8Array(); + const u = keyParams[5].toUint8Array(); + + n[0]++; + const valid = await openpgp.crypto.publicKey.rsa.validateParams(n, e, d, p, q, u); + expect(valid).to.be.false; + }); - it(`ECDH ${curve} - detect invalid Q`, async function() { - const keyPacket = cloneKeyPacket(ecdhKey); - const Q = keyPacket.publicParams.Q; - Q[16]++; - await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); + it('detect invalid RSA e', async function() { + const n = keyParams[0].toUint8Array(); + const e = keyParams[1].toUint8Array(); + const d = keyParams[2].toUint8Array(); + const p = keyParams[3].toUint8Array(); + const q = keyParams[4].toUint8Array(); + const u = keyParams[5].toUint8Array(); + + e[0]++; + const valid = await openpgp.crypto.publicKey.rsa.validateParams(n, e, d, p, q, u); + expect(valid).to.be.false; + }); +}); - const infQ = new Uint8Array(Q.length); - keyPacket.publicParams.Q = infQ; - infQ[0] = 4; - await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); - }); - }); +describe('DSA parameter validation', function() { + let dsaKey; + before(async () => { + dsaKey = (await openpgp.key.readArmored(armoredDSAKey)).keys[0]; }); - describe('RSA parameter validation', function() { - let rsaKey; - before(async () => { - rsaKey = (await openpgp.generateKey({ type: 'rsa', rsaBits: 2048, userIds: [{ name: 'Test', email: 'test@test.com' }] })).key; - }); + it('DSA params should be valid', async function() { + const params = dsaKey.keyPacket.params; + const p = params[0].toUint8Array(); + const q = params[1].toUint8Array(); + const g = params[2].toUint8Array(); + const y = params[3].toUint8Array(); + const x = params[4].toUint8Array(); + const valid = await openpgp.crypto.publicKey.dsa.validateParams(p, q, g, y, x); + expect(valid).to.be.true; + }); - it('generated RSA params are valid', async function() { - await expect(rsaKey.keyPacket.validate()).to.not.be.rejected; - }); + it('detect invalid DSA p', async function() { + const params = dsaKey.keyPacket.params; + const p = params[0].toUint8Array(); + const q = params[1].toUint8Array(); + const g = params[2].toUint8Array(); + const y = params[3].toUint8Array(); + const x = params[4].toUint8Array(); - it('detect invalid RSA n', async function() { - const keyPacket = cloneKeyPacket(rsaKey); - const n = keyPacket.publicParams.n; - n[0]++; - await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); - }); + p[0]++; + const valid = await openpgp.crypto.publicKey.dsa.validateParams(p, q, g, y, x); - it('detect invalid RSA e', async function() { - const keyPacket = cloneKeyPacket(rsaKey); - const e = keyPacket.publicParams.e; - e[0]++; - await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); - }); + expect(valid).to.be.false; }); - describe('DSA parameter validation', function() { - let dsaKey; - before(async () => { - dsaKey = await openpgp.readArmoredKey(armoredDSAKey); - }); - - it('DSA params should be valid', async function() { - await expect(dsaKey.keyPacket.validate()).to.not.be.rejected; - }); + it('detect invalid DSA y', async function() { + const params = dsaKey.keyPacket.params; + const p = params[0].toUint8Array(); + const q = params[1].toUint8Array(); + const g = params[2].toUint8Array(); + const y = params[3].toUint8Array(); + const x = params[4].toUint8Array(); - it('detect invalid DSA p', async function() { - const keyPacket = cloneKeyPacket(dsaKey); - const p = keyPacket.publicParams.p; - p[0]++; - await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); - }); + y[0]++; + const valid = await openpgp.crypto.publicKey.dsa.validateParams(p, q, g, y, x); - it('detect invalid DSA y', async function() { - const keyPacket = cloneKeyPacket(dsaKey); - const y = keyPacket.publicParams.y; + expect(valid).to.be.false; + }); - y[0]++; - await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); - }); + it('detect invalid DSA g', async function() { + const params = dsaKey.keyPacket.params; + const p = params[0].toUint8Array(); + const q = params[1].toUint8Array(); + const g = params[2].toUint8Array(); + const y = params[3].toUint8Array(); + const x = params[4].toUint8Array(); + + g[0]++; + let valid = await openpgp.crypto.publicKey.dsa.validateParams(p, q, g, y, x); + expect(valid).to.be.false; + + const gOne = new Uint8Array([1]); + valid = await openpgp.crypto.publicKey.dsa.validateParams(p, q, gOne, y, x); + expect(valid).to.be.false; + }); +}); - it('detect invalid DSA g', async function() { - const keyPacket = cloneKeyPacket(dsaKey); - const g = keyPacket.publicParams.g; +describe('ElGamal parameter validation', function() { + let egKey; + before(async () => { + egKey = (await openpgp.key.readArmored(armoredElGamalKey)).keys[0].subKeys[0]; + }); - g[0]++; - await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); + it('params should be valid', async function() { + const params = egKey.keyPacket.params; + const p = params[0].toUint8Array(); + const g = params[1].toUint8Array(); + const y = params[2].toUint8Array(); + const x = params[3].toUint8Array(); - keyPacket.publicParams.g = new Uint8Array([1]); - await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); - }); + const valid = await openpgp.crypto.publicKey.elgamal.validateParams(p, g, y, x); + expect(valid).to.be.true; }); - describe('ElGamal parameter validation', function() { - let egKey; - before(async () => { - egKey = (await openpgp.readArmoredKey(armoredElGamalKey)).subKeys[0]; - }); + it('detect invalid p', async function() { + const params = egKey.keyPacket.params; + const p = params[0].toUint8Array(); + const g = params[1].toUint8Array(); + const y = params[2].toUint8Array(); + const x = params[3].toUint8Array(); + p[0]++; + const valid = await openpgp.crypto.publicKey.elgamal.validateParams(p, g, y, x); - it('params should be valid', async function() { - await expect(egKey.keyPacket.validate()).to.not.be.rejected; - }); + expect(valid).to.be.false; + }); - it('detect invalid p', async function() { - const keyPacket = cloneKeyPacket(egKey); - const p = keyPacket.publicParams.p; - p[0]++; - await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); - }); + it('detect invalid y', async function() { + const params = egKey.keyPacket.params; + const p = params[0].toUint8Array(); + const g = params[1].toUint8Array(); + const y = params[2].toUint8Array(); + const x = params[3].toUint8Array(); - it('detect invalid y', async function() { - const keyPacket = cloneKeyPacket(egKey); - const y = keyPacket.publicParams.y; - y[0]++; - await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); - }); + y[0]++; + const valid = await openpgp.crypto.publicKey.elgamal.validateParams(p, g, y, x); + + expect(valid).to.be.false; + }); - it('detect invalid g', async function() { - const keyPacket = cloneKeyPacket(egKey); - const g = keyPacket.publicParams.g; + it('detect invalid g', async function() { + const params = egKey.keyPacket.params; + const p = params[0].toUint8Array(); + const g = params[1].toUint8Array(); + const y = params[2].toUint8Array(); + const x = params[3].toUint8Array(); - g[0]++; - await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); + g[0]++; + let valid = await openpgp.crypto.publicKey.elgamal.validateParams(p, g, y, x); + expect(valid).to.be.false; - keyPacket.publicParams.g = new Uint8Array([1]); - await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); - }); + const gOne = new Uint8Array([1]); + valid = await openpgp.crypto.publicKey.elgamal.validateParams(p, gOne, y, x); + expect(valid).to.be.false; + }); - it('detect g with small order', async function() { - const keyPacket = cloneKeyPacket(egKey); - const p = keyPacket.publicParams.p; - const g = keyPacket.publicParams.g; - - const pBN = new BN(p); - const gModP = new BN(g).toRed(new BN.red(pBN)); - // g**(p-1)/2 has order 2 - const gOrd2 = gModP.redPow(pBN.subn(1).shrn(1)); - keyPacket.publicParams.g = gOrd2.toArrayLike(Uint8Array, 'be'); - await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid'); - }); + it('detect g with small order', async function() { + const params = egKey.keyPacket.params; + const p = params[0].toUint8Array(); + const g = params[1].toUint8Array(); + const y = params[2].toUint8Array(); + const x = params[3].toUint8Array(); + + const pBN = new BN(p); + const gModP = new BN(g).toRed(new BN.red(pBN)); + // g**(p-1)/2 has order 2 + const gOrd2 = gModP.redPow(pBN.subn(1).shrn(1)); + const valid = await openpgp.crypto.publicKey.elgamal.validateParams(p, gOrd2.toArrayLike(Uint8Array, 'be'), y, x); + expect(valid).to.be.false; }); -}; +}); diff --git a/test/general/armor.js b/test/general/armor.js index d3f77133..d91768be 100644 --- a/test/general/armor.js +++ b/test/general/armor.js @@ -1,10 +1,10 @@ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); const chai = require('chai'); const { expect } = chai; -module.exports = () => describe("ASCII armor", function() { +describe("ASCII armor", function() { function getArmor(headers, signatureHeaders) { return ['-----BEGIN PGP SIGNED MESSAGE-----'] @@ -28,55 +28,55 @@ module.exports = () => describe("ASCII armor", function() { it('Parse cleartext signed message', async function () { let msg = getArmor(['Hash: SHA1']); - msg = await openpgp.readArmoredCleartextMessage(msg); - expect(msg).to.be.an.instanceof(openpgp.CleartextMessage); + msg = await openpgp.cleartext.readArmored(msg); + expect(msg).to.be.an.instanceof(openpgp.cleartext.CleartextMessage); }); it('Exception if mismatch in armor header and signature', async function () { let msg = getArmor(['Hash: SHA256']); - msg = openpgp.readArmoredCleartextMessage(msg); + msg = openpgp.cleartext.readArmored(msg); await expect(msg).to.be.rejectedWith(Error, /Hash algorithm mismatch in armor header and signature/); }); it('Exception if no header and non-MD5 signature', async function () { let msg = getArmor(null); - msg = openpgp.readArmoredCleartextMessage(msg); + msg = openpgp.cleartext.readArmored(msg); await expect(msg).to.be.rejectedWith(Error, /If no "Hash" header in cleartext signed message, then only MD5 signatures allowed/); }); it('Exception if unknown hash algorithm', async function () { let msg = getArmor(['Hash: LAV750']); - msg = openpgp.readArmoredCleartextMessage(msg); + msg = openpgp.cleartext.readArmored(msg); await expect(msg).to.be.rejectedWith(Error, /Unknown hash algorithm in armor header/); }); it('Multiple hash values', async function () { let msg = getArmor(['Hash: SHA1, SHA256']); - msg = await openpgp.readArmoredCleartextMessage(msg); - expect(msg).to.be.an.instanceof(openpgp.CleartextMessage); + msg = await openpgp.cleartext.readArmored(msg); + expect(msg).to.be.an.instanceof(openpgp.cleartext.CleartextMessage); }); it('Multiple hash header lines', async function () { let msg = getArmor(['Hash: SHA1', 'Hash: SHA256']); - msg = await openpgp.readArmoredCleartextMessage(msg); - expect(msg).to.be.an.instanceof(openpgp.CleartextMessage); + msg = await openpgp.cleartext.readArmored(msg); + expect(msg).to.be.an.instanceof(openpgp.cleartext.CleartextMessage); }); it('Non-hash header line throws exception', async function () { let msg = getArmor(['Hash: SHA1', 'Comment: could be anything']); - msg = openpgp.readArmoredCleartextMessage(msg); + msg = openpgp.cleartext.readArmored(msg); await expect(msg).to.be.rejectedWith(Error, /Only "Hash" header allowed in cleartext signed message/); }); it('Multiple wrong hash values', async function () { let msg = getArmor(['Hash: SHA512, SHA256']); - msg = openpgp.readArmoredCleartextMessage(msg); + msg = openpgp.cleartext.readArmored(msg); await expect(msg).to.be.rejectedWith(Error, /Hash algorithm mismatch in armor header and signature/); }); it('Multiple wrong hash values', async function () { let msg = getArmor(['Hash: SHA512, SHA256']); - msg = openpgp.readArmoredCleartextMessage(msg); + msg = openpgp.cleartext.readArmored(msg); await expect(msg).to.be.rejectedWith(Error, /Hash algorithm mismatch in armor header and signature/); }); @@ -96,33 +96,33 @@ module.exports = () => describe("ASCII armor", function() { '=e/eA', '-----END PGP SIGNATURE-----'].join('\n'); - msg = await openpgp.readArmoredCleartextMessage(msg); - expect(msg).to.be.an.instanceof(openpgp.CleartextMessage); + msg = await openpgp.cleartext.readArmored(msg); + expect(msg).to.be.an.instanceof(openpgp.cleartext.CleartextMessage); }); it('Exception if improperly formatted armor header - plaintext section', async function () { let msg = getArmor(['Hash:SHA256']); - msg = openpgp.readArmoredCleartextMessage(msg); + msg = openpgp.cleartext.readArmored(msg); await expect(msg).to.be.rejectedWith(Error, /Improperly formatted armor header/); msg = getArmor(['Ha sh: SHA256']); - msg = openpgp.readArmoredCleartextMessage(msg); + msg = openpgp.cleartext.readArmored(msg); await expect(msg).to.be.rejectedWith(Error, /Only "Hash" header allowed in cleartext signed message/); msg = getArmor(['Hash SHA256']); - msg = openpgp.readArmoredCleartextMessage(msg); + msg = openpgp.cleartext.readArmored(msg); await expect(msg).to.be.rejectedWith(Error, /Improperly formatted armor header/); }); it('Exception if improperly formatted armor header - signature section', async function () { await Promise.all(['Space : trailing', 'Space :switched', ': empty', 'none', 'Space:missing'].map(async function (invalidHeader) { - await expect(openpgp.readArmoredCleartextMessage(getArmor(['Hash: SHA1'], [invalidHeader]))).to.be.rejectedWith(Error, /Improperly formatted armor header/); + await expect(openpgp.cleartext.readArmored(getArmor(['Hash: SHA1'], [invalidHeader]))).to.be.rejectedWith(Error, /Improperly formatted armor header/); })); }); it('Ignore unknown armor header - signature section', async function () { const validHeaders = ['Version: BCPG C# v1.7.4114.6375', 'Independent Reserve Pty. Ltd. 2017: 1.0.0.0']; - expect(await openpgp.readArmoredCleartextMessage(getArmor(['Hash: SHA1'], validHeaders))).to.be.an.instanceof(openpgp.CleartextMessage); + expect(await openpgp.cleartext.readArmored(getArmor(['Hash: SHA1'], validHeaders))).to.be.an.instanceof(openpgp.cleartext.CleartextMessage); await Promise.all(['A: Hello', 'Ab: 1.2.3', 'Abcd: #!/yah', 'Acd 123 5.6.$.8: Hello', '_: Hello', '*: Hello', '* & ## ?? ()(): Hello', '( ): Weird'].map(async function (validHeader) { - expect(await openpgp.readArmoredCleartextMessage(getArmor(['Hash: SHA1'], [validHeader]))).to.be.an.instanceof(openpgp.CleartextMessage); + expect(await openpgp.cleartext.readArmored(getArmor(['Hash: SHA1'], [validHeader]))).to.be.an.instanceof(openpgp.cleartext.CleartextMessage); })); }); @@ -141,7 +141,7 @@ module.exports = () => describe("ASCII armor", function() { '=e/eA', '-----END PGP SIGNNATURE-----'].join('\n'); - msg = openpgp.readArmoredCleartextMessage(msg); + msg = openpgp.cleartext.readArmored(msg); await expect(msg).to.be.rejectedWith(Error, /Unknown ASCII armor type/); }); @@ -167,14 +167,18 @@ module.exports = () => describe("ASCII armor", function() { '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); // try with default config - await expect(openpgp.readArmoredKey(privKey)).to.be.rejectedWith(/Ascii armor integrity check on message failed/); + const result_1 = await openpgp.key.readArmored(privKey); + expect(result_1.err).to.exist; + expect(result_1.err[0].message).to.match(/Ascii armor integrity check on message failed/); // try opposite config - openpgp.config.checksumRequired = !openpgp.config.checksumRequired; - await expect(openpgp.readArmoredKey(privKey)).to.be.rejectedWith(/Ascii armor integrity check on message failed/); + openpgp.config.checksum_required = !openpgp.config.checksum_required; + const result_2 = await openpgp.key.readArmored(privKey); + expect(result_2.err).to.exist; + expect(result_2.err[0].message).to.match(/Ascii armor integrity check on message failed/); // back to default - openpgp.config.checksumRequired = !openpgp.config.checksumRequired; + openpgp.config.checksum_required = !openpgp.config.checksum_required; }); it('Armor checksum validation - valid', async function () { @@ -199,14 +203,16 @@ module.exports = () => describe("ASCII armor", function() { '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); // try with default config - await openpgp.readArmoredKey(privKey); + const result_1 = await openpgp.key.readArmored(privKey); + expect(result_1.err).to.not.exist; // try opposite config - openpgp.config.checksumRequired = !openpgp.config.checksumRequired; - await openpgp.readArmoredKey(privKey); + openpgp.config.checksum_required = !openpgp.config.checksum_required; + const result_2 = await openpgp.key.readArmored(privKey); + expect(result_2.err).to.not.exist; // back to default - openpgp.config.checksumRequired = !openpgp.config.checksumRequired; + openpgp.config.checksum_required = !openpgp.config.checksum_required; }); it('Armor checksum validation - missing', async function () { @@ -230,22 +236,26 @@ module.exports = () => describe("ASCII armor", function() { '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); // try with default config - if (openpgp.config.checksumRequired) { - await expect(openpgp.readArmoredKey(privKeyNoCheckSum)).to.be.rejectedWith(/Ascii armor integrity check on message failed/); + const result_1 = await openpgp.key.readArmored(privKeyNoCheckSum); + if(openpgp.config.checksum_required) { + expect(result_1.err).to.exist; + expect(result_1.err[0].message).to.match(/Ascii armor integrity check on message failed/); } else { - await openpgp.readArmoredKey(privKeyNoCheckSum); + expect(result_1.err).to.not.exist; } // try opposite config - openpgp.config.checksumRequired = !openpgp.config.checksumRequired; - if (openpgp.config.checksumRequired) { - await expect(openpgp.readArmoredKey(privKeyNoCheckSum)).to.be.rejectedWith(/Ascii armor integrity check on message failed/); + openpgp.config.checksum_required = !openpgp.config.checksum_required; + const result_2 = await openpgp.key.readArmored(privKeyNoCheckSum); + if(openpgp.config.checksum_required) { + expect(result_2.err).to.exist; + expect(result_2.err[0].message).to.match(/Ascii armor integrity check on message failed/); } else { - await openpgp.readArmoredKey(privKeyNoCheckSum); + expect(result_2.err).to.not.exist; } // back to default - openpgp.config.checksumRequired = !openpgp.config.checksumRequired; + openpgp.config.checksum_required = !openpgp.config.checksum_required; }); it('Armor checksum validation - missing - trailing newline', async function () { @@ -270,22 +280,26 @@ module.exports = () => describe("ASCII armor", function() { ''].join('\n'); // try with default config - if (openpgp.config.checksumRequired) { - await expect(openpgp.readArmoredKey(privKeyNoCheckSumWithTrailingNewline)).to.be.rejectedWith(/Ascii armor integrity check on message failed/); + const result_1 = await openpgp.key.readArmored(privKeyNoCheckSumWithTrailingNewline); + if(openpgp.config.checksum_required) { + expect(result_1.err).to.exist; + expect(result_1.err[0].message).to.match(/Ascii armor integrity check on message failed/); } else { - await openpgp.readArmoredKey(privKeyNoCheckSumWithTrailingNewline); + expect(result_1.err).to.not.exist; } // try opposite config - openpgp.config.checksumRequired = !openpgp.config.checksumRequired; - if (openpgp.config.checksumRequired) { - await expect(openpgp.readArmoredKey(privKeyNoCheckSumWithTrailingNewline)).to.be.rejectedWith(/Ascii armor integrity check on message failed/); + openpgp.config.checksum_required = !openpgp.config.checksum_required; + const result_2 = await openpgp.key.readArmored(privKeyNoCheckSumWithTrailingNewline); + if(openpgp.config.checksum_required) { + expect(result_2.err).to.exist; + expect(result_2.err[0].message).to.match(/Ascii armor integrity check on message failed/); } else { - await openpgp.readArmoredKey(privKeyNoCheckSumWithTrailingNewline); + expect(result_2.err).to.not.exist; } // back to default - openpgp.config.checksumRequired = !openpgp.config.checksumRequired; + openpgp.config.checksum_required = !openpgp.config.checksum_required; }); it('Accept header with trailing whitespace', async function () { @@ -310,13 +324,14 @@ module.exports = () => describe("ASCII armor", function() { '-----END PGP PRIVATE KEY BLOCK-----', ''].join('\t \r\n'); - const result = await openpgp.readArmoredKey(privKey); - expect(result).to.be.an.instanceof(openpgp.Key); + const result = await openpgp.key.readArmored(privKey); + expect(result.err).to.not.exist; + expect(result.keys[0]).to.be.an.instanceof(openpgp.key.Key); }); it('Do not filter blank lines after header', async function () { let msg = getArmor(['Hash: SHA1', '']); - msg = await openpgp.readArmoredCleartextMessage(msg); + msg = await openpgp.cleartext.readArmored(msg); expect(msg.text).to.equal('\r\nsign this'); }); @@ -377,15 +392,15 @@ NJCB6+LWtabSoVIjNVgKwyKqyTLaESNwC2ogZwkdE8qPGiDFEHo4Gg9zuRof -----END PGP PUBLIC KEY BLOCK----- `; - const { type, data } = await openpgp.unarmor(pubKey); - const armor = await openpgp.stream.readToEnd(openpgp.armor(type, data)); + const { type, data } = await openpgp.armor.decode(pubKey); + const armor = await openpgp.stream.readToEnd(openpgp.armor.encode(type, data)); expect( armor - .replace(/^(Version|Comment): .*$\n/mg, '') + .replace(/^(Version|Comment): .*$\r\n/mg, '') ).to.equal( pubKey .replace('\n=', '=') - .replace(/\n\r/g, '\n') + .replace(/\n/g, '\r\n') ); }); diff --git a/test/general/biginteger.js b/test/general/biginteger.js deleted file mode 100644 index b4fd1f90..00000000 --- a/test/general/biginteger.js +++ /dev/null @@ -1,162 +0,0 @@ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); -const random = require('../../src/crypto/random'); -const util = require('../../src/util'); - -const BN = require('bn.js'); -const chai = require('chai'); -chai.use(require('chai-as-promised')); - -const expect = chai.expect; -let BigInteger; - -async function getRandomBN(min, max) { - if (max.cmp(min) <= 0) { - throw new Error('Illegal parameter value: max <= min'); - } - - const modulus = max.sub(min); - const bytes = modulus.byteLength(); - const r = new BN(await random.getRandomBytes(bytes + 8)); - return r.mod(modulus).add(min); -} - -module.exports = () => describe('BigInteger interface', function() { - before(async () => { - BigInteger = await util.getBigInteger(); - }); - - it('constructor throws on undefined input', function() { - expect(() => new BigInteger()).to.throw('Invalid BigInteger input'); - }); - - - it('constructor supports strings', function() { - const input = '417653931840771530406225971293556769925351769207235721650257629558293828796031115397206059067934284452829611906818956352854418342467914729341523414945427019410284762464062112274326172407819051167058569790660930309496043254270888417520676082271432948852231332576271876251597199882908964994070268531832274431027'; - const got = new BigInteger(input); - const expected = new BN(input); - expect(got.toString()).to.equal(expected.toString()); - }); - - it('constructor supports Uint8Arrays', function() { - const expected = new BN('417653931840771530406225971293556769925351769207235721650257629558293828796031115397206059067934284452829611906818956352854418342467914729341523414945427019410284762464062112274326172407819051167058569790660930309496043254270888417520676082271432948852231332576271876251597199882908964994070268531832274431027'); - const input = expected.toArrayLike(Uint8Array); - const got = new BigInteger(input); - expect(got.toString()).to.equal(expected.toString()); - }); - - it('conditional operators are correct', function() { - const a = new BigInteger(12); - const b = new BigInteger(34); - - expect(a.equal(a)).to.be.true; - expect(a.equal(b)).to.be.false; - expect(a.gt(a) === a.lt(a)).to.be.true; - expect(a.gt(b) === a.lt(b)).to.be.false; - expect(a.gte(a) === a.lte(a)).to.be.true; - - const zero = new BigInteger(0); - const one = new BigInteger(1); - expect(zero.isZero()).to.be.true; - expect(one.isZero()).to.be.false; - - expect(one.isOne()).to.be.true; - expect(zero.isOne()).to.be.false; - - expect(zero.isEven()).to.be.true; - expect(one.isEven()).to.be.false; - - expect(zero.isNegative()).to.be.false; - expect(zero.dec().isNegative()).to.be.true; - }); - - it('bitLength is correct', function() { - const n = new BigInteger(127); - let expected = 7; - expect(n.bitLength() === expected).to.be.true; - expect(n.inc().bitLength() === (++expected)).to.be.true; - }); - - it('byteLength is correct', function() { - const n = new BigInteger(65535); - let expected = 2; - expect(n.byteLength() === expected).to.be.true; - expect(n.inc().byteLength() === (++expected)).to.be.true; - }); - - it('toUint8Array is correct', function() { - const nString = '417653931840771530406225971293556769925351769207235721650257629558293828796031115397206059067934284452829611906818956352854418342467914729341523414945427019410284762464062112274326172407819051167058569790660930309496043254270888417520676082271432948852231332576271876251597199882908964994070268531832274431027'; - const n = new BigInteger(nString); - const paddedSize = Number(n.byteLength()) + 1; - // big endian, unpadded - let expected = new BN(nString).toArrayLike(Uint8Array); - expect(n.toUint8Array()).to.deep.equal(expected); - // big endian, padded - expected = new BN(nString).toArrayLike(Uint8Array, 'be', paddedSize); - expect(n.toUint8Array('be', paddedSize)).to.deep.equal(expected); - // little endian, unpadded - expected = new BN(nString).toArrayLike(Uint8Array, 'le'); - expect(n.toUint8Array('le')).to.deep.equal(expected); - //little endian, padded - expected = new BN(nString).toArrayLike(Uint8Array, 'le', paddedSize); - expect(n.toUint8Array('le', paddedSize)).to.deep.equal(expected); - }); - - it('binary operators are consistent', function() { - const a = new BigInteger(12); - const b = new BigInteger(34); - const ops = ['add', 'sub', 'mul', 'mod', 'leftShift', 'rightShift']; - ops.forEach(op => { - const iop = `i${op}`; - expect(a[op](b).equal(a[iop](b))).to.be.true; - }); - }); - - it('unary operators are consistent', function() { - const a = new BigInteger(12); - const one = new BigInteger(1); - expect(a.sub(one).equal(a.dec())).to.be.true; - expect(a.add(one).equal(a.inc())).to.be.true; - }); - - it('modExp is correct (large values)', function() { - const stringX = '417653931840771530406225971293556769925351769207235721650257629558293828796031115397206059067934284452829611906818956352854418342467914729341523414945427019410284762464062112274326172407819051167058569790660930309496043254270888417520676082271432948852231332576271876251597199882908964994070268531832274431027'; - const stringE = '21139356010872569239159922781526379521587348169074209285187910481667533072168468011617194695181255483288792585413365359733692097084373249198758148704369207793873998901870577262254971784191473102265830193058813215898765238784670469696574407580179153118937858890572095234316482449291777882525949871374961971753'; - const stringN = '129189808515414783602892982235788912674846062846614219472827821758734760420002631653235573915244294540972376140705505703576175711417114803419704967903726436285518767606681184247119430411311152556442947708732584954518890222684529678365388350886907287414896703685680210648760841628375425909680236584021041565183'; - const x = new BigInteger(stringX); - const e = new BigInteger(stringE); - const n = new BigInteger(stringN); - - const got = x.modExp(e, n); - const expected = new BN(stringX).toRed(BN.red(new BN(stringN))).redPow(new BN(stringE)); - // different formats, it's easier to compare strings - expect(got.toString() === expected.toString()).to.be.true; - }); - - it('gcd is correct', async function() { - const aBN = await getRandomBN(new BN(2), new BN(200)); - const bBN = await getRandomBN(new BN(2), new BN(200)); - if (aBN.isEven()) aBN.iaddn(1); - const a = new BigInteger(aBN.toString()); - const b = new BigInteger(bBN.toString()); - const expected = aBN.gcd(bBN); - expect(a.gcd(b).toString()).to.equal(expected.toString()); - }); - - it('modular inversion is correct', async function() { - const moduloBN = new BN(229); // this is a prime - const baseBN = await getRandomBN(new BN(2), moduloBN); - const a = new BigInteger(baseBN.toString()); - const n = new BigInteger(moduloBN.toString()); - const expected = baseBN.invm(moduloBN); - expect(a.modInv(n).toString()).to.equal(expected.toString()); - expect(() => a.mul(n).modInv(n)).to.throw('Inverse does not exist'); - }); - - it('getBit is correct', async function() { - const i = 5; - const nBN = await getRandomBN(new BN(2), new BN(200)); - const n = new BigInteger(nBN.toString()); - const expected = nBN.testn(5) ? 1 : 0; - expect(n.getBit(i) === expected).to.be.true; - }); -}); diff --git a/test/general/brainpool.js b/test/general/brainpool.js index 5f7e0856..7b6f5bef 100644 --- a/test/general/brainpool.js +++ b/test/general/brainpool.js @@ -1,7 +1,6 @@ /* globals tryTests: true */ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); -const util = require('../../src/util'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); const chai = require('chai'); chai.use(require('chai-as-promised')); @@ -9,9 +8,9 @@ const input = require('./testInputs.js'); const expect = chai.expect; -module.exports = () => (openpgp.config.ci ? describe.skip : describe)('Brainpool Cryptography @lightweight', function () { +(openpgp.config.ci ? describe.skip : describe)('Brainpool Cryptography @lightweight', function () { //only x25519 crypto is fully functional in lightbuild - if (!openpgp.config.useIndutnyElliptic && !util.getNodeCrypto()) { + if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) { before(function() { this.skip(); }); @@ -173,21 +172,26 @@ EJ4QcD/oQ6x1M/8X/iKQCtxZP8RnlrbH7ExkNON5s5g= if (data[name].pub_key) { return data[name].pub_key; } - const pub = await openpgp.readArmoredKey(data[name].pub); - expect(pub.getKeyId().toHex()).to.equal(data[name].id); - data[name].pub_key = pub; - return pub; + const pub = await openpgp.key.readArmored(data[name].pub); + expect(pub).to.exist; + expect(pub.err).to.not.exist; + expect(pub.keys).to.have.length(1); + expect(pub.keys[0].getKeyId().toHex()).to.equal(data[name].id); + data[name].pub_key = pub.keys[0]; + return data[name].pub_key; } async function load_priv_key(name) { if (data[name].priv_key) { return data[name].priv_key; } - const pk = await openpgp.readArmoredKey(data[name].priv); + const pk = await openpgp.key.readArmored(data[name].priv); expect(pk).to.exist; - expect(pk.getKeyId().toHex()).to.equal(data[name].id); - await pk.decrypt(data[name].pass); - data[name].priv_key = pk; - return pk; + expect(pk.err).to.not.exist; + expect(pk.keys).to.have.length(1); + expect(pk.keys[0].getKeyId().toHex()).to.equal(data[name].id); + expect(await pk.keys[0].decrypt(data[name].pass)).to.be.true; + data[name].priv_key = pk.keys[0]; + return data[name].priv_key; } it('Load public key', async function () { await load_pub_key('romeo'); @@ -200,7 +204,7 @@ EJ4QcD/oQ6x1M/8X/iKQCtxZP8RnlrbH7ExkNON5s5g= }); it('Verify clear signed message', async function () { const pub = await load_pub_key('juliet'); - const msg = await openpgp.readArmoredCleartextMessage(data.juliet.message_signed); + const msg = await openpgp.cleartext.readArmored(data.juliet.message_signed); return openpgp.verify({publicKeys: [pub], message: msg}).then(function(result) { expect(result).to.exist; expect(result.data).to.equal(data.juliet.message); @@ -210,9 +214,9 @@ EJ4QcD/oQ6x1M/8X/iKQCtxZP8RnlrbH7ExkNON5s5g= }); it('Sign message', async function () { const romeoPrivate = await load_priv_key('romeo'); - const signed = await openpgp.sign({privateKeys: [romeoPrivate], message: openpgp.CleartextMessage.fromText(data.romeo.message)}); + const signed = await openpgp.sign({privateKeys: [romeoPrivate], message: openpgp.cleartext.fromText(data.romeo.message)}); const romeoPublic = await load_pub_key('romeo'); - const msg = await openpgp.readArmoredCleartextMessage(signed); + const msg = await openpgp.cleartext.readArmored(signed.data); const result = await openpgp.verify({publicKeys: [romeoPublic], message: msg}); expect(result).to.exist; @@ -223,7 +227,7 @@ EJ4QcD/oQ6x1M/8X/iKQCtxZP8RnlrbH7ExkNON5s5g= it('Decrypt and verify message', async function () { const juliet = await load_pub_key('juliet'); const romeo = await load_priv_key('romeo'); - const msg = await openpgp.readArmoredMessage(data.romeo.message_encrypted); + const msg = await openpgp.message.readArmored(data.romeo.message_encrypted); const result = await openpgp.decrypt({ privateKeys: romeo, publicKeys: [juliet], message: msg }); expect(result).to.exist; @@ -234,7 +238,7 @@ EJ4QcD/oQ6x1M/8X/iKQCtxZP8RnlrbH7ExkNON5s5g= it('Decrypt and verify message with leading zero in hash', async function () { const juliet = await load_priv_key('juliet'); const romeo = await load_pub_key('romeo'); - const msg = await openpgp.readArmoredMessage(data.romeo.message_encrypted_with_leading_zero_in_hash); + const msg = await openpgp.message.readArmored(data.romeo.message_encrypted_with_leading_zero_in_hash); const result = await openpgp.decrypt({privateKeys: juliet, publicKeys: [romeo], message: msg}); expect(result).to.exist; @@ -243,14 +247,14 @@ EJ4QcD/oQ6x1M/8X/iKQCtxZP8RnlrbH7ExkNON5s5g= expect(result.signatures[0].valid).to.be.true; }); it('Decrypt and verify message with leading zero in hash signed with old elliptic algorithm', async function () { - //this test would not work with nodeCrypto, since message is signed with leading zero stripped from the hash - const useNative = openpgp.config.useNative; - openpgp.config.useNative = false; + //this test would not work with nodeCrypto, since message is signed with leading zero stripped from the hash + const use_native = openpgp.config.use_native; + openpgp.config.use_native = false; const juliet = await load_priv_key('juliet'); const romeo = await load_pub_key('romeo'); - const msg = await openpgp.readArmoredMessage(data.romeo. message_encrypted_with_leading_zero_in_hash_signed_by_elliptic_with_old_implementation); + const msg = await openpgp.message.readArmored(data.romeo. message_encrypted_with_leading_zero_in_hash_signed_by_elliptic_with_old_implementation); const result = await openpgp.decrypt({privateKeys: juliet, publicKeys: [romeo], message: msg}); - openpgp.config.useNative = useNative; + openpgp.config.use_native = use_native; expect(result).to.exist; expect(result.data).to.equal(data.romeo.message_with_leading_zero_in_hash_old_elliptic_implementation); expect(result.signatures).to.have.length(1); @@ -260,9 +264,9 @@ EJ4QcD/oQ6x1M/8X/iKQCtxZP8RnlrbH7ExkNON5s5g= it('Encrypt and sign message', async function () { const romeoPrivate = await load_priv_key('romeo'); const julietPublic = await load_pub_key('juliet'); - const encrypted = await openpgp.encrypt({publicKeys: [julietPublic], privateKeys: [romeoPrivate], message: openpgp.Message.fromText(data.romeo.message)}); + const encrypted = await openpgp.encrypt({publicKeys: [julietPublic], privateKeys: [romeoPrivate], message: openpgp.message.fromText(data.romeo.message)}); - const message = await openpgp.readArmoredMessage(encrypted); + const message = await openpgp.message.readArmored(encrypted.data); const romeoPublic = await load_pub_key('romeo'); const julietPrivate = await load_priv_key('juliet'); const result = await openpgp.decrypt({privateKeys: julietPrivate, publicKeys: [romeoPublic], message: message}); @@ -272,10 +276,6 @@ EJ4QcD/oQ6x1M/8X/iKQCtxZP8RnlrbH7ExkNON5s5g= expect(result.signatures).to.have.length(1); expect(result.signatures[0].valid).to.be.true; }); - - tryTests('Brainpool Omnibus Tests @lightweight', omnibus, { - if: openpgp.config.useIndutnyElliptic || util.getNodeCrypto() - }); }); function omnibus() { @@ -295,9 +295,9 @@ function omnibus() { return Promise.all([ // Signing message openpgp.sign( - { message: openpgp.CleartextMessage.fromText(testData), privateKeys: hi } + { message: openpgp.cleartext.fromText(testData), privateKeys: hi } ).then(async signed => { - const msg = await openpgp.readArmoredCleartextMessage(signed); + const msg = await openpgp.cleartext.readArmored(signed.data); // Verifying signed message return Promise.all([ openpgp.verify( @@ -306,9 +306,9 @@ function omnibus() { // Verifying detached signature openpgp.verify( { - message: openpgp.CleartextMessage.fromText(testData), + message: openpgp.cleartext.fromText(testData), publicKeys: pubHi, - signature: await openpgp.readArmoredSignature(signed) + signature: await openpgp.signature.readArmored(signed.data) } ).then(output => expect(output.signatures[0].valid).to.be.true) ]); @@ -316,12 +316,12 @@ function omnibus() { // Encrypting and signing openpgp.encrypt( { - message: openpgp.Message.fromText(testData2), + message: openpgp.message.fromText(testData2), publicKeys: [pubBye], privateKeys: [hi] } ).then(async encrypted => { - const msg = await openpgp.readArmoredMessage(encrypted); + const msg = await openpgp.message.readArmored(encrypted.data); // Decrypting and verifying return openpgp.decrypt( { @@ -340,4 +340,21 @@ function omnibus() { }); } +tryTests('Brainpool Omnibus Tests @lightweight', omnibus, { + if: !openpgp.config.ci && (openpgp.config.use_indutny_elliptic || openpgp.util.getNodeCrypto()) +}); + +tryTests('Brainpool Omnibus Tests - Worker @lightweight', omnibus, { + if: typeof window !== 'undefined' && window.Worker && (openpgp.config.use_indutny_elliptic || openpgp.util.getNodeCrypto()), + before: async function() { + await openpgp.initWorker({ path: '../dist/openpgp.worker.js' }); + }, + beforeEach: function() { + openpgp.config.use_native = true; + }, + after: function() { + openpgp.destroyWorker(); + } +}); + // TODO find test vectors diff --git a/test/general/decompression.js b/test/general/decompression.js index d2df75b6..af0ce61f 100644 --- a/test/general/decompression.js +++ b/test/general/decompression.js @@ -1,4 +1,4 @@ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); const chai = require('chai'); chai.use(require('chai-as-promised')); @@ -40,17 +40,17 @@ Xg== } }; -module.exports = () => describe('Decrypt and decompress message tests', function () { +describe('Decrypt and decompress message tests', function () { function runTest(key, test) { it(`Decrypts message compressed with ${key}`, async function () { - const message = await openpgp.readArmoredMessage(test.input); + const message = await openpgp.message.readArmored(test.input); const options = { - passwords: password, - message - }; - return openpgp.decrypt(options).then(function (decrypted) { - expect(decrypted.data).to.equal(test.output + '\n'); + passwords: password, + message + }; + return openpgp.decrypt(options).then(function (encrypted) { + expect(encrypted.data).to.equal(test.output + '\n'); }); }); } diff --git a/test/general/ecc_nist.js b/test/general/ecc_nist.js index 4b148849..600bf0e5 100644 --- a/test/general/ecc_nist.js +++ b/test/general/ecc_nist.js @@ -1,6 +1,6 @@ /* globals tryTests: true */ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); const chai = require('chai'); chai.use(require('chai-as-promised')); @@ -8,7 +8,7 @@ const input = require('./testInputs.js'); const expect = chai.expect; -module.exports = () => describe('Elliptic Curve Cryptography for NIST P-256,P-384,P-521 curves @lightweight', function () { +describe('Elliptic Curve Cryptography for NIST P-256,P-384,P-521 curves @lightweight', function () { function omnibus() { it('Omnibus NIST P-256 Test', function () { const options = { userIds: {name: "Hi", email: "hi@hel.lo"}, curve: "p256" }; @@ -27,9 +27,9 @@ module.exports = () => describe('Elliptic Curve Cryptography for NIST P-256,P-38 // Signing message openpgp.sign( - { message: openpgp.CleartextMessage.fromText(testData), privateKeys: hi } + { message: openpgp.cleartext.fromText(testData), privateKeys: hi } ).then(async signed => { - const msg = await openpgp.readArmoredCleartextMessage(signed); + const msg = await openpgp.cleartext.readArmored(signed.data); // Verifying signed message return Promise.all([ openpgp.verify( @@ -37,19 +37,19 @@ module.exports = () => describe('Elliptic Curve Cryptography for NIST P-256,P-38 ).then(output => expect(output.signatures[0].valid).to.be.true), // Verifying detached signature openpgp.verify( - { message: openpgp.CleartextMessage.fromText(testData), + { message: openpgp.cleartext.fromText(testData), publicKeys: pubHi, - signature: await openpgp.readArmoredSignature(signed) } + signature: await openpgp.signature.readArmored(signed.data) } ).then(output => expect(output.signatures[0].valid).to.be.true) ]); }), // Encrypting and signing openpgp.encrypt( - { message: openpgp.Message.fromText(testData2), + { message: openpgp.message.fromText(testData2), publicKeys: [pubBye], privateKeys: [hi] } ).then(async encrypted => { - const msg = await openpgp.readArmoredMessage(encrypted); + const msg = await openpgp.message.readArmored(encrypted.data); // Decrypting and verifying return openpgp.decrypt( { message: msg, @@ -72,8 +72,8 @@ module.exports = () => describe('Elliptic Curve Cryptography for NIST P-256,P-38 const testData = input.createSomeMessage(); let options = { userIds: {name: "Hi", email: "hi@hel.lo"}, curve: "p256" }; const firstKey = await openpgp.generateKey(options); - const signature = await openpgp.sign({ message: openpgp.CleartextMessage.fromText(testData), privateKeys: firstKey.key }); - const msg = await openpgp.readArmoredCleartextMessage(signature); + const signature = await openpgp.sign({ message: openpgp.cleartext.fromText(testData), privateKeys: firstKey.key }); + const msg = await openpgp.cleartext.readArmored(signature.data); const result = await openpgp.verify({ message: msg, publicKeys: firstKey.key.toPublic()}); expect(result.signatures[0].valid).to.be.true; }); @@ -85,11 +85,11 @@ module.exports = () => describe('Elliptic Curve Cryptography for NIST P-256,P-38 options = { userIds: { name: "Bye", email: "bye@good.bye" }, curve: "p256" }; const secondKey = await openpgp.generateKey(options); const encrypted = await openpgp.encrypt( - { message: openpgp.Message.fromText(testData), + { message: openpgp.message.fromText(testData), publicKeys: [secondKey.key.toPublic()], privateKeys: [firstKey.key] } ); - const msg = await openpgp.readArmoredMessage(encrypted); + const msg = await openpgp.message.readArmored(encrypted.data); const result = await openpgp.decrypt( { message: msg, privateKeys: secondKey.key, @@ -98,5 +98,18 @@ module.exports = () => describe('Elliptic Curve Cryptography for NIST P-256,P-38 expect(result.signatures[0].valid).to.be.true; }); + tryTests('ECC Worker Tests', omnibus, { + if: typeof window !== 'undefined' && window.Worker, + before: async function() { + await openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); + }, + beforeEach: function() { + openpgp.config.use_native = true; + }, + after: function() { + openpgp.destroyWorker(); + } + }); + // TODO find test vectors }); diff --git a/test/general/ecc_secp256k1.js b/test/general/ecc_secp256k1.js index a314fa86..76c93bbb 100644 --- a/test/general/ecc_secp256k1.js +++ b/test/general/ecc_secp256k1.js @@ -1,15 +1,14 @@ /* globals tryTests: true */ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); -const util = require('../../src/util'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); const chai = require('chai'); chai.use(require('chai-as-promised')); const expect = chai.expect; -module.exports = () => describe('Elliptic Curve Cryptography for secp256k1 curve @lightweight', function () { - if (!openpgp.config.useIndutnyElliptic && !util.getNodeCrypto()) { +describe('Elliptic Curve Cryptography for secp256k1 curve @lightweight', function () { + if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) { before(function() { this.skip(); }); @@ -142,22 +141,26 @@ module.exports = () => describe('Elliptic Curve Cryptography for secp256k1 curve if (data[name].pub_key) { return data[name].pub_key; } - const pub = await openpgp.readArmoredKey(data[name].pub); + const pub = await openpgp.key.readArmored(data[name].pub); expect(pub).to.exist; - expect(pub.getKeyId().toHex()).to.equal(data[name].id); - data[name].pub_key = pub; - return pub; + expect(pub.err).to.not.exist; + expect(pub.keys).to.have.length(1); + expect(pub.keys[0].getKeyId().toHex()).to.equal(data[name].id); + data[name].pub_key = pub.keys[0]; + return data[name].pub_key; } async function load_priv_key(name) { if (data[name].priv_key) { return data[name].priv_key; } - const pk = await openpgp.readArmoredKey(data[name].priv); + const pk = await openpgp.key.readArmored(data[name].priv); expect(pk).to.exist; - expect(pk.getKeyId().toHex()).to.equal(data[name].id); - await pk.decrypt(data[name].pass); - data[name].priv_key = pk; - return pk; + expect(pk.err).to.not.exist; + expect(pk.keys).to.have.length(1); + expect(pk.keys[0].getKeyId().toHex()).to.equal(data[name].id); + expect(await pk.keys[0].decrypt(data[name].pass)).to.be.true; + data[name].priv_key = pk.keys[0]; + return data[name].priv_key; } it('Load public key', async function () { const romeoPublic = await load_pub_key('romeo'); @@ -176,7 +179,7 @@ module.exports = () => describe('Elliptic Curve Cryptography for secp256k1 curve }); it('Verify clear signed message', async function () { const pub = await load_pub_key('juliet'); - const msg = await openpgp.readArmoredCleartextMessage(data.juliet.message_signed); + const msg = await openpgp.cleartext.readArmored(data.juliet.message_signed); return openpgp.verify({publicKeys: [pub], message: msg}).then(function(result) { expect(result).to.exist; expect(result.data).to.equal(data.juliet.message); @@ -186,9 +189,9 @@ module.exports = () => describe('Elliptic Curve Cryptography for secp256k1 curve }); it('Sign message', async function () { const romeoPrivate = await load_priv_key('romeo'); - const signed = await openpgp.sign({privateKeys: [romeoPrivate], message: openpgp.CleartextMessage.fromText(data.romeo.message)}); + const signed = await openpgp.sign({privateKeys: [romeoPrivate], message: openpgp.cleartext.fromText(data.romeo.message)}); const romeoPublic = await load_pub_key('romeo'); - const msg = await openpgp.readArmoredCleartextMessage(signed); + const msg = await openpgp.cleartext.readArmored(signed.data); const result = await openpgp.verify({publicKeys: [romeoPublic], message: msg}); expect(result).to.exist; @@ -199,7 +202,7 @@ module.exports = () => describe('Elliptic Curve Cryptography for secp256k1 curve it('Decrypt and verify message', async function () { const juliet = await load_pub_key('juliet'); const romeo = await load_priv_key('romeo'); - const msg = await openpgp.readArmoredMessage(data.juliet.message_encrypted); + const msg = await openpgp.message.readArmored(data.juliet.message_encrypted); const result = await openpgp.decrypt({privateKeys: romeo, publicKeys: [juliet], message: msg}); expect(result).to.exist; @@ -210,9 +213,9 @@ module.exports = () => describe('Elliptic Curve Cryptography for secp256k1 curve it('Encrypt and sign message', async function () { const romeoPrivate = await load_priv_key('romeo'); const julietPublic = await load_pub_key('juliet'); - const encrypted = await openpgp.encrypt({publicKeys: [julietPublic], privateKeys: [romeoPrivate], message: openpgp.Message.fromText(data.romeo.message)}); + const encrypted = await openpgp.encrypt({publicKeys: [julietPublic], privateKeys: [romeoPrivate], message: openpgp.message.fromText(data.romeo.message)}); - const message = await openpgp.readArmoredMessage(encrypted); + const message = await openpgp.message.readArmored(encrypted.data); const romeoPublic = await load_pub_key('romeo'); const julietPrivate = await load_priv_key('juliet'); const result = await openpgp.decrypt({privateKeys: julietPrivate, publicKeys: [romeoPublic], message: message}); diff --git a/test/general/hkp.js b/test/general/hkp.js index 5746806e..0a4cd4e1 100644 --- a/test/general/hkp.js +++ b/test/general/hkp.js @@ -1,10 +1,10 @@ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); const chai = require('chai'); const { expect } = chai; -module.exports = () => describe.skip('HKP unit tests', function() { +describe.skip('HKP unit tests', function() { this.timeout(60000); let hkp; diff --git a/test/general/index.js b/test/general/index.js index 692542d1..bf2a0586 100644 --- a/test/general/index.js +++ b/test/general/index.js @@ -1,20 +1,19 @@ -module.exports = () => describe('General', function () { - require('./util.js')(); - require('./biginteger.js')(); - require('./armor.js')(); - require('./packet.js')(); - require('./keyring.js')(); - require('./signature.js')(); - require('./key.js')(); - require('./openpgp.js')(); - require('./hkp.js')(); - require('./wkd.js')(); - require('./oid.js')(); - require('./ecc_nist.js')(); - require('./ecc_secp256k1.js')(); - require('./x25519.js')(); - require('./brainpool.js')(); - require('./decompression.js')(); - require('./streaming.js')(); +describe('General', function () { + require('./util.js'); + require('./armor.js'); + require('./packet.js'); + require('./keyring.js'); + require('./signature.js'); + require('./key.js'); + require('./openpgp.js'); + require('./hkp.js'); + require('./wkd.js'); + require('./oid.js'); + require('./ecc_nist.js'); + require('./ecc_secp256k1.js'); + require('./x25519.js'); + require('./brainpool.js'); + require('./decompression.js'); + require('./streaming.js'); }); diff --git a/test/general/key.js b/test/general/key.js index f8c3e988..bc553b7e 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -1,9 +1,8 @@ /* globals tryTests: true */ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); -const util = require('../../src/util'); -const key = require('../../src/key'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); +const stub = require('sinon/lib/sinon/stub'); const chai = require('chai'); chai.use(require('chai-as-promised')); @@ -167,535 +166,535 @@ zoGJ6s48HcP591pN93uAitCcYcinY2ZslmdiCXw+zbeoX4spNrV4T4CYxBjNQdIa const twoKeys = ['-----BEGIN PGP PUBLIC KEY BLOCK-----', - 'Version: GnuPG v2.0.19 (GNU/Linux)', - '', - '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', - 'PepQmgsu', - '=w6wd', - '-----END PGP PUBLIC KEY BLOCK-----'].join("\n"); + 'Version: GnuPG v2.0.19 (GNU/Linux)', + '', + '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', + 'PepQmgsu', + '=w6wd', + '-----END PGP PUBLIC KEY BLOCK-----'].join("\n"); const pub_revoked_subkeys = ['-----BEGIN PGP PUBLIC KEY BLOCK-----', - 'Version: GnuPG v2.0.19 (GNU/Linux)', - '', - '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', - 'pMiJM+NJAQ==', - '=ok+o', - '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); + 'Version: GnuPG v2.0.19 (GNU/Linux)', + '', + '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', + 'pMiJM+NJAQ==', + '=ok+o', + '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); const pub_revoked_with_cert = ['-----BEGIN PGP PUBLIC KEY BLOCK-----', - 'Comment: GPGTools - https://gpgtools.org', - '', - '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', - 'onCOcv4DKguN2w==', - '=OqO3', - '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); + 'Comment: GPGTools - https://gpgtools.org', + '', + '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', + 'onCOcv4DKguN2w==', + '=OqO3', + '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); const pub_sig_test = ['-----BEGIN PGP PUBLIC KEY BLOCK-----', - 'Version: GnuPG v2.0.19 (GNU/Linux)', - '', - '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', - 'yeSTTh8ZnzRiwuUH1t90E7w=', - '=e8xo', - '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); + 'Version: GnuPG v2.0.19 (GNU/Linux)', + '', + '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', + 'yeSTTh8ZnzRiwuUH1t90E7w=', + '=e8xo', + '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); const priv_key_rsa = ['-----BEGIN PGP PRIVATE KEY BLOCK-----', - 'Version: GnuPG v2.0.19 (GNU/Linux)', - '', - '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', - '=lw5e', - '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); + 'Version: GnuPG v2.0.19 (GNU/Linux)', + '', + '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', + '=lw5e', + '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); const user_attr_key = ['-----BEGIN PGP PUBLIC KEY BLOCK-----', - 'Version: GnuPG v2.0.22 (GNU/Linux)', - '', - 'mI0EUmEvTgEEANyWtQQMOybQ9JltDqmaX0WnNPJeLILIM36sw6zL0nfTQ5zXSS3+', - 'fIF6P29lJFxpblWk02PSID5zX/DYU9/zjM2xPO8Oa4xo0cVTOTLj++Ri5mtr//f5', - 'GLsIXxFrBJhD/ghFsL3Op0GXOeLJ9A5bsOn8th7x6JucNKuaRB6bQbSPABEBAAG0', - 'JFRlc3QgTWNUZXN0aW5ndG9uIDx0ZXN0QGV4YW1wbGUuY29tPoi5BBMBAgAjBQJS', - 'YS9OAhsvBwsJCAcDAgEGFQgCCQoLBBYCAwECHgECF4AACgkQSmNhOk1uQJQwDAP6', - 'AgrTyqkRlJVqz2pb46TfbDM2TDF7o9CBnBzIGoxBhlRwpqALz7z2kxBDmwpQa+ki', - 'Bq3jZN/UosY9y8bhwMAlnrDY9jP1gdCo+H0sD48CdXybblNwaYpwqC8VSpDdTndf', - '9j2wE/weihGp/DAdy/2kyBCaiOY1sjhUfJ1GogF49rDRwc7BzAEQAAEBAAAAAAAA', - 'AAAAAAAA/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQN', - 'DAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/', - '2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy', - 'MjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAAFABQDASIAAhEBAxEB/8QAHwAAAQUB', - 'AQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQID', - 'AAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0', - 'NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKT', - 'lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl', - '5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL', - '/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHB', - 'CSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpj', - 'ZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3', - 'uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIR', - 'AxEAPwD3+iiigAooooA//9mIuQQTAQIAIwUCUzxDqQIbLwcLCQgHAwIBBhUIAgkK', - 'CwQWAgMBAh4BAheAAAoJEEpjYTpNbkCU9PEEAKMMaXjhGdgDISBXAAEVXL6MB3x1', - 'd/7zBdnUljh1gM34TSKvbeZf7h/1DNgLbJFfSF3KiLViiqRVOumIkjwNIMZPqYtu', - 'WoEcElY50mvTETzOKemCt1GYI0GhOY2uZOVRtQLrkX0CB9r5hEQalkrnjNKlbghj', - 'LfOYu1uARF16cZUWuI0EUmEvTgEEAOkfz7QRWiWk+I6tdMqgEpOLKsFTLHOh3Inz', - 'OZUnccxMRT++J2lDDMhLChz+d0MUxdBq6rrGoEIP2bYE9AjdR1DNedsuwAjnadYI', - 'io6TMzk0ApagqHJcr1jhQfi/0sBhCCX+y0ghK8KAbiYnyXPMQFa9F19CbYaFvrj/', - 'dXk0N16bABEBAAGJAT0EGAECAAkFAlJhL04CGy4AqAkQSmNhOk1uQJSdIAQZAQIA', - 'BgUCUmEvTgAKCRDghPdEbCAsl7qiBADZpokQgEhe2Cuz7xZIniTcM3itFdxdpRl/', - 'rrumN0P2cXbcHOMUfpnvwkgZrFEcl0ztvTloTxi7Mzx/c0iVPQXQ4ur9Mjaa5hT1', - '/9TYNAG5/7ApMHrb48QtWCL0yxcLVC/+7+jUtm2abFMUU4PfnEqzFlkjY4mPalCm', - 'o5tbbszw2VwFBADDZgDd8Vzfyo8r49jitnJNF1u+PLJf7XN6oijzCftAJDBez44Z', - 'ofZ8ahPfkAhJe6opxaqgS47s4FIQVOEJcF9RgwLTU6uooSzA+b9XfNmQu7TWrXZQ', - 'zBlpyHbxDAr9hmXLiKg0Pa11rOPXu7atTZ3C2Ic97WIyoaBUyhCKt8tz6Q==', - '=MVfN', - '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); + 'Version: GnuPG v2.0.22 (GNU/Linux)', + '', + 'mI0EUmEvTgEEANyWtQQMOybQ9JltDqmaX0WnNPJeLILIM36sw6zL0nfTQ5zXSS3+', + 'fIF6P29lJFxpblWk02PSID5zX/DYU9/zjM2xPO8Oa4xo0cVTOTLj++Ri5mtr//f5', + 'GLsIXxFrBJhD/ghFsL3Op0GXOeLJ9A5bsOn8th7x6JucNKuaRB6bQbSPABEBAAG0', + 'JFRlc3QgTWNUZXN0aW5ndG9uIDx0ZXN0QGV4YW1wbGUuY29tPoi5BBMBAgAjBQJS', + 'YS9OAhsvBwsJCAcDAgEGFQgCCQoLBBYCAwECHgECF4AACgkQSmNhOk1uQJQwDAP6', + 'AgrTyqkRlJVqz2pb46TfbDM2TDF7o9CBnBzIGoxBhlRwpqALz7z2kxBDmwpQa+ki', + 'Bq3jZN/UosY9y8bhwMAlnrDY9jP1gdCo+H0sD48CdXybblNwaYpwqC8VSpDdTndf', + '9j2wE/weihGp/DAdy/2kyBCaiOY1sjhUfJ1GogF49rDRwc7BzAEQAAEBAAAAAAAA', + 'AAAAAAAA/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQN', + 'DAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/', + '2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy', + 'MjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAAFABQDASIAAhEBAxEB/8QAHwAAAQUB', + 'AQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQID', + 'AAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0', + 'NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKT', + 'lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl', + '5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL', + '/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHB', + 'CSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpj', + 'ZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3', + 'uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIR', + 'AxEAPwD3+iiigAooooA//9mIuQQTAQIAIwUCUzxDqQIbLwcLCQgHAwIBBhUIAgkK', + 'CwQWAgMBAh4BAheAAAoJEEpjYTpNbkCU9PEEAKMMaXjhGdgDISBXAAEVXL6MB3x1', + 'd/7zBdnUljh1gM34TSKvbeZf7h/1DNgLbJFfSF3KiLViiqRVOumIkjwNIMZPqYtu', + 'WoEcElY50mvTETzOKemCt1GYI0GhOY2uZOVRtQLrkX0CB9r5hEQalkrnjNKlbghj', + 'LfOYu1uARF16cZUWuI0EUmEvTgEEAOkfz7QRWiWk+I6tdMqgEpOLKsFTLHOh3Inz', + 'OZUnccxMRT++J2lDDMhLChz+d0MUxdBq6rrGoEIP2bYE9AjdR1DNedsuwAjnadYI', + 'io6TMzk0ApagqHJcr1jhQfi/0sBhCCX+y0ghK8KAbiYnyXPMQFa9F19CbYaFvrj/', + 'dXk0N16bABEBAAGJAT0EGAECAAkFAlJhL04CGy4AqAkQSmNhOk1uQJSdIAQZAQIA', + 'BgUCUmEvTgAKCRDghPdEbCAsl7qiBADZpokQgEhe2Cuz7xZIniTcM3itFdxdpRl/', + 'rrumN0P2cXbcHOMUfpnvwkgZrFEcl0ztvTloTxi7Mzx/c0iVPQXQ4ur9Mjaa5hT1', + '/9TYNAG5/7ApMHrb48QtWCL0yxcLVC/+7+jUtm2abFMUU4PfnEqzFlkjY4mPalCm', + 'o5tbbszw2VwFBADDZgDd8Vzfyo8r49jitnJNF1u+PLJf7XN6oijzCftAJDBez44Z', + 'ofZ8ahPfkAhJe6opxaqgS47s4FIQVOEJcF9RgwLTU6uooSzA+b9XfNmQu7TWrXZQ', + 'zBlpyHbxDAr9hmXLiKg0Pa11rOPXu7atTZ3C2Ic97WIyoaBUyhCKt8tz6Q==', + '=MVfN', + '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); const pgp_desktop_pub = ['-----BEGIN PGP PUBLIC KEY BLOCK-----', - 'Version: Encryption Desktop 10.3.0 (Build 9307)', - '', - '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', - '=dVeR', - '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); + 'Version: Encryption Desktop 10.3.0 (Build 9307)', + '', + 'mQENBFNjoowBCACeKvpQnv8wN3UdDVrZN//Bh/Dtq60hbZ3ObfTbNVBQ0DLD6jWA', + 'lKgwgSa3GLr0a3qrc30CRq0hRIjrFMrg4aPu5sRiZYP90B1cUGf08F2by8f+as2b', + 'BOBzRkcxH/ZmBZPU0pkRoOnkMvoT+YVt2MxzaJRBKM1dgcPXTHvZ52j7V0uEJvs8', + 's/H8DJq6MtgYqoS1zt/+eqUSDCcsVJBsEl7o7qU2d9i074hiBouM2B2mimvBKFIn', + 'W2kmG6fSryNSLaUMwvOTEC/esVNlgvSBfhu82Gic8Rwc+g0cHUnAESChxz/jE0P6', + 'INt2IpBZKeuXCY97tQmce3R4GOc/r3FNBBa3ABEBAAG0HVBHUCBEZXNrdG9wIDEw', + 'LjMgPHBncEBzeW0uZGU+iQFyBBABAgBcBQJTY6KMMBSAAAAAACAAB3ByZWZlcnJl', + 'ZC1lbWFpbC1lbmNvZGluZ0BwZ3AuY29tcGdwbWltZQgLCQgHAwIBCgIZAQUbAwAA', + 'AAUWAAMCAQUeAQAAAAYVCAkKAwIACgkQjhjogWc7SuswzggAhxyEqLPiKTJdQOCj', + 'ewGX/2gyY+oreHZWVqoDU8J0AO3Ppnpv4mcyaKCqAteBzLtDj1KPxqCBF0mpYn9H', + '4o6qPTPlOFm83tmw8O5bLeNltDjElt93sNaHtWxKWjZReDbq4ZmwbjOoYt6ms1Tm', + 'azkVeEuSTSbDPknSaNh1a9ew1gytH5MWQwovqNxU0AgAKKdspXltssCbLux7gFdI', + 'nzOcRPuCHkCfy4C97qFlwZ2Tb2mDgwZYvACfvU7L5BY68WNnq0GKP5eZzM/Ge0xd', + 'NU8oSSzQ2E5A6clW8Y4xUymhwcpG2CzfbFpA/dVobM4wplD5BPkyJsgWIgnRO9Lo', + 'VF83+7kBDQRTY6KNAQgA6tnPjznr7HHcoEFXNRC+LEkDOLAm5kTU9MY+2joJyHG7', + 'XmEAhPRt4Cp5Fq79sXPvGZ6tQnD8NVvqc3+91ThTLLKCIRdLOunIGIEJdCr7gN49', + 'kgDYisWxt7QQIsv7Q0SqbGJa7F/jPj5EDf36XJlACJy1yfP6KI6NunffLa23BUU0', + 't0S/TWqq4185nQczJ1JnZItyyBIyIWXrNtz56B/mIDvIU56SxxpsrcYctAT68vW0', + 'njyQ7XRNIzsmvn4o+H9YHnSz3VdXeJaXd7TdU+WLT2lbgzF5BvDN3AlJI8jiONfu', + '0rW9oBmHsQdjDcOlWdExsCx5Lz7+La7EK/mX0rUVeQARAQABiQJBBBgBAgErBQJT', + 'Y6KPBRsMAAAAwF0gBBkBCAAGBQJTY6KOAAoJED0FhXx5gwvfTzoH/3j1tYLvkjM+', + 'XghFCzRWDKB7qMzY1kRFV2TNQALnnu1sdUOrs4bQ3w2/viMp2uMqAyU/2WK1CDum', + 'CA6+DYV1vFPsMX/l+efjK8g2b/3RJx/9oc/hUEphWbzY5WCawGodVFa+Yd6nkpBy', + 'oksEIR1I5K03ki5Bk45Bp4vQIoZvnQeTlmLQTxdaEPTcbTMQXHZPhpq65n7NFiie', + 'mRrruRDbl3gzJOAsRtM/2TVFWdkvmANx8S+OTsQGxSCP6ZFQed6K0wj9/HZzG5Ie', + 'zXoyGihFLI++Ad0Ivk5jvO8+r1O0Ld09LttPsm40rK+7dlPEdJoCeRf46ICD/YrL', + '7UOZmhXdA6MACgkQjhjogWc7Suvv0Qf9Fl+dKh80b/AwQJXdtHjw6ePvUFhVTFcA', + 'u57Cx7gQTmsdFm2i9UWvb5CBKk04n91ygTK8StOxz3WAPFawJvuLBzobHXfrCrHH', + '6Q6gjjAiagMouX/t6bGExydrPjHFiZrcdZDFqWyEf4nr5ixLISu8vUc17eH5EZhk', + 'EI60kmrH+xgvHa8wj5V2yk855tUr27BU2TOtcMgczT7nQhM4GWvzqyQxgvfvyXmY', + '8Lb9xUxv5RtWxkDjbbDa5dsKjquy7OPg857N8AizSsAK4Q4q9c8W5ivjYCegqv3S', + '+ysgG+xjsUOP8UzMbS35tIlmQ8j0hO7JuY1Gm0WnPN5PIJFZjebxjQ==', + '=dVeR', + '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); const pgp_desktop_priv = ['-----BEGIN PGP PRIVATE KEY BLOCK-----', - 'Version: Encryption Desktop 10.3.0 (Build 9307)', - '', - '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', - '5g==', - '=63Nq', - '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); - -const rsa_ecc_pub = + 'Version: Encryption Desktop 10.3.0 (Build 9307)', + '', + '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', + '5g==', + '=63Nq', + '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); + + const rsa_ecc_pub = ['pub rsa4096/C9DEDC77 2015-10-17 [expires: 2018-10-16]', - 'uid Google Security Team ', - 'sub nistp384/70C16E3C 2015-10-17 [expires: 2018-10-16]', - 'sub rsa4096/50CB43FB 2015-10-17 [expires: 2018-10-16]', - 'sub nistp384/102D9086 2015-10-17 [expires: 2018-10-16]', - 'sub rsa4096/DFC40367 2015-10-17 [expires: 2018-10-16]', - '', - '-----BEGIN PGP PUBLIC KEY BLOCK-----', - 'Version: GnuPG v2', - '', - 'mQINBFYiIB8BEACxs55+7GG6ONQV3UFYf36UDSVFbuvNB5V1NaEnkY0t+RVMigLR', - 'Zdl0HHsiaTKfKs4jqjLQAoR6Fcre9jlEhatotRg3AvHV1XYebxRlzdfXxyD0d6i9', - 'Quc1zbca0T8F1C5c7xfYP5g9iKWn5yFtHC3S7mLeOg7Ltx84bTo8AF7bHGA3uIQf', - 'uCtE8l6Z57HTeaf2IR/893jLOir8lvmTef83m/+e1j6ZwmKxxZO2s+aGKre6Fqsz', - 'Oo89CpWKNrdZ3IN8+Y4udZNlr7u0os7ffY0shfbLrqt+eVEu4EHfbpQTJxvalZJK', - 'tEnGtV8S7Z3dcPcimxvO7HZu7Wz8VnRzY/AZtee4fC+i2yBu1rWKgY3V1tFKdxVr', - 'KDnmS5MBgBAxv69mM3bf8QPinL4mtIQ65/Dt4ksJuysRmGwQ8LkjSLQCMMepnjBs', - '/63wJ3e4wN1LCwnJonA2f8gZQHNeGPUhVVd/dWFDtmQaLwKFcI0GS/DiUPBIJir5', - 'DWnrEedtlcSLlwwcUglFsG4Sds/tLr+z5yE88ZrDrIlX9fb9cCAsDq7c8/NCzgvw', - 'kFez14sXgGhMz6ZfFzM49o0XwlvAeuSJRWBvnKonxM7/laqv4gK0zur3a6+D6qCN', - 'vt9iWO/YG+0Fvhmyxe34/Q71nXWc9t5aLcokmYLGY1Dpzf9oB8hDRdMCAQARAQAB', - 'tCpHb29nbGUgU2VjdXJpdHkgVGVhbSA8c2VjdXJpdHlAZ29vZ2xlLmNvbT6JAjwE', - 'EwEIACYFAlYiIB8CGwEFCQWjmoAFCwkIBwIGFQgJCgsCAxYCAQIeAQIXgAAKCRC4', - '5BBcyd7cd8MzD/9YdMVZniQH4qBKxLFIoYGfLzCEI0S9IVUA37wrZ4YiRODSJRMf', - 'El6oVfTO/g8xpeQlDgHj1w2IDoSkeQrY+7rf9H41sGGOBDGXSQT+7Z7XFH2mPPvC', - 'cqYqR32BDNDkO/LL1BzzRlQvNmnGHxo098sqTgb7hoVsP+qFoem7JUMpcAV1KrUo', - 'P81haV8a/25ouWFZu5P68WFh861TyIjIYLQCns2fG+zlKFGN9Uynv6E5+Qk7dmni', - 'XnHRaiYZP9+wux6zm5a5wD/h6Iv4hyg/0Vnx5SyH8QOm3Qm6pkUciQkSmZQvf0r7', - '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', - '=Q/kB', - '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); + 'uid Google Security Team ', + 'sub nistp384/70C16E3C 2015-10-17 [expires: 2018-10-16]', + 'sub rsa4096/50CB43FB 2015-10-17 [expires: 2018-10-16]', + 'sub nistp384/102D9086 2015-10-17 [expires: 2018-10-16]', + 'sub rsa4096/DFC40367 2015-10-17 [expires: 2018-10-16]', + '', + '-----BEGIN PGP PUBLIC KEY BLOCK-----', + 'Version: GnuPG v2', + '', + 'mQINBFYiIB8BEACxs55+7GG6ONQV3UFYf36UDSVFbuvNB5V1NaEnkY0t+RVMigLR', + 'Zdl0HHsiaTKfKs4jqjLQAoR6Fcre9jlEhatotRg3AvHV1XYebxRlzdfXxyD0d6i9', + 'Quc1zbca0T8F1C5c7xfYP5g9iKWn5yFtHC3S7mLeOg7Ltx84bTo8AF7bHGA3uIQf', + 'uCtE8l6Z57HTeaf2IR/893jLOir8lvmTef83m/+e1j6ZwmKxxZO2s+aGKre6Fqsz', + 'Oo89CpWKNrdZ3IN8+Y4udZNlr7u0os7ffY0shfbLrqt+eVEu4EHfbpQTJxvalZJK', + 'tEnGtV8S7Z3dcPcimxvO7HZu7Wz8VnRzY/AZtee4fC+i2yBu1rWKgY3V1tFKdxVr', + 'KDnmS5MBgBAxv69mM3bf8QPinL4mtIQ65/Dt4ksJuysRmGwQ8LkjSLQCMMepnjBs', + '/63wJ3e4wN1LCwnJonA2f8gZQHNeGPUhVVd/dWFDtmQaLwKFcI0GS/DiUPBIJir5', + 'DWnrEedtlcSLlwwcUglFsG4Sds/tLr+z5yE88ZrDrIlX9fb9cCAsDq7c8/NCzgvw', + 'kFez14sXgGhMz6ZfFzM49o0XwlvAeuSJRWBvnKonxM7/laqv4gK0zur3a6+D6qCN', + 'vt9iWO/YG+0Fvhmyxe34/Q71nXWc9t5aLcokmYLGY1Dpzf9oB8hDRdMCAQARAQAB', + 'tCpHb29nbGUgU2VjdXJpdHkgVGVhbSA8c2VjdXJpdHlAZ29vZ2xlLmNvbT6JAjwE', + 'EwEIACYFAlYiIB8CGwEFCQWjmoAFCwkIBwIGFQgJCgsCAxYCAQIeAQIXgAAKCRC4', + '5BBcyd7cd8MzD/9YdMVZniQH4qBKxLFIoYGfLzCEI0S9IVUA37wrZ4YiRODSJRMf', + 'El6oVfTO/g8xpeQlDgHj1w2IDoSkeQrY+7rf9H41sGGOBDGXSQT+7Z7XFH2mPPvC', + 'cqYqR32BDNDkO/LL1BzzRlQvNmnGHxo098sqTgb7hoVsP+qFoem7JUMpcAV1KrUo', + 'P81haV8a/25ouWFZu5P68WFh861TyIjIYLQCns2fG+zlKFGN9Uynv6E5+Qk7dmni', + 'XnHRaiYZP9+wux6zm5a5wD/h6Iv4hyg/0Vnx5SyH8QOm3Qm6pkUciQkSmZQvf0r7', + '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', + '=Q/kB', + '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); const valid_binding_sig_among_many_expired_sigs_pub = [ '-----BEGIN PGP PUBLIC KEY BLOCK-----', @@ -792,54 +791,54 @@ const key_without_subkey = [ const multi_uid_key = ['-----BEGIN PGP PUBLIC KEY BLOCK-----', - 'Version: GnuPG v1', - '', - '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', - 'ljIYQms3pCv1ja6bLlNKpPII', - '=qxBI', - '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); + 'Version: GnuPG v1', + '', + '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', + 'ljIYQms3pCv1ja6bLlNKpPII', + '=qxBI', + '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); const wrong_key = ['-----BEGIN PGP PUBLIC KEY BLOCK-----', - 'Version: OpenPGP.js v0.9.0', - '', - 'xk0EUlhMvAEB/2MZtCUOAYvyLFjDp3OBMGn3Ev8FwjzyPbIF0JUw+L7y2XR5', - 'RVGvbK88unV3cU/1tOYdNsXI6pSp/Ztjyv7vbBUAEQEAAc0pV2hpdGVvdXQg', - 'VXNlciA8d2hpdGVvdXQudGVzdEB0LW9ubGluZS5kZT7CXAQQAQgAEAUCUlhM', - 'vQkQ9vYOm0LN/0wAAAW4Af9C+kYW1AvNWmivdtr0M0iYCUjM9DNOQH1fcvXq', - 'IiN602mWrkd8jcEzLsW5IUNzVPLhrFIuKyBDTpLnC07Loce1', - '=6XMW', - '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); + 'Version: OpenPGP.js v0.9.0', + '', + 'xk0EUlhMvAEB/2MZtCUOAYvyLFjDp3OBMGn3Ev8FwjzyPbIF0JUw+L7y2XR5', + 'RVGvbK88unV3cU/1tOYdNsXI6pSp/Ztjyv7vbBUAEQEAAc0pV2hpdGVvdXQg', + 'VXNlciA8d2hpdGVvdXQudGVzdEB0LW9ubGluZS5kZT7CXAQQAQgAEAUCUlhM', + 'vQkQ9vYOm0LN/0wAAAW4Af9C+kYW1AvNWmivdtr0M0iYCUjM9DNOQH1fcvXq', + 'IiN602mWrkd8jcEzLsW5IUNzVPLhrFIuKyBDTpLnC07Loce1', + '=6XMW', + '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); const expiredKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- @@ -1917,88 +1916,6 @@ vqBGKJzmO5q3cECw =X9kJ -----END PGP PRIVATE KEY BLOCK-----`; -const dsaPrivateKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- - -lQNTBF69PO8RCACHP4KLQcYOPGsGV9owTZvxnvHvvrY8W0v8xDUL3y6CLc05srF1 -kQp/81iUfP5g57BEiDpJV95kMh+ulBthIOGnuMCkodJjuBICB4K6BtFTV4Fw1Q5S -S7aLC9beCaMvvGHXsK6MbknYl+IVJY7Zmml1qUSrBIQFGp5kqdhIX4o+OrzZ1zYj -ALicqzD7Zx2VRjGNQv7UKv4CkBOC8ncdnq/4/OQeOYFzVbCOf+sJhTgz6yxjHJVC -fLk7w8l2v1zV11VJuc8cQiQ9g8tjbKgLMsbyzy7gl4m9MSCdinG36XZuPibZrSm0 -H8gKAdd1FT84a3/qU2rtLLR0y8tCxBj89Xx/AQCv7CDmwoU+/yGpBVVl1mh0ZUkA -/VJUhnJfv5MIOIi3AQf8CS9HrEmYJg/A3z0DcvcwIu/9gqpRLTqH1iT5o4BCg2j+ -Cog2ExYkQl1OEPkEQ1lKJSnD8MDwO3BlkJ4cD0VSKxlnwd9dsu9m2+F8T+K1hoA7 -PfH89TjD5HrEaGAYIdivLYSwoTNOO+fY8FoVC0RR9pFNOmjiTU5PZZedOxAql5Os -Hp2bYhky0G9trjo8Mt6CGhvgA3dAKyONftLQr9HSM0GKacFV+nRd9TGCPNZidKU8 -MDa/SB/08y1bBGX5FK5wwiZ6H5qD8VAUobH3kwKlrg0nL00/EqtYHJqvJ2gkT5/v -h8+z4R4TuYiy4kKF2FLPd5OjdA31IVDoVgCwF0WHLgf/X9AiTr/DPs/5dIYN1+hf -UJwqjzr3dlokRwx3CVDcOVsdkWRwb8cvxubbsIorvUrF02IhYjHJMjIHT/zFt2zA -+VPzO4zabUlawWVepPEwrCtXgvn9aXqjhAYbilG3UZamhfstGUmbmvWVDadALwby -EO8u2pfLhI2lep63V/+KtUOLhfk8jKRSvxvxlYAvMi7sK8kB+lYy17XKN+IMYgf8 -gMFV6XGKpdmMSV3jOvat8cI6vnRO0i+g3jANP3PfrFEivat/rVgxo67r4rxezfFn -J29qwB9rgbRgMBGsbDvIlQNV/NWFvHy2uQAEKn5eX4CoLsCZoR2VfK3BwBCxhYDp -/wAA/0GSmI9MlMnLadFNlcX2Bm4i15quZAGF8JxwHbj1dhdUEYq0E1Rlc3QgPHRl -c3RAdGVzdC5pbz6IlAQTEQgAPBYhBAq6lCI5EfrbHP1qZCxnOy/rlEGVBQJevTzv -AhsDBQsJCAcCAyICAQYVCgkICwIEFgIDAQIeBwIXgAAKCRAsZzsv65RBlUPoAP9Q -aTCWpHWZkvZzC8VU64O76fHp31rLWlcZFttuDNLyeAEAhOxkQHk6GR88R+EF5mrn -clr63t9Q4wreqOlO0NR5/9k= -=UW2O ------END PGP PRIVATE KEY BLOCK----- -`; - -const uidlessKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- - -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 -CciXuhqnLwoVF5/uXMYffVtfl/OU+w== -=EqcV ------END PGP PRIVATE KEY BLOCK-----`; const rsaSignOnly = `-----BEGIN PGP PRIVATE KEY BLOCK----- @@ -2046,129 +1963,99 @@ zUdJ3Sg6Eu+OC2ae5II63iB5fG+lCwZtfuepWnePDv8RDKNHCVP/LoBNpGOZ U9I6AUkZWdcsueib9ghKDDy+HbUbf2kCJWUnuyeOCKqQifDb8bsLmdQY4Wb6 EBeLgD8oZHVsH3NLjPakPw== =STqy ------END PGP MESSAGE-----`; - -const shortP521Key = `-----BEGIN PGP PRIVATE KEY BLOCK----- - -xcAiBV/Pa+4TAAAAjQUrgQQAIwQjBADY+IGvRpeUzbT0+YRUe0KCMxAZmDY1 -KjDzULlmOJOS0bfZfqd4HsUF2hRoU/rg1gu1ju/Nt/18db0SJExOqVB9CgA0 -ZYiYyJhGYDOVg/bD54E7a3txWuDPB1DzkGWJH8PkqGNzU0BJpZcUVA6Th09s -YeO7rx5jSoyWNXjUYKwc24trFAAAAAAARQIItVgIiTWNT+QEVnZqDKKTIOUU -XEetkjCjPed1RiSchiZpwq+Bvx5hWGsbV5Pjj0S6EuH/ca5w+2ZyITLWZjr1 -LP8eP80UVGVzdCA8dGVzdEB0ZXN0LmNvbT7CwBUFEBMKACEFAl/Pa+4ECwkH -CAMVCAoEFgIBAAIZAQIbAwIeBwMiAQIAIyIhBbDerrGG7kdy9vbLYvb/j6TC -53fuQgK9Gtt1xng5MgpSUX0CCJZB+Ppt8yG5hBzwiGz5ZRpPVBFihEftaTOO -tKUuYRpWlvgA/XV11DgL6KZWRwu4C2venydBW987CVXCbRp4r18FAgkBjTR1 -AXHEstTVwJYj8mWkOZrz+Bfqvu6pWPmVYclgHJK2sSWizakvX/DtX/LFgTJL -UoASOVvu1hYHDsCO7vWWC/bHwCYFX89r7hIAAACRBSuBBAAjBCMEAULqNQ3L -CcdVlpIHiq4Xb+elTEdu2oDDA+FCbwroX3wvMatrH6GodxCcrjQKUrfVNiiI -cvj+r6SE/pRDnxsvW/JSAWUz3XKfVXccb0PYf0ikkTmb8UW33AaNYX6Srk0W -iamEmEzUpCMiiyXiYe+fp9JD63rKLXBbvLCT2mHuYO/hOikKAwEKCQAAAAAA -RQIDBONtE8bb3Yr2otNhdR67lg529mm3rSRsyWwMBVUPwX0RTTZ/bejq7XP5 -fuXV8QSEjWdOdPBARGw9jhw51D1XWl8gFsK9BRgTCgAJBQJfz2vuAhsMACMi -IQWw3q6xhu5Hcvb2y2L2/4+kwud37kICvRrbdcZ4OTIKUiWwAgkBdH+OZHBt -D2Yx2xKVPqDGJgMa5Ta8GmQZOFnoC2SpB6i9hIOfwiNjNLs+bv+kTxZ09nzf -3ZUGYi5Ei70hLrDAy7UCCNQNObtPmUYaUTtRzj3S9jUohbIpQxcfyoCMh6aP -usLw5q4tc+I5gdq57aiulJ8r4Jj9rdzsZFA7PzNJ9WPGVYJ3 -=GSXO ------END PGP PRIVATE KEY BLOCK-----`; +-----END PGP MESSAGE-----` function versionSpecificTests() { it('Preferences of generated key', function() { const testPref = function(key) { // key flags const keyFlags = openpgp.enums.keyFlags; - expect(key.users[0].selfCertifications[0].keyFlags[0] & keyFlags.certifyKeys).to.equal(keyFlags.certifyKeys); - expect(key.users[0].selfCertifications[0].keyFlags[0] & keyFlags.signData).to.equal(keyFlags.signData); - expect(key.subKeys[0].bindingSignatures[0].keyFlags[0] & keyFlags.encryptCommunication).to.equal(keyFlags.encryptCommunication); - expect(key.subKeys[0].bindingSignatures[0].keyFlags[0] & keyFlags.encryptStorage).to.equal(keyFlags.encryptStorage); + expect(key.users[0].selfCertifications[0].keyFlags[0] & keyFlags.certify_keys).to.equal(keyFlags.certify_keys); + expect(key.users[0].selfCertifications[0].keyFlags[0] & keyFlags.sign_data).to.equal(keyFlags.sign_data); + expect(key.subKeys[0].bindingSignatures[0].keyFlags[0] & keyFlags.encrypt_communication).to.equal(keyFlags.encrypt_communication); + expect(key.subKeys[0].bindingSignatures[0].keyFlags[0] & keyFlags.encrypt_storage).to.equal(keyFlags.encrypt_storage); const sym = openpgp.enums.symmetric; - expect(key.users[0].selfCertifications[0].preferredSymmetricAlgorithms).to.eql([sym.aes256, sym.aes128, sym.aes192]); - if (openpgp.config.aeadProtect) { + expect(key.users[0].selfCertifications[0].preferredSymmetricAlgorithms).to.eql([sym.aes256, sym.aes128, sym.aes192, sym.cast5, sym.tripledes]); + if (openpgp.config.aead_protect) { const aead = openpgp.enums.aead; expect(key.users[0].selfCertifications[0].preferredAeadAlgorithms).to.eql([aead.eax, aead.ocb]); } const hash = openpgp.enums.hash; - expect(key.users[0].selfCertifications[0].preferredHashAlgorithms).to.eql([hash.sha256, hash.sha512]); + expect(key.users[0].selfCertifications[0].preferredHashAlgorithms).to.eql([hash.sha256, hash.sha512, hash.sha1]); const compr = openpgp.enums.compression; expect(key.users[0].selfCertifications[0].preferredCompressionAlgorithms).to.eql([compr.zlib, compr.zip, compr.uncompressed]); - - let expectedFeatures; - if (openpgp.config.v5Keys) { - expectedFeatures = [7]; // v5 + aead + mdc - } else if (openpgp.config.aeadProtect) { - expectedFeatures = [3]; // aead + mdc - } else { - expectedFeatures = [1]; // mdc - } - expect(key.users[0].selfCertifications[0].features).to.eql(expectedFeatures); + expect(key.users[0].selfCertifications[0].features).to.eql(openpgp.config.v5_keys ? [7] : [1]); }; - const opt = { userIds: { name: 'test', email: 'a@b.com' }, passphrase: 'hello' }; + const opt = {numBits: 512, userIds: 'test ', passphrase: 'hello'}; + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys return openpgp.generateKey(opt).then(async function(key) { testPref(key.key); - testPref(await openpgp.readArmoredKey(key.publicKeyArmored)); + testPref((await openpgp.key.readArmored(key.publicKeyArmored)).keys[0]); }); }); it('Preferences of generated key - with config values', async function() { - const encryptionCipherVal = openpgp.config.encryptionCipher; - const preferHashAlgorithmVal = openpgp.config.preferHashAlgorithm; + const encryption_cipherVal = openpgp.config.encryption_cipher; + const prefer_hash_algorithmVal = openpgp.config.prefer_hash_algorithm; const compressionVal = openpgp.config.compression; - const aeadModeVal = openpgp.config.aeadMode; - openpgp.config.encryptionCipher = openpgp.enums.symmetric.aes192; - openpgp.config.preferHashAlgorithm = openpgp.enums.hash.sha224; + const aead_modeVal = openpgp.config.aead_mode; + openpgp.config.encryption_cipher = openpgp.enums.symmetric.aes192; + openpgp.config.prefer_hash_algorithm = openpgp.enums.hash.sha224; openpgp.config.compression = openpgp.enums.compression.zlib; - openpgp.config.aeadMode = openpgp.enums.aead.experimentalGcm; + openpgp.config.aead_mode = openpgp.enums.aead.experimental_gcm; + if (openpgp.getWorker()) { + openpgp.getWorker().workers.forEach(worker => { + worker.postMessage({ event: 'configure', config: openpgp.config }); + }); + } const testPref = function(key) { // key flags const keyFlags = openpgp.enums.keyFlags; - expect(key.users[0].selfCertifications[0].keyFlags[0] & keyFlags.certifyKeys).to.equal(keyFlags.certifyKeys); - expect(key.users[0].selfCertifications[0].keyFlags[0] & keyFlags.signData).to.equal(keyFlags.signData); - expect(key.subKeys[0].bindingSignatures[0].keyFlags[0] & keyFlags.encryptCommunication).to.equal(keyFlags.encryptCommunication); - expect(key.subKeys[0].bindingSignatures[0].keyFlags[0] & keyFlags.encryptStorage).to.equal(keyFlags.encryptStorage); + expect(key.users[0].selfCertifications[0].keyFlags[0] & keyFlags.certify_keys).to.equal(keyFlags.certify_keys); + expect(key.users[0].selfCertifications[0].keyFlags[0] & keyFlags.sign_data).to.equal(keyFlags.sign_data); + expect(key.subKeys[0].bindingSignatures[0].keyFlags[0] & keyFlags.encrypt_communication).to.equal(keyFlags.encrypt_communication); + expect(key.subKeys[0].bindingSignatures[0].keyFlags[0] & keyFlags.encrypt_storage).to.equal(keyFlags.encrypt_storage); const sym = openpgp.enums.symmetric; - expect(key.users[0].selfCertifications[0].preferredSymmetricAlgorithms).to.eql([sym.aes192, sym.aes256, sym.aes128]); - if (openpgp.config.aeadProtect) { + expect(key.users[0].selfCertifications[0].preferredSymmetricAlgorithms).to.eql([sym.aes192, sym.aes256, sym.aes128, sym.cast5, sym.tripledes]); + if (openpgp.config.aead_protect) { const aead = openpgp.enums.aead; - expect(key.users[0].selfCertifications[0].preferredAeadAlgorithms).to.eql([aead.experimentalGcm, aead.eax, aead.ocb]); + expect(key.users[0].selfCertifications[0].preferredAeadAlgorithms).to.eql([aead.experimental_gcm, aead.eax, aead.ocb]); } const hash = openpgp.enums.hash; - expect(key.users[0].selfCertifications[0].preferredHashAlgorithms).to.eql([hash.sha224, hash.sha256, hash.sha512]); + expect(key.users[0].selfCertifications[0].preferredHashAlgorithms).to.eql([hash.sha224, hash.sha256, hash.sha512, hash.sha1]); const compr = openpgp.enums.compression; expect(key.users[0].selfCertifications[0].preferredCompressionAlgorithms).to.eql([compr.zlib, compr.zip, compr.uncompressed]); - - let expectedFeatures; - if (openpgp.config.v5Keys) { - expectedFeatures = [7]; // v5 + aead + mdc - } else if (openpgp.config.aeadProtect) { - expectedFeatures = [3]; // aead + mdc - } else { - expectedFeatures = [1]; // mdc - } - expect(key.users[0].selfCertifications[0].features).to.eql(expectedFeatures); + expect(key.users[0].selfCertifications[0].features).to.eql(openpgp.config.v5_keys ? [7] : [1]); }; - const opt = { userIds: { name: 'test', email: 'a@b.com' }, passphrase: 'hello' }; + const opt = {numBits: 512, userIds: 'test ', passphrase: 'hello'}; + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys try { const key = await openpgp.generateKey(opt); testPref(key.key); - testPref(await openpgp.readArmoredKey(key.publicKeyArmored)); + testPref((await openpgp.key.readArmored(key.publicKeyArmored)).keys[0]); } finally { - openpgp.config.encryptionCipher = encryptionCipherVal; - openpgp.config.preferHashAlgorithm = preferHashAlgorithmVal; + openpgp.config.encryption_cipher = encryption_cipherVal; + openpgp.config.prefer_hash_algorithm = prefer_hash_algorithmVal; openpgp.config.compression = compressionVal; - openpgp.config.aeadMode = aeadModeVal; + openpgp.config.aead_mode = aead_modeVal; + if (openpgp.getWorker()) { + openpgp.getWorker().workers.forEach(worker => { + worker.postMessage({ event: 'configure', config: openpgp.config }); + }); + } } }); it('Generated key is not unlocked by default', function() { - const opt = { userIds: { name: 'test', email: 'a@b.com' }, passphrase: '123' }; + const opt = {numBits: 512, userIds: 'test ', passphrase: '123'}; + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys let key; return openpgp.generateKey(opt).then(function(newKey) { key = newKey.key; - return openpgp.Message.fromText('hello').encrypt([key]); + return openpgp.message.fromText('hello').encrypt([key]); }).then(function(msg) { - return msg.decrypt([key]); + return msg.message.decrypt([key]); }).catch(function(err) { expect(err.message).to.equal('Private key is not decrypted.'); }); @@ -2176,7 +2063,8 @@ function versionSpecificTests() { it('Generate key - single userid', function() { const userId = { name: 'test', email: 'a@b.com', comment: 'test comment' }; - const opt = { userIds: userId, passphrase: '123' }; + const opt = {numBits: 512, userIds: userId, passphrase: '123'}; + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys return openpgp.generateKey(opt).then(function(key) { key = key.key; expect(key.users.length).to.equal(1); @@ -2189,7 +2077,8 @@ function versionSpecificTests() { it('Generate key - single userid (all missing)', function() { const userId = { name: '', email: '', comment: '' }; - const opt = { userIds: userId, passphrase: '123' }; + const opt = {numBits: 512, userIds: userId, passphrase: '123'}; + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys return openpgp.generateKey(opt).then(function(key) { key = key.key; expect(key.users.length).to.equal(1); @@ -2202,7 +2091,8 @@ function versionSpecificTests() { it('Generate key - single userid (missing email)', function() { const userId = { name: 'test', email: '', comment: 'test comment' }; - const opt = { userIds: userId, passphrase: '123' }; + const opt = {numBits: 512, userIds: userId, passphrase: '123'}; + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys return openpgp.generateKey(opt).then(function(key) { key = key.key; expect(key.users.length).to.equal(1); @@ -2215,7 +2105,8 @@ function versionSpecificTests() { it('Generate key - single userid (missing comment)', function() { const userId = { name: 'test', email: 'a@b.com', comment: '' }; - const opt = { userIds: userId, passphrase: '123' }; + const opt = {numBits: 512, userIds: userId, passphrase: '123'}; + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys return openpgp.generateKey(opt).then(function(key) { key = key.key; expect(key.users.length).to.equal(1); @@ -2229,10 +2120,12 @@ function versionSpecificTests() { it('Generate key - setting date to the past', function() { const past = new Date(0); const opt = { + numBits: 512, userIds: { name: 'Test User', email: 'text@example.com' }, passphrase: 'secret', date: past }; + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys return openpgp.generateKey(opt).then(function(newKey) { expect(newKey.key).to.exist; @@ -2245,10 +2138,12 @@ function versionSpecificTests() { it('Generate key - setting date to the future', function() { const future = new Date(Math.ceil(Date.now() / 1000) * 1000 + 1000); const opt = { + numBits: 512, userIds: { name: 'Test User', email: 'text@example.com' }, passphrase: 'secret', date: future }; + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys return openpgp.generateKey(opt).then(function(newKey) { expect(newKey.key).to.exist; @@ -2259,40 +2154,27 @@ function versionSpecificTests() { }); it('Generate key - multi userid', function() { - const userId1 = { name: 'test', email: 'a@b.com' }; - const userId2 = { name: 'test', email: 'b@c.com' }; - const opt = { userIds: [userId1, userId2], passphrase: '123' }; + const userId1 = 'test '; + const userId2 = 'test '; + const opt = {numBits: 512, userIds: [userId1, userId2], passphrase: '123'}; + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys return openpgp.generateKey(opt).then(function(key) { key = key.key; expect(key.users.length).to.equal(2); - expect(key.users[0].userId.userid).to.equal('test '); + expect(key.users[0].userId.userid).to.equal(userId1); expect(key.users[0].selfCertifications[0].isPrimaryUserID).to.be.true; - expect(key.users[1].userId.userid).to.equal('test '); + expect(key.users[1].userId.userid).to.equal(userId2); expect(key.users[1].selfCertifications[0].isPrimaryUserID).to.be.null; }); }); - it('Generate key - default values', function() { - const userId = { name: 'test', email: 'a@b.com' }; - const opt = { userIds: [userId] }; - return openpgp.generateKey(opt).then(function({ key }) { - expect(key.isDecrypted()).to.be.true; - expect(key.getAlgorithmInfo().algorithm).to.equal('eddsa'); - expect(key.users.length).to.equal(1); - expect(key.users[0].userId.userid).to.equal('test '); - expect(key.users[0].selfCertifications[0].isPrimaryUserID).to.be.true; - expect(key.subKeys).to.have.length(1); - expect(key.subKeys[0].getAlgorithmInfo().algorithm).to.equal('ecdh'); - }); - }); - it('Generate key - two subkeys with default values', function() { - const userId = { name: 'test', email: 'a@b.com' }; - const opt = { userIds: [userId], passphrase: '123', subkeys:[{},{}] }; + const userId = 'test '; + const opt = {curve: 'curve25519', userIds: [userId], passphrase: '123', subkeys:[{},{}]}; return openpgp.generateKey(opt).then(function(key) { key = key.key; expect(key.users.length).to.equal(1); - expect(key.users[0].userId.userid).to.equal('test '); + expect(key.users[0].userId.userid).to.equal(userId); expect(key.users[0].selfCertifications[0].isPrimaryUserID).to.be.true; expect(key.subKeys).to.have.length(2); expect(key.subKeys[0].getAlgorithmInfo().algorithm).to.equal('ecdh'); @@ -2300,33 +2182,13 @@ function versionSpecificTests() { }); }); - it('Generate RSA key - two subkeys with default values', async function() { - const rsaBits = 512; - const minRsaBits = openpgp.config.minRsaBits; - openpgp.config.minRsaBits = rsaBits; - - const userId = { name: 'test', email: 'a@b.com' }; - const opt = { type: 'rsa', rsaBits, userIds: [userId], passphrase: '123', subkeys:[{},{}] }; - try { - const { key } = await openpgp.generateKey(opt); - expect(key.users.length).to.equal(1); - expect(key.users[0].userId.userid).to.equal('test '); - expect(key.users[0].selfCertifications[0].isPrimaryUserID).to.be.true; - expect(key.subKeys).to.have.length(2); - expect(key.subKeys[0].getAlgorithmInfo().algorithm).to.equal('rsaEncryptSign'); - expect(key.subKeys[1].getAlgorithmInfo().algorithm).to.equal('rsaEncryptSign'); - } finally { - openpgp.config.minRsaBits = minRsaBits; - } - }); - it('Generate key - one signing subkey', function() { - const userId = { name: 'test', email: 'a@b.com' }; - const opt = { userIds: [userId], passphrase: '123', subkeys:[{}, { sign: true }] }; + const userId = 'test '; + const opt = {curve: 'curve25519', userIds: [userId], passphrase: '123', subkeys:[{}, {sign: true}]}; return openpgp.generateKey(opt).then(async function({ privateKeyArmored }) { - const key = await openpgp.readArmoredKey(privateKeyArmored); + const { keys: [key] } = await openpgp.key.readArmored(privateKeyArmored); expect(key.users.length).to.equal(1); - expect(key.users[0].userId.userid).to.equal('test '); + expect(key.users[0].userId.userid).to.equal(userId); expect(key.users[0].selfCertifications[0].isPrimaryUserID).to.be.true; expect(key.subKeys).to.have.length(2); expect(key.subKeys[0].getAlgorithmInfo().algorithm).to.equal('ecdh'); @@ -2337,15 +2199,15 @@ function versionSpecificTests() { }); it('Reformat key - one signing subkey', function() { - const userId = { name: 'test', email: 'a@b.com' }; - const opt = { userIds: [userId], passphrase: '123', subkeys:[{}, { sign: true }] }; + const userId = 'test '; + const opt = {curve: 'curve25519', userIds: [userId], passphrase: '123', subkeys:[{}, {sign: true}]}; return openpgp.generateKey(opt).then(async function({ key }) { await key.decrypt('123'); return openpgp.reformatKey({ privateKey: key, userIds: [userId] }); }).then(async function({ privateKeyArmored }) { - const key = await openpgp.readArmoredKey(privateKeyArmored); + const { keys: [key] } = await openpgp.key.readArmored(privateKeyArmored); expect(key.users.length).to.equal(1); - expect(key.users[0].userId.userid).to.equal('test '); + expect(key.users[0].userId.userid).to.equal(userId); expect(key.users[0].selfCertifications[0].isPrimaryUserID).to.be.true; expect(key.subKeys).to.have.length(2); expect(key.subKeys[0].getAlgorithmInfo().algorithm).to.equal('ecdh'); @@ -2355,40 +2217,37 @@ function versionSpecificTests() { }); }); - it('Generate key - override main RSA key options for subkey', async function() { - const rsaBits = 512; - const minRsaBits = openpgp.config.minRsaBits; - openpgp.config.minRsaBits = rsaBits; - - const userId = { name: 'test', email: 'a@b.com' }; - const opt = { type: 'rsa', rsaBits, userIds: [userId], passphrase: '123', subkeys:[{ type: 'ecc', curve: 'curve25519' }] }; - try { - const { key } = await openpgp.generateKey(opt); + it('Generate key - override main key options for subkey', function() { + const userId = 'test '; + const opt = {numBits: 512, userIds: [userId], passphrase: '123', subkeys:[{curve: 'curve25519'}]}; + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys + return openpgp.generateKey(opt).then(function(key) { + key = key.key; expect(key.users.length).to.equal(1); - expect(key.users[0].userId.userid).to.equal('test '); + expect(key.users[0].userId.userid).to.equal(userId); expect(key.users[0].selfCertifications[0].isPrimaryUserID).to.be.true; - expect(key.getAlgorithmInfo().algorithm).to.equal('rsaEncryptSign'); - expect(key.getAlgorithmInfo().bits).to.equal(opt.rsaBits); + expect(key.getAlgorithmInfo().algorithm).to.equal('rsa_encrypt_sign'); + expect(key.getAlgorithmInfo().bits).to.equal(opt.numBits); + expect(key.getAlgorithmInfo().rsaBits).to.equal(key.getAlgorithmInfo().bits); expect(key.subKeys[0].getAlgorithmInfo().algorithm).to.equal('ecdh'); - } finally { - openpgp.config.minRsaBits = minRsaBits; - } + }); }); it('Encrypt key with new passphrase', async function() { - const userId = { name: 'test', email: 'a@b.com' }; - const opt = { userIds: userId, passphrase: 'passphrase' }; + const userId = 'test '; + const opt = {numBits: 512, userIds: userId, passphrase: 'passphrase'}; + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys const key = (await openpgp.generateKey(opt)).key; const armor1 = key.armor(); const armor2 = key.armor(); expect(armor1).to.equal(armor2); - await key.decrypt('passphrase'); + expect(await key.decrypt('passphrase')).to.be.true; expect(key.isDecrypted()).to.be.true; await key.encrypt('new_passphrase'); expect(key.isDecrypted()).to.be.false; - await expect(key.decrypt('passphrase')).to.be.rejectedWith('Incorrect key passphrase'); + await expect(key.decrypt('passphrase')).to.eventually.be.rejectedWith('Incorrect key passphrase'); expect(key.isDecrypted()).to.be.false; - await key.decrypt('new_passphrase'); + expect(await key.decrypt('new_passphrase')).to.be.true; expect(key.isDecrypted()).to.be.true; const armor3 = key.armor(); expect(armor3).to.not.equal(armor1); @@ -2396,8 +2255,9 @@ function versionSpecificTests() { it('Generate key - ensure keyExpirationTime works', function() { const expect_delta = 365 * 24 * 60 * 60; - const userId = { name: 'test', email: 'a@b.com' }; - const opt = { userIds: userId, passphrase: '123', keyExpirationTime: expect_delta }; + const userId = 'test '; + const opt = {numBits: 512, userIds: userId, passphrase: '123', keyExpirationTime: expect_delta}; + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys return openpgp.generateKey(opt).then(async function(key) { key = key.key; @@ -2416,8 +2276,8 @@ function versionSpecificTests() { }); it('Sign and verify key - primary user', async function() { - let publicKey = await openpgp.readArmoredKey(pub_sig_test); - const privateKey = await openpgp.readArmoredKey(priv_key_rsa); + let publicKey = (await openpgp.key.readArmored(pub_sig_test)).keys[0]; + const privateKey = (await openpgp.key.readArmored(priv_key_rsa)).keys[0]; await privateKey.decrypt('hello world'); publicKey = await publicKey.signPrimaryUser([privateKey]); const signatures = await publicKey.verifyPrimaryUser([privateKey]); @@ -2431,9 +2291,9 @@ function versionSpecificTests() { }); it('Sign key and verify with wrong key - primary user', async function() { - let publicKey = await openpgp.readArmoredKey(pub_sig_test); - const privateKey = await openpgp.readArmoredKey(priv_key_rsa); - const wrongKey = await openpgp.readArmoredKey(wrong_key); + let publicKey = (await openpgp.key.readArmored(pub_sig_test)).keys[0]; + const privateKey = (await openpgp.key.readArmored(priv_key_rsa)).keys[0]; + const wrongKey = (await openpgp.key.readArmored(wrong_key)).keys[0]; await privateKey.decrypt('hello world'); publicKey = await publicKey.signPrimaryUser([privateKey]); const signatures = await publicKey.verifyPrimaryUser([wrongKey]); @@ -2447,8 +2307,8 @@ function versionSpecificTests() { }); it('Sign and verify key - all users', async function() { - let publicKey = await openpgp.readArmoredKey(multi_uid_key); - const privateKey = await openpgp.readArmoredKey(priv_key_rsa); + let publicKey = (await openpgp.key.readArmored(multi_uid_key)).keys[0]; + const privateKey = (await openpgp.key.readArmored(priv_key_rsa)).keys[0]; await privateKey.decrypt('hello world'); publicKey = await publicKey.signAllUsers([privateKey]); const signatures = await publicKey.verifyAllUsers([privateKey]); @@ -2470,9 +2330,9 @@ function versionSpecificTests() { }); it('Sign key and verify with wrong key - all users', async function() { - let publicKey = await openpgp.readArmoredKey(multi_uid_key); - const privateKey = await openpgp.readArmoredKey(priv_key_rsa); - const wrongKey = await openpgp.readArmoredKey(wrong_key); + let publicKey = (await openpgp.key.readArmored(multi_uid_key)).keys[0]; + const privateKey = (await openpgp.key.readArmored(priv_key_rsa)).keys[0]; + const wrongKey = (await openpgp.key.readArmored(wrong_key)).keys[0]; await privateKey.decrypt('hello world'); publicKey = await publicKey.signAllUsers([privateKey]); const signatures = await publicKey.verifyAllUsers([wrongKey]); @@ -2494,71 +2354,72 @@ function versionSpecificTests() { }); it('Reformat key without passphrase', function() { - const userId1 = { name: 'test', email: 'a@b.com' }; - const userId2 = { name: 'test', email: 'b@c.com' }; - const opt = { userIds: userId1 }; + const userId1 = 'test1 '; + const userId2 = 'test2 '; + const opt = {numBits: 512, userIds: userId1}; + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys return openpgp.generateKey(opt).then(function(key) { key = key.key; expect(key.users.length).to.equal(1); - expect(key.users[0].userId.userid).to.equal('test '); + expect(key.users[0].userId.userid).to.equal(userId1); expect(key.isDecrypted()).to.be.true; opt.privateKey = key; opt.userIds = userId2; return openpgp.reformatKey(opt).then(function(newKey) { newKey = newKey.key; expect(newKey.users.length).to.equal(1); - expect(newKey.users[0].userId.userid).to.equal('test '); + expect(newKey.users[0].userId.userid).to.equal(userId2); expect(newKey.isDecrypted()).to.be.true; }); }); }); it('Reformat key with no subkey with passphrase', async function() { - const userId = { name: 'test', email: 'a@b.com' }; - const key = await openpgp.readArmoredKey(key_without_subkey); - const opt = { privateKey: key, userIds: [userId], passphrase: "test" }; + const userId = 'test1 '; + const keys = (await openpgp.key.readArmored(key_without_subkey)).keys; + const opt = {privateKey: keys[0], userIds: [userId], passphrase: "test"}; return openpgp.reformatKey(opt).then(function(newKey) { newKey = newKey.key; expect(newKey.users.length).to.equal(1); - expect(newKey.users[0].userId.userid).to.equal('test '); + expect(newKey.users[0].userId.userid).to.equal(userId); expect(newKey.isDecrypted()).to.be.false; }); }); it('Reformat key with two subkeys with passphrase', function() { - const userId1 = { name: 'test', email: 'a@b.com' }; - const userId2 = { name: 'test', email: 'b@c.com' }; - const now = util.normalizeDate(new Date()); - const before = util.normalizeDate(new Date(0)); - const opt1 = { userIds: [userId1], date: now }; + const userId1 = 'test '; + const userId2 = 'test '; + const now = openpgp.util.normalizeDate(new Date()); + const before = openpgp.util.normalizeDate(new Date(0)); + const opt1 = {curve: 'curve25519', userIds: [userId1], date: now}; return openpgp.generateKey(opt1).then(function(newKey) { newKey = newKey.key; - expect(newKey.users[0].userId.userid).to.equal('test '); + expect(newKey.users[0].userId.userid).to.equal(userId1); expect(+newKey.getCreationTime()).to.equal(+now); expect(+newKey.subKeys[0].getCreationTime()).to.equal(+now); expect(+newKey.subKeys[0].bindingSignatures[0].created).to.equal(+now); - const opt2 = { privateKey: newKey, userIds: [userId2], date: before }; + const opt2 = {privateKey: newKey, userIds: [userId2], date: before}; return openpgp.reformatKey(opt2).then(function(refKey) { refKey = refKey.key; expect(refKey.users.length).to.equal(1); - expect(refKey.users[0].userId.userid).to.equal('test '); + expect(refKey.users[0].userId.userid).to.equal(userId2); expect(+refKey.subKeys[0].bindingSignatures[0].created).to.equal(+before); }); }); }); it('Reformat key with no subkey without passphrase', async function() { - const userId = { name: 'test', email: 'a@b.com' }; - const key = await openpgp.readArmoredKey(key_without_subkey); - const opt = { privateKey: key, userIds: [userId] }; + const userId = 'test1 '; + const keys = (await openpgp.key.readArmored(key_without_subkey)).keys; + const opt = {privateKey: keys[0], userIds: [userId]}; return openpgp.reformatKey(opt).then(function(newKey) { newKey = newKey.key; expect(newKey.users.length).to.equal(1); - expect(newKey.users[0].userId.userid).to.equal('test '); + expect(newKey.users[0].userId.userid).to.equal(userId); expect(newKey.isDecrypted()).to.be.true; - return openpgp.sign({ message: openpgp.CleartextMessage.fromText('hello'), privateKeys: newKey, armor: true }).then(async function(signed) { + return openpgp.sign({message: openpgp.cleartext.fromText('hello'), privateKeys: newKey, armor: true}).then(async function(signed) { return openpgp.verify( - { message: await openpgp.readArmoredCleartextMessage(signed), publicKeys: newKey.toPublic() } + {message: await openpgp.cleartext.readArmored(signed.data), publicKeys: newKey.toPublic()} ).then(async function(verified) { expect(verified.signatures[0].valid).to.be.true; const newSigningKey = await newKey.getSigningKey(); @@ -2570,10 +2431,11 @@ function versionSpecificTests() { }); it('Reformat and encrypt key', function() { - const userId1 = { name: 'test1', email: 'a@b.com' }; - const userId2 = { name: 'test2', email: 'b@c.com' }; - const userId3 = { name: 'test3', email: 'c@d.com' }; - const opt = { userIds: userId1 }; + const userId1 = 'test1 '; + const userId2 = 'test2 '; + const userId3 = 'test3 '; + const opt = {numBits: 512, userIds: userId1}; + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys return openpgp.generateKey(opt).then(function(key) { key = key.key; opt.privateKey = key; @@ -2582,7 +2444,7 @@ function versionSpecificTests() { return openpgp.reformatKey(opt).then(async function(newKey) { newKey = newKey.key; expect(newKey.users.length).to.equal(2); - expect(newKey.users[0].userId.userid).to.equal('test2 '); + expect(newKey.users[0].userId.userid).to.equal(userId2); expect(newKey.isDecrypted()).to.be.false; await newKey.decrypt('123'); expect(newKey.isDecrypted()).to.be.true; @@ -2591,17 +2453,18 @@ function versionSpecificTests() { }); it('Sign and encrypt with reformatted key', function() { - const userId1 = { name: 'test1', email: 'a@b.com' }; - const userId2 = { name: 'test2', email: 'b@c.com' }; - const opt = { userIds: userId1 }; + const userId1 = 'test1 '; + const userId2 = 'test2 '; + const opt = {numBits: 512, userIds: userId1}; + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys return openpgp.generateKey(opt).then(function(key) { key = key.key; opt.privateKey = key; opt.userIds = userId2; return openpgp.reformatKey(opt).then(function(newKey) { newKey = newKey.key; - return openpgp.encrypt({ message: openpgp.Message.fromText('hello'), publicKeys: newKey.toPublic(), privateKeys: newKey, armor: true }).then(async function(encrypted) { - return openpgp.decrypt({ message: await openpgp.readArmoredMessage(encrypted), privateKeys: newKey, publicKeys: newKey.toPublic() }).then(function(decrypted) { + return openpgp.encrypt({message: openpgp.message.fromText('hello'), publicKeys: newKey.toPublic(), privateKeys: newKey, armor: true}).then(async function(encrypted) { + return openpgp.decrypt({message: await openpgp.message.readArmored(encrypted.data), privateKeys: newKey, publicKeys: newKey.toPublic()}).then(function(decrypted) { expect(decrypted.data).to.equal('hello'); expect(decrypted.signatures[0].valid).to.be.true; }); @@ -2611,22 +2474,24 @@ function versionSpecificTests() { }); it('Reject with user-friendly error when reformatting encrypted key', function() { - const opt = { userIds: { name: 'test', email: 'a@b.com' }, passphrase: '1234' }; + const opt = {numBits: 512, userIds: 'test1 ', passphrase: '1234'}; + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys return openpgp.generateKey(opt).then(function(original) { - return openpgp.reformatKey({ privateKey: original.key, userIds: { name: 'test2', email: 'a@b.com' }, passphrase: '1234' }).then(function() { + return openpgp.reformatKey({privateKey: original.key, userIds: 'test2 ', passphrase: '1234'}).then(function() { throw new Error('reformatKey should result in error when key not decrypted'); }).catch(function(error) { - expect(error.message).to.equal('Error reformatting keypair: Key is not decrypted'); + expect(error.message).to.equal('Error reformatting keypair: Key not decrypted'); }); }); }); it('Revoke generated key with revocation certificate', function() { - const opt = { userIds: { name: 'test', email: 'a@b.com' }, passphrase: '1234' }; + const opt = {numBits: 512, userIds: 'test1 ', passphrase: '1234'}; + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys return openpgp.generateKey(opt).then(function(original) { - return openpgp.revokeKey({ key: original.key.toPublic(), revocationCertificate: original.revocationCertificate }).then(async function(revKey) { + return openpgp.revokeKey({key: original.key.toPublic(), revocationCertificate: original.revocationCertificate}).then(async function(revKey) { revKey = revKey.publicKey; - expect(revKey.revocationSignatures[0].reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.noReason); + expect(revKey.revocationSignatures[0].reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.no_reason); expect(revKey.revocationSignatures[0].reasonForRevocationString).to.equal(''); await expect(revKey.verifyPrimaryKey()).to.be.rejectedWith('Primary key is revoked'); }); @@ -2634,12 +2499,13 @@ function versionSpecificTests() { }); it('Revoke generated key with private key', function() { - const opt = { userIds: { name: 'test', email: 'a@b.com' }, passphrase: '1234' }; + const opt = {numBits: 512, userIds: 'test1 ', passphrase: '1234'}; + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys return openpgp.generateKey(opt).then(async function(original) { await original.key.decrypt('1234'); - return openpgp.revokeKey({ key: original.key, reasonForRevocation: { string: 'Testing key revocation' } }).then(async function(revKey) { + return openpgp.revokeKey({key: original.key, reasonForRevocation: {string: 'Testing key revocation'}}).then(async function(revKey) { revKey = revKey.publicKey; - expect(revKey.revocationSignatures[0].reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.noReason); + expect(revKey.revocationSignatures[0].reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.no_reason); expect(revKey.revocationSignatures[0].reasonForRevocationString).to.equal('Testing key revocation'); await expect(revKey.verifyPrimaryKey()).to.be.rejectedWith('Primary key is revoked'); }); @@ -2652,89 +2518,107 @@ function versionSpecificTests() { // uid emma.goldman@example.net // ssb cv25519 2019-03-20 [E] // E4557C2B02FFBF4B04F87401EC336AF7133D0F85BE7FD09BAEFD9CAEB8C93965 - const key = await openpgp.readArmoredKey(v5_sample_key); + const { keys: [key] } = await openpgp.key.readArmored(v5_sample_key); expect(key.primaryKey.getFingerprint()).to.equal('19347bc9872464025f99df3ec2e0000ed9884892e1f7b3ea4c94009159569b54'); expect(key.subKeys[0].getFingerprint()).to.equal('e4557c2b02ffbf4b04f87401ec336af7133d0f85be7fd09baefd9caeb8c93965'); await key.verifyPrimaryKey(); }); } -module.exports = () => describe('Key', function() { - let v5KeysVal; - let aeadProtectVal; +describe('Key', function() { + let rsaGenStub; + let rsaGenValue = openpgp.crypto.publicKey.rsa.generate(openpgp.util.getWebCryptoAll() ? 2048 : 512, "10001"); + + beforeEach(function() { + rsaGenStub = stub(openpgp.crypto.publicKey.rsa, 'generate'); + rsaGenStub.returns(rsaGenValue); + }); + + afterEach(function() { + rsaGenStub.restore(); + }); tryTests('V4', versionSpecificTests, { - if: !openpgp.config.ci, - beforeEach: function() { - v5KeysVal = openpgp.config.v5Keys; - openpgp.config.v5Keys = false; + if: !openpgp.config.ci + }); + + tryTests('V4 - With Worker', versionSpecificTests, { + if: typeof window !== 'undefined' && window.Worker, + before: async function() { + await openpgp.initWorker({ path: '../dist/openpgp.worker.js' }); }, - afterEach: function() { - openpgp.config.v5Keys = v5KeysVal; + after: function() { + openpgp.destroyWorker(); } }); + let v5_keysVal; + let aead_protectVal; tryTests('V5', versionSpecificTests, { if: !openpgp.config.ci, beforeEach: function() { - v5KeysVal = openpgp.config.v5Keys; - aeadProtectVal = openpgp.config.aeadProtect; - openpgp.config.v5Keys = true; - openpgp.config.aeadProtect = true; + v5_keysVal = openpgp.config.v5_keys; + aead_protectVal = openpgp.config.aead_protect; + openpgp.config.v5_keys = true; + openpgp.config.aead_protect = true; }, afterEach: function() { - openpgp.config.v5Keys = v5KeysVal; - openpgp.config.aeadProtect = aeadProtectVal; + openpgp.config.v5_keys = v5_keysVal; + openpgp.config.aead_protect = aead_protectVal; } }); it('Parsing armored text with RSA key and ECC subkey', async function() { - const pubKeys = await openpgp.readArmoredKeys(rsa_ecc_pub); + openpgp.config.tolerant = true; + const pubKeys = await openpgp.key.readArmored(rsa_ecc_pub); expect(pubKeys).to.exist; - expect(pubKeys).to.have.length(1); - expect(pubKeys[0].getKeyId().toHex()).to.equal('b8e4105cc9dedc77'); + expect(pubKeys.err).to.not.exist; + expect(pubKeys.keys).to.have.length(1); + expect(pubKeys.keys[0].getKeyId().toHex()).to.equal('b8e4105cc9dedc77'); }); it('Parsing armored text with two keys', async function() { - const pubKeys = await openpgp.readArmoredKeys(twoKeys); + const pubKeys = await openpgp.key.readArmored(twoKeys); expect(pubKeys).to.exist; - expect(pubKeys).to.have.length(2); - expect(pubKeys[0].getKeyId().toHex()).to.equal('4a63613a4d6e4094'); - expect(pubKeys[1].getKeyId().toHex()).to.equal('dbf223e870534df4'); + expect(pubKeys.err).to.not.exist; + expect(pubKeys.keys).to.have.length(2); + expect(pubKeys.keys[0].getKeyId().toHex()).to.equal('4a63613a4d6e4094'); + expect(pubKeys.keys[1].getKeyId().toHex()).to.equal('dbf223e870534df4'); }); it('Parsing armored key with an authorized revocation key in a User ID self-signature', async function() { - const pubKey = await openpgp.readArmoredKey(key_with_authorized_revocation_key); + const { keys: [pubKey] } = await openpgp.key.readArmored(key_with_authorized_revocation_key); await expect(pubKey.getPrimaryUser()).to.be.rejectedWith('This key is intended to be revoked with an authorized key, which OpenPGP.js does not support.'); }); it('Parsing armored key with an authorized revocation key in a direct-key signature', async function() { - const pubKey = await openpgp.readArmoredKey(key_with_authorized_revocation_key_in_separate_sig); + const { keys: [pubKey] } = await openpgp.key.readArmored(key_with_authorized_revocation_key_in_separate_sig); const primaryUser = await pubKey.getPrimaryUser(); expect(primaryUser).to.exist; }); it('Parsing V5 public key packet', async function() { // Manually modified from https://gitlab.com/openpgp-wg/rfc4880bis/blob/00b2092/back.mkd#sample-eddsa-key - const packetBytes = util.hexToUint8Array(` + let packetBytes = openpgp.util.hex_to_Uint8Array(` 98 37 05 53 f3 5f 0b 16 00 00 00 2d 09 2b 06 01 04 01 da 47 0f 01 01 07 40 3f 09 89 94 bd d9 16 ed 40 53 19 79 34 e4 a8 7c 80 73 3a 12 80 d6 2f 80 10 99 2e 43 ee 3b 24 06 `.replace(/\s+/g, '')); - const packetlist = new openpgp.PacketList(); - await packetlist.read(packetBytes, { PublicKeyPacket: openpgp.PublicKeyPacket }); - const key = packetlist[0]; + let packetlist = new openpgp.packet.List(); + await packetlist.read(packetBytes); + let key = packetlist[0]; expect(key).to.exist; }); it('Testing key ID and fingerprint for V4 keys', async function() { - const pubKeysV4 = await openpgp.readArmoredKeys(twoKeys); + const pubKeysV4 = await openpgp.key.readArmored(twoKeys); expect(pubKeysV4).to.exist; - expect(pubKeysV4).to.have.length(2); + expect(pubKeysV4.err).to.not.exist; + expect(pubKeysV4.keys).to.have.length(2); - const pubKeyV4 = pubKeysV4[0]; + const pubKeyV4 = pubKeysV4.keys[0]; expect(pubKeyV4).to.exist; expect(pubKeyV4.getKeyId().toHex()).to.equal('4a63613a4d6e4094'); @@ -2742,19 +2626,25 @@ module.exports = () => describe('Key', function() { }); it('Create new key ID with fromId()', async function() { - const [pubKeyV4] = await openpgp.readArmoredKeys(twoKeys); + const pubKeyV4 = (await openpgp.key.readArmored(twoKeys)).keys[0]; const keyId = pubKeyV4.getKeyId(); const newKeyId = keyId.constructor.fromId(keyId.toHex()); expect(newKeyId.toHex()).to.equal(keyId.toHex()); }); it('Testing key method getSubkeys', async function() { - const pubKey = await openpgp.readArmoredKey(pub_sig_test); + const pubKeys = await openpgp.key.readArmored(pub_sig_test); + + expect(pubKeys).to.exist; + expect(pubKeys.err).to.not.exist; + expect(pubKeys.keys).to.have.length(1); + + const pubKey = pubKeys.keys[0]; expect(pubKey).to.exist; - const packetlist = new openpgp.PacketList(); + const packetlist = new openpgp.packet.List(); - await packetlist.read((await openpgp.unarmor(pub_sig_test)).data, openpgp); + await packetlist.read((await openpgp.armor.decode(pub_sig_test)).data); const subkeys = pubKey.getSubkeys(); expect(subkeys).to.exist; @@ -2764,12 +2654,17 @@ module.exports = () => describe('Key', function() { }); it('Verify status of revoked primary key', async function() { - const pubKey = await openpgp.readArmoredKey(pub_revoked_subkeys); + const pubKey = (await openpgp.key.readArmored(pub_revoked_subkeys)).keys[0]; await expect(pubKey.verifyPrimaryKey()).to.be.rejectedWith('Primary key is revoked'); }); it('Verify status of revoked subkey', async function() { - const pubKey = await openpgp.readArmoredKey(pub_sig_test); + const pubKeys = await openpgp.key.readArmored(pub_sig_test); + expect(pubKeys).to.exist; + expect(pubKeys.err).to.not.exist; + expect(pubKeys.keys).to.have.length(1); + + const pubKey = pubKeys.keys[0]; expect(pubKey).to.exist; expect(pubKey.subKeys).to.exist; expect(pubKey.subKeys).to.have.length(2); @@ -2780,13 +2675,13 @@ module.exports = () => describe('Key', function() { }); it('Verify status of key with non-self revocation signature', async function() { - const pubKey = await openpgp.readArmoredKey(key_with_revoked_third_party_cert); + const { keys: [pubKey] } = await openpgp.key.readArmored(key_with_revoked_third_party_cert); const [selfCertification] = await pubKey.verifyPrimaryUser(); const publicSigningKey = await pubKey.getSigningKey(); expect(selfCertification.keyid.toHex()).to.equal(publicSigningKey.getKeyId().toHex()); expect(selfCertification.valid).to.be.true; - const certifyingKey = await openpgp.readArmoredKey(certifying_key); + const { keys: [certifyingKey] } = await openpgp.key.readArmored(certifying_key); const certifyingSigningKey = await certifyingKey.getSigningKey(); const signatures = await pubKey.verifyPrimaryUser([certifyingKey]); expect(signatures.length).to.equal(2); @@ -2800,7 +2695,7 @@ module.exports = () => describe('Key', function() { }); it('Verify certificate of key with future creation date', async function() { - const pubKey = await openpgp.readArmoredKey(key_created_2030); + const { keys: [pubKey] } = await openpgp.key.readArmored(key_created_2030); const user = pubKey.users[0]; await user.verifyCertificate(pubKey.primaryKey, user.selfCertifications[0], [pubKey], pubKey.primaryKey.created); const verifyAllResult = await user.verifyAllCertifications(pubKey.primaryKey, [pubKey], pubKey.primaryKey.created); @@ -2809,69 +2704,66 @@ module.exports = () => describe('Key', function() { }); it('Evaluate key flags to find valid encryption key packet', async function() { - const pubKey = await openpgp.readArmoredKey(pub_sig_test); + const pubKeys = await openpgp.key.readArmored(pub_sig_test); + expect(pubKeys).to.exist; + expect(pubKeys.err).to.not.exist; + expect(pubKeys.keys).to.have.length(1); + + const pubKey = pubKeys.keys[0]; // remove subkeys pubKey.subKeys = []; // primary key has only key flags for signing await expect(pubKey.getEncryptionKey()).to.be.rejectedWith('Could not find valid encryption key packet in key c076e634d32b498d'); }); - it('should pad an ECDSA P-521 key with shorter secret key', async function() { - const key = await openpgp.readArmoredKey(shortP521Key); - // secret key should be padded - expect(key.keyPacket.privateParams.d.length === 66); - // sanity check - await expect(key.validate()).to.be.fulfilled; - }); - it('should not decrypt using a sign-only RSA key, unless explicitly configured', async function () { - const allowSigningKeyDecryption = openpgp.config.allowInsecureDecryptionWithSigningKeys; - const key = await openpgp.readArmoredKey(rsaSignOnly); + const allowSigningKeyDecryption = openpgp.config.allow_insecure_decryption_with_signing_keys; + const { keys: [key] } = await openpgp.key.readArmored(rsaSignOnly); try { - openpgp.config.allowInsecureDecryptionWithSigningKeys = false; + openpgp.config.allow_insecure_decryption_with_signing_keys = false; await expect(openpgp.decrypt({ - message: await openpgp.readArmoredMessage(encryptedRsaSignOnly), + message: await openpgp.message.readArmored(encryptedRsaSignOnly), privateKeys: key })).to.be.rejectedWith(/Session key decryption failed/); - openpgp.config.allowInsecureDecryptionWithSigningKeys = true; + openpgp.config.allow_insecure_decryption_with_signing_keys = true; await expect(openpgp.decrypt({ - message: await openpgp.readArmoredMessage(encryptedRsaSignOnly), + message: await openpgp.message.readArmored(encryptedRsaSignOnly), privateKeys: key })).to.be.fulfilled; } finally { - openpgp.config.allowInsecureDecryptionWithSigningKeys = allowSigningKeyDecryption; + openpgp.config.allow_insecure_decryption_with_signing_keys = allowSigningKeyDecryption; } }); it('Method getExpirationTime V4 Key', async function() { - const [, pubKey] = await openpgp.readArmoredKeys(twoKeys); + const pubKey = (await openpgp.key.readArmored(twoKeys)).keys[1]; expect(pubKey).to.exist; - expect(pubKey).to.be.an.instanceof(openpgp.Key); + expect(pubKey).to.be.an.instanceof(openpgp.key.Key); const expirationTime = await pubKey.getExpirationTime(); expect(expirationTime.toISOString()).to.be.equal('2018-11-26T10:58:29.000Z'); }); it('Method getExpirationTime expired V4 Key', async function() { - const pubKey = await openpgp.readArmoredKey(expiredKey); + const pubKey = (await openpgp.key.readArmored(expiredKey)).keys[0]; expect(pubKey).to.exist; - expect(pubKey).to.be.an.instanceof(openpgp.Key); + expect(pubKey).to.be.an.instanceof(openpgp.key.Key); const expirationTime = await pubKey.getExpirationTime(); expect(expirationTime.toISOString()).to.be.equal('1970-01-01T00:22:18.000Z'); }); it('Method getExpirationTime V4 SubKey', async function() { - const [, pubKey] = await openpgp.readArmoredKeys(twoKeys); + const pubKey = (await openpgp.key.readArmored(twoKeys)).keys[1]; expect(pubKey).to.exist; - expect(pubKey).to.be.an.instanceof(openpgp.Key); + expect(pubKey).to.be.an.instanceof(openpgp.key.Key); const expirationTime = await pubKey.subKeys[0].getExpirationTime(pubKey.primaryKey); expect(expirationTime.toISOString()).to.be.equal('2018-11-26T10:58:29.000Z'); }); it('Method getExpirationTime V4 Key with capabilities', async function() { - const pubKey = await openpgp.readArmoredKey(priv_key_2000_2008); + const pubKey = (await openpgp.key.readArmored(priv_key_2000_2008)).keys[0]; expect(pubKey).to.exist; - expect(pubKey).to.be.an.instanceof(openpgp.Key); + expect(pubKey).to.be.an.instanceof(openpgp.key.Key); pubKey.users[0].selfCertifications[0].keyFlags = [1]; const expirationTime = await pubKey.getExpirationTime(); expect(expirationTime).to.equal(Infinity); @@ -2880,9 +2772,9 @@ module.exports = () => describe('Key', function() { }); it('Method getExpirationTime V4 Key with capabilities - capable primary key', async function() { - const pubKey = await openpgp.readArmoredKey(priv_key_2000_2008); + const pubKey = (await openpgp.key.readArmored(priv_key_2000_2008)).keys[0]; expect(pubKey).to.exist; - expect(pubKey).to.be.an.instanceof(openpgp.Key); + expect(pubKey).to.be.an.instanceof(openpgp.key.Key); const expirationTime = await pubKey.getExpirationTime(); expect(expirationTime).to.equal(Infinity); const encryptExpirationTime = await pubKey.getExpirationTime('encrypt_sign'); @@ -2890,13 +2782,13 @@ module.exports = () => describe('Key', function() { }); it("decrypt() - throw if key parameters don't correspond", async function() { - const key = await openpgp.readArmoredKey(mismatchingKeyParams); + const { keys: [key] } = await openpgp.key.readArmored(mismatchingKeyParams); await expect(key.decrypt('userpass')).to.be.rejectedWith('Key is invalid'); }); it("decrypt(keyId) - throw if key parameters don't correspond", async function() { - const key = await openpgp.readArmoredKey(mismatchingKeyParams); - const subKeyId = key.subKeys[0].getKeyId(); + const { keys: [key] } = await openpgp.key.readArmored(mismatchingKeyParams); + const subKeyId = key.subKeys[0].getKeyId() await expect(key.decrypt('userpass', subKeyId)).to.be.rejectedWith('Key is invalid'); }); @@ -2906,54 +2798,34 @@ module.exports = () => describe('Key', function() { }); it("validate() - throw if all-gnu-dummy key", async function() { - const key = await openpgp.readArmoredKey(gnuDummyKey); + const { keys: [key] } = await openpgp.key.readArmored(gnuDummyKey); await expect(key.validate()).to.be.rejectedWith('Cannot validate an all-gnu-dummy key'); }); it("validate() - gnu-dummy primary key with signing subkey", async function() { - const key = await openpgp.readArmoredKey(gnuDummyKeySigningSubkey); + const { keys: [key] } = await openpgp.key.readArmored(gnuDummyKeySigningSubkey); await expect(key.validate()).to.not.be.rejected; }); it("validate() - gnu-dummy primary key with encryption subkey", async function() { - const key = await openpgp.readArmoredKey(dsaGnuDummyKeyWithElGamalSubkey); + const { keys: [key] } = await openpgp.key.readArmored(dsaGnuDummyKeyWithElGamalSubkey); await expect(key.validate()).to.not.be.rejected; }); it("validate() - curve ed25519 (eddsa) cannot be used for ecdsa", async function() { - const key = await openpgp.readArmoredKey(eddsaKeyAsEcdsa); + const { keys: [key] } = await openpgp.key.readArmored(eddsaKeyAsEcdsa); await expect(key.validate()).to.be.rejectedWith('Key is invalid'); }); - it("isDecrypted() - should reflect whether all (sub)keys are encrypted", async function() { - const passphrase = '12345678'; - const { key } = await openpgp.generateKey({ userIds: {}, curve: 'ed25519', passphrase }); - expect(key.isDecrypted()).to.be.false; - await key.decrypt(passphrase, key.subKeys[0].getKeyId()); - expect(key.isDecrypted()).to.be.true; - }); - - it("isDecrypted() - gnu-dummy primary key", async function() { - const key = await openpgp.readArmoredKey(gnuDummyKeySigningSubkey); - expect(key.isDecrypted()).to.be.true; - await key.encrypt('12345678'); - expect(key.isDecrypted()).to.be.false; - }); - - it("isDecrypted() - all-gnu-dummy key", async function() { - const key = await openpgp.readArmoredKey(gnuDummyKey); - expect(key.isDecrypted()).to.be.false; - }); - it('makeDummy() - the converted key can be parsed', async function() { - const { key } = await openpgp.generateKey({ userIds: { name: 'dummy', email: 'dummy@alice.com' } }); + const { key: key } = await openpgp.generateKey({ userIds: 'dummy ' }); key.primaryKey.makeDummy(); - const parsedKeys = await openpgp.readArmoredKey(key.armor()); + const parsedKeys = (await openpgp.key.readArmored(key.armor())).keys; expect(parsedKeys).to.not.be.empty; }); it('makeDummy() - the converted key can be encrypted and decrypted', async function() { - const { key } = await openpgp.generateKey({ userIds: { name: 'dummy', email: 'dummy@alice.com' } }); + const { key: key } = await openpgp.generateKey({ userIds: 'dummy ' }); const passphrase = 'passphrase'; key.primaryKey.makeDummy(); expect(key.isDecrypted()).to.be.true; @@ -2964,99 +2836,75 @@ module.exports = () => describe('Key', function() { }); it('makeDummy() - the converted key is valid but can no longer sign', async function() { - const key = await openpgp.readArmoredKey(priv_key_rsa); + const { keys: [key] } = await openpgp.key.readArmored(priv_key_rsa); await key.decrypt('hello world'); expect(key.primaryKey.isDummy()).to.be.false; key.primaryKey.makeDummy(); expect(key.primaryKey.isDummy()).to.be.true; await key.validate(); - await expect(openpgp.reformatKey({ privateKey: key, userIds: { name: 'test', email: 'a@b.com' } })).to.be.rejectedWith(/Cannot reformat a gnu-dummy primary key/); + await expect(openpgp.reformatKey({ privateKey: key, userIds: 'test2 ' })).to.be.rejectedWith(/Missing private key parameters/); }); it('makeDummy() - subkeys of the converted key can still sign', async function() { - const key = await openpgp.readArmoredKey(priv_key_rsa); + const { keys: [key] } = await openpgp.key.readArmored(priv_key_rsa); await key.decrypt('hello world'); expect(key.primaryKey.isDummy()).to.be.false; key.primaryKey.makeDummy(); expect(key.primaryKey.isDummy()).to.be.true; - await expect(openpgp.sign({ message: openpgp.Message.fromText('test'), privateKeys: [key] })).to.be.fulfilled; - }); - - it('makeDummy() - should work for encrypted keys', async function() { - const key = await openpgp.readArmoredKey(priv_key_rsa); - expect(key.primaryKey.isDummy()).to.be.false; - expect(key.primaryKey.makeDummy()).to.not.throw; - expect(key.primaryKey.isDummy()).to.be.true; - // dummy primary key should always be marked as not decrypted - await expect(key.decrypt('hello world')).to.be.fulfilled; - expect(key.primaryKey.isDummy()).to.be.true; - expect(key.primaryKey.isEncrypted === null); - expect(key.primaryKey.isDecrypted()).to.be.false; - await expect(key.encrypt('hello world')).to.be.fulfilled; - expect(key.primaryKey.isDummy()).to.be.true; - expect(key.primaryKey.isEncrypted === null); - expect(key.primaryKey.isDecrypted()).to.be.false; - // confirm that the converted key can be parsed - const parsedKeys = (await openpgp.readArmoredKey(key.armor())).keys; - expect(parsedKeys).to.be.undefined; + await expect(openpgp.sign({ message: openpgp.message.fromText('test'), privateKeys: [key] })).to.be.fulfilled; }); it('clearPrivateParams() - check that private key can no longer be used', async function() { - const key = await openpgp.readArmoredKey(priv_key_rsa); + const { keys: [key] } = await openpgp.key.readArmored(priv_key_rsa); await key.decrypt('hello world'); await key.clearPrivateParams(); await expect(key.validate()).to.be.rejectedWith('Key is not decrypted'); }); it('clearPrivateParams() - detect that private key parameters were removed', async function() { - const key = await openpgp.readArmoredKey(priv_key_rsa); + const { keys: [key] } = await openpgp.key.readArmored(priv_key_rsa); await key.decrypt('hello world'); - const signingKeyPacket = key.subKeys[0].keyPacket; - const privateParams = signingKeyPacket.privateParams; + const params = key.primaryKey.params; await key.clearPrivateParams(); key.primaryKey.isEncrypted = false; - key.primaryKey.privateParams = privateParams; + key.primaryKey.params = params; key.subKeys[0].keyPacket.isEncrypted = false; - key.subKeys[0].keyPacket.privateParams = privateParams; - await expect(key.validate()).to.be.rejectedWith('Key is invalid'); + key.subKeys[0].keyPacket.params = params; + await expect(key.validate()).to.be.rejectedWith('Missing key parameters'); }); it('clearPrivateParams() - detect that private key parameters were zeroed out', async function() { - const key = await openpgp.readArmoredKey(priv_key_rsa); + const { keys: [key] } = await openpgp.key.readArmored(priv_key_rsa); await key.decrypt('hello world'); - const signingKeyPacket = key.subKeys[0].keyPacket; - const privateParams = {}; - Object.entries(signingKeyPacket.privateParams).forEach(([name, value]) => { - privateParams[name] = value; - }); + const params = key.primaryKey.params.slice(); await key.clearPrivateParams(); key.primaryKey.isEncrypted = false; - key.primaryKey.privateParams = privateParams; + key.primaryKey.params = params; key.subKeys[0].keyPacket.isEncrypted = false; - key.subKeys[0].keyPacket.privateParams = privateParams; + key.subKeys[0].keyPacket.params = params; await expect(key.validate()).to.be.rejectedWith('Key is invalid'); }); it('update() - throw error if fingerprints not equal', async function() { - const keys = await openpgp.readArmoredKeys(twoKeys); + const keys = (await openpgp.key.readArmored(twoKeys)).keys; await expect(keys[0].update.bind( keys[0], keys[1] )()).to.be.rejectedWith('Key update method: fingerprints of keys not equal'); }); it('update() - merge revocation signatures', async function() { - const source = await openpgp.readArmoredKey(pub_revoked_subkeys); - const dest = await openpgp.readArmoredKey(pub_revoked_subkeys); + const source = (await openpgp.key.readArmored(pub_revoked_subkeys)).keys[0]; + const dest = (await openpgp.key.readArmored(pub_revoked_subkeys)).keys[0]; expect(source.revocationSignatures).to.exist; dest.revocationSignatures = []; return dest.update(source).then(() => { - expect(dest.revocationSignatures[0]).to.exist.and.be.an.instanceof(openpgp.SignaturePacket); + expect(dest.revocationSignatures[0]).to.exist.and.be.an.instanceof(openpgp.packet.Signature); }); }); it('update() - merge user', async function() { - const source = await openpgp.readArmoredKey(pub_sig_test); - const dest = await openpgp.readArmoredKey(pub_sig_test); + const source = (await openpgp.key.readArmored(pub_sig_test)).keys[0]; + const dest = (await openpgp.key.readArmored(pub_sig_test)).keys[0]; expect(source.users[1]).to.exist; dest.users.pop(); return dest.update(source).then(() => { @@ -3066,8 +2914,8 @@ module.exports = () => describe('Key', function() { }); it('update() - merge user - other and certification revocation signatures', async function() { - const source = await openpgp.readArmoredKey(pub_sig_test); - const dest = await openpgp.readArmoredKey(pub_sig_test); + const source = (await openpgp.key.readArmored(pub_sig_test)).keys[0]; + const dest = (await openpgp.key.readArmored(pub_sig_test)).keys[0]; expect(source.users[1].otherCertifications).to.exist; expect(source.users[1].revocationSignatures).to.exist; dest.users[1].otherCertifications = []; @@ -3081,8 +2929,8 @@ module.exports = () => describe('Key', function() { }); it('update() - merge subkey', async function() { - const source = await openpgp.readArmoredKey(pub_sig_test); - const dest = await openpgp.readArmoredKey(pub_sig_test); + const source = (await openpgp.key.readArmored(pub_sig_test)).keys[0]; + const dest = (await openpgp.key.readArmored(pub_sig_test)).keys[0]; expect(source.subKeys[1]).to.exist; dest.subKeys.pop(); return dest.update(source).then(() => { @@ -3094,8 +2942,8 @@ module.exports = () => describe('Key', function() { }); it('update() - merge subkey - revocation signature', async function() { - const source = await openpgp.readArmoredKey(pub_sig_test); - const dest = await openpgp.readArmoredKey(pub_sig_test); + const source = (await openpgp.key.readArmored(pub_sig_test)).keys[0]; + const dest = (await openpgp.key.readArmored(pub_sig_test)).keys[0]; expect(source.subKeys[0].revocationSignatures).to.exist; dest.subKeys[0].revocationSignatures = []; return dest.update(source).then(() => { @@ -3105,8 +2953,8 @@ module.exports = () => describe('Key', function() { }); it('update() - merge private key into public key', async function() { - const source = await openpgp.readArmoredKey(priv_key_rsa); - const [dest] = await openpgp.readArmoredKeys(twoKeys); + const source = (await openpgp.key.readArmored(priv_key_rsa)).keys[0]; + const dest = (await openpgp.key.readArmored(twoKeys)).keys[0]; expect(dest.isPublic()).to.be.true; return dest.update(source).then(() => { expect(dest.isPrivate()).to.be.true; @@ -3125,31 +2973,27 @@ module.exports = () => describe('Key', function() { }); it('update() - merge private key into public key - no subkeys', async function() { - const source = await openpgp.readArmoredKey(priv_key_rsa); - const [dest] = await openpgp.readArmoredKeys(twoKeys); + const source = (await openpgp.key.readArmored(priv_key_rsa)).keys[0]; + const dest = (await openpgp.key.readArmored(twoKeys)).keys[0]; source.subKeys = []; dest.subKeys = []; expect(dest.isPublic()).to.be.true; - - await dest.update(source); - expect(dest.isPrivate()).to.be.true; - - const { selfCertification: destCertification } = await dest.getPrimaryUser(); - const { selfCertification: sourceCertification } = await source.getPrimaryUser(); - destCertification.verified = null; - sourceCertification.verified = null; - await dest.verifyPrimaryKey().then(async () => expect(destCertification.verified).to.be.true); - await source.verifyPrimaryKey().then(async () => expect(sourceCertification.verified).to.be.true); - - destCertification.verified = null; - sourceCertification.verified = null; - await dest.users[0].verify(dest.primaryKey).then(async () => expect(destCertification.verified).to.be.true); - await source.users[0].verify(source.primaryKey).then(async () => expect(sourceCertification.verified).to.be.true); + return dest.update(source).then(() => { + expect(dest.isPrivate()).to.be.true; + return Promise.all([ + dest.verifyPrimaryKey().then(result => { + expect(source.verifyPrimaryKey()).to.eventually.equal(result); + }), + dest.users[0].verify(dest.primaryKey).then(result => { + expect(source.users[0].verify(source.primaryKey)).to.eventually.equal(result); + }) + ]); + }); }); it('update() - merge private key into public key - mismatch throws error', async function() { - const source = await openpgp.readArmoredKey(priv_key_rsa); - const [dest] = await openpgp.readArmoredKeys(twoKeys); + const source = (await openpgp.key.readArmored(priv_key_rsa)).keys[0]; + const dest = (await openpgp.key.readArmored(twoKeys)).keys[0]; source.subKeys = []; expect(dest.subKeys).to.exist; expect(dest.isPublic()).to.be.true; @@ -3158,8 +3002,8 @@ module.exports = () => describe('Key', function() { }); it('update() - merge subkey binding signatures', async function() { - const source = await openpgp.readArmoredKey(pgp_desktop_pub); - const dest = await openpgp.readArmoredKey(pgp_desktop_priv); + const source = (await openpgp.key.readArmored(pgp_desktop_pub)).keys[0]; + const dest = (await openpgp.key.readArmored(pgp_desktop_priv)).keys[0]; expect(source.subKeys[0].bindingSignatures[0]).to.exist; await source.subKeys[0].verify(source.primaryKey); expect(dest.subKeys[0].bindingSignatures[0]).to.not.exist; @@ -3169,8 +3013,8 @@ module.exports = () => describe('Key', function() { }); it('update() - merge multiple subkey binding signatures', async function() { - const source = await openpgp.readArmoredKey(multipleBindingSignatures); - const dest = await openpgp.readArmoredKey(multipleBindingSignatures); + const source = (await openpgp.key.readArmored(multipleBindingSignatures)).keys[0]; + const dest = (await openpgp.key.readArmored(multipleBindingSignatures)).keys[0]; // remove last subkey binding signature of destination subkey dest.subKeys[0].bindingSignatures.length = 1; expect((await source.subKeys[0].getExpirationTime(source.primaryKey)).toISOString()).to.equal('2015-10-18T07:41:30.000Z'); @@ -3183,16 +3027,16 @@ module.exports = () => describe('Key', function() { }); it('revoke() - primary key', async function() { - const privKey = await openpgp.readArmoredKey(priv_key_arm2); + const privKey = (await openpgp.key.readArmored(priv_key_arm2)).keys[0]; await privKey.decrypt('hello world'); await privKey.revoke({ - flag: openpgp.enums.reasonForRevocation.keyRetired, + flag: openpgp.enums.reasonForRevocation.key_retired, string: 'Testing key revocation' }).then(async revKey => { expect(revKey.revocationSignatures).to.exist.and.have.length(1); - expect(revKey.revocationSignatures[0].signatureType).to.equal(openpgp.enums.signature.keyRevocation); - expect(revKey.revocationSignatures[0].reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.keyRetired); + expect(revKey.revocationSignatures[0].signatureType).to.equal(openpgp.enums.signature.key_revocation); + expect(revKey.revocationSignatures[0].reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.key_retired); expect(revKey.revocationSignatures[0].reasonForRevocationString).to.equal('Testing key revocation'); await privKey.verifyPrimaryKey(); @@ -3201,17 +3045,17 @@ module.exports = () => describe('Key', function() { }); it('revoke() - subkey', async function() { - const pubKey = await openpgp.readArmoredKey(pub_key_arm2); - const privKey = await openpgp.readArmoredKey(priv_key_arm2); + const pubKey = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; + const privKey = (await openpgp.key.readArmored(priv_key_arm2)).keys[0]; await privKey.decrypt('hello world'); const subKey = pubKey.subKeys[0]; await subKey.revoke(privKey.primaryKey, { - flag: openpgp.enums.reasonForRevocation.keySuperseded + flag: openpgp.enums.reasonForRevocation.key_superseded }).then(async revKey => { expect(revKey.revocationSignatures).to.exist.and.have.length(1); - expect(revKey.revocationSignatures[0].signatureType).to.equal(openpgp.enums.signature.subkeyRevocation); - expect(revKey.revocationSignatures[0].reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.keySuperseded); + expect(revKey.revocationSignatures[0].signatureType).to.equal(openpgp.enums.signature.subkey_revocation); + expect(revKey.revocationSignatures[0].reasonForRevocationFlag).to.equal(openpgp.enums.reasonForRevocation.key_superseded); expect(revKey.revocationSignatures[0].reasonForRevocationString).to.equal(''); await subKey.verify(pubKey.primaryKey); @@ -3220,27 +3064,27 @@ module.exports = () => describe('Key', function() { }); it('applyRevocationCertificate() should produce the same revoked key as GnuPG', async function() { - const pubKey = await openpgp.readArmoredKey(pub_key_arm4); + const pubKey = (await openpgp.key.readArmored(pub_key_arm4)).keys[0]; return pubKey.applyRevocationCertificate(revocation_certificate_arm4).then(async revKey => { - expect(revKey.armor()).to.equal((await openpgp.readArmoredKey(revoked_key_arm4)).armor()); + expect(revKey.armor()).to.equal((await openpgp.key.readArmored(revoked_key_arm4)).keys[0].armor()); }); }); it('getRevocationCertificate() should produce the same revocation certificate as GnuPG', async function() { - const revKey = await openpgp.readArmoredKey(revoked_key_arm4); + const revKey = (await openpgp.key.readArmored(revoked_key_arm4)).keys[0]; const revocationCertificate = await revKey.getRevocationCertificate(); - const input = await openpgp.unarmor(revocation_certificate_arm4); - const packetlist = new openpgp.PacketList(); - await packetlist.read(input.data, { SignaturePacket: openpgp.SignaturePacket }); - const armored = openpgp.armor(openpgp.enums.armor.publicKey, packetlist.write()); + const input = await openpgp.armor.decode(revocation_certificate_arm4); + const packetlist = new openpgp.packet.List(); + await packetlist.read(input.data); + const armored = openpgp.armor.encode(openpgp.enums.armor.public_key, packetlist.write()); - expect(revocationCertificate.replace(/^Comment: .*$\n/mg, '')).to.equal(armored.replace(/^Comment: .*$\n/mg, '')); + expect(revocationCertificate.replace(/^Comment: .*$\r\n/mg, '')).to.equal(armored.replace(/^Comment: .*$\r\n/mg, '')); }); it('getRevocationCertificate() should have an appropriate comment', async function() { - const revKey = await openpgp.readArmoredKey(revoked_key_arm4); + const revKey = (await openpgp.key.readArmored(revoked_key_arm4)).keys[0]; const revocationCertificate = await revKey.getRevocationCertificate(); expect(revocationCertificate).to.match(/Comment: This is a revocation certificate/); @@ -3248,44 +3092,44 @@ module.exports = () => describe('Key', function() { }); it("getPreferredAlgo('symmetric') - one key - AES256", async function() { - const [key1] = await openpgp.readArmoredKeys(twoKeys); - const prefAlgo = await key.getPreferredAlgo('symmetric', [key1]); + const key1 = (await openpgp.key.readArmored(twoKeys)).keys[0]; + const prefAlgo = await openpgp.key.getPreferredAlgo('symmetric', [key1]); expect(prefAlgo).to.equal(openpgp.enums.symmetric.aes256); }); it("getPreferredAlgo('symmetric') - two key - AES192", async function() { - const keys = await openpgp.readArmoredKeys(twoKeys); + const keys = (await openpgp.key.readArmored(twoKeys)).keys; const key1 = keys[0]; const key2 = keys[1]; const primaryUser = await key2.getPrimaryUser(); primaryUser.selfCertification.preferredSymmetricAlgorithms = [6,8,3]; - const prefAlgo = await key.getPreferredAlgo('symmetric', [key1, key2]); + const prefAlgo = await openpgp.key.getPreferredAlgo('symmetric', [key1, key2]); expect(prefAlgo).to.equal(openpgp.enums.symmetric.aes192); }); it("getPreferredAlgo('symmetric') - two key - one without pref", async function() { - const keys = await openpgp.readArmoredKeys(twoKeys); + const keys = (await openpgp.key.readArmored(twoKeys)).keys; const key1 = keys[0]; const key2 = keys[1]; const primaryUser = await key2.getPrimaryUser(); primaryUser.selfCertification.preferredSymmetricAlgorithms = null; - const prefAlgo = await key.getPreferredAlgo('symmetric', [key1, key2]); + const prefAlgo = await openpgp.key.getPreferredAlgo('symmetric', [key1, key2]); expect(prefAlgo).to.equal(openpgp.enums.symmetric.aes128); }); it("getPreferredAlgo('aead') - one key - OCB", async function() { - const [key1] = await openpgp.readArmoredKeys(twoKeys); + const key1 = (await openpgp.key.readArmored(twoKeys)).keys[0]; const primaryUser = await key1.getPrimaryUser(); primaryUser.selfCertification.features = [7]; // Monkey-patch AEAD feature flag primaryUser.selfCertification.preferredAeadAlgorithms = [2,1]; - const prefAlgo = await key.getPreferredAlgo('aead', [key1]); + const prefAlgo = await openpgp.key.getPreferredAlgo('aead', [key1]); expect(prefAlgo).to.equal(openpgp.enums.aead.ocb); - const supported = await key.isAeadSupported([key1]); + const supported = await openpgp.key.isAeadSupported([key1]); expect(supported).to.be.true; }); it("getPreferredAlgo('aead') - two key - one without pref", async function() { - const keys = await openpgp.readArmoredKeys(twoKeys); + const keys = (await openpgp.key.readArmored(twoKeys)).keys; const key1 = keys[0]; const key2 = keys[1]; const primaryUser = await key1.getPrimaryUser(); @@ -3293,40 +3137,40 @@ module.exports = () => describe('Key', function() { primaryUser.selfCertification.preferredAeadAlgorithms = [2,1]; const primaryUser2 = await key2.getPrimaryUser(); primaryUser2.selfCertification.features = [7]; // Monkey-patch AEAD feature flag - const prefAlgo = await key.getPreferredAlgo('aead', [key1, key2]); + const prefAlgo = await openpgp.key.getPreferredAlgo('aead', [key1, key2]); expect(prefAlgo).to.equal(openpgp.enums.aead.eax); - const supported = await key.isAeadSupported([key1, key2]); + const supported = await openpgp.key.isAeadSupported([key1, key2]); expect(supported).to.be.true; }); it("getPreferredAlgo('aead') - two key - one with no support", async function() { - const keys = await openpgp.readArmoredKeys(twoKeys); + const keys = (await openpgp.key.readArmored(twoKeys)).keys; const key1 = keys[0]; const key2 = keys[1]; const primaryUser = await key1.getPrimaryUser(); primaryUser.selfCertification.features = [7]; // Monkey-patch AEAD feature flag primaryUser.selfCertification.preferredAeadAlgorithms = [2,1]; - const prefAlgo = await key.getPreferredAlgo('aead', [key1, key2]); + const prefAlgo = await openpgp.key.getPreferredAlgo('aead', [key1, key2]); expect(prefAlgo).to.equal(openpgp.enums.aead.eax); - const supported = await key.isAeadSupported([key1, key2]); + const supported = await openpgp.key.isAeadSupported([key1, key2]); expect(supported).to.be.false; }); it('User attribute packet read & write', async function() { - const key = await openpgp.readArmoredKey(user_attr_key); - const key2 = await openpgp.readArmoredKey(key.armor()); + const key = (await openpgp.key.readArmored(user_attr_key)).keys[0]; + const key2 = (await openpgp.key.readArmored(key.armor())).keys[0]; expect(key.users[1].userAttribute).eql(key2.users[1].userAttribute); }); it('getPrimaryUser()', async function() { - const key = await openpgp.readArmoredKey(pub_sig_test); + const key = (await openpgp.key.readArmored(pub_sig_test)).keys[0]; const primUser = await key.getPrimaryUser(); expect(primUser).to.exist; expect(primUser.user.userId.userid).to.equal('Signature Test '); expect(primUser.user.userId.name).to.equal('Signature Test'); expect(primUser.user.userId.email).to.equal('signature@test.com'); expect(primUser.user.userId.comment).to.equal(''); - expect(primUser.selfCertification).to.be.an.instanceof(openpgp.SignaturePacket); + expect(primUser.selfCertification).to.be.an.instanceof(openpgp.packet.Signature); }); it('getPrimaryUser() should throw if no UserIDs are bound', async function() { @@ -3342,109 +3186,98 @@ Vz/bMCJoAShgybW1r6kRWejybzIjFSLnx/YA/iLZeo5UNdlXRJco+15RbFiNSAbw VYGdb3eNlV8CfoEC =FYbP -----END PGP PRIVATE KEY BLOCK-----`; - const key = await openpgp.readArmoredKey(keyWithoutUserID); + const key = (await openpgp.key.readArmored(keyWithoutUserID)).keys[0]; await expect(key.getPrimaryUser()).to.be.rejectedWith('Could not find valid self-signature in key 3ce893915c44212f'); }); - it('Generate session key - latest created user', async function() { - const publicKey = await openpgp.readArmoredKey(multi_uid_key); - const privateKey = await openpgp.readArmoredKey(priv_key_rsa); + it('Encrypt - latest created user', async function() { + let publicKey = (await openpgp.key.readArmored(multi_uid_key)).keys[0]; + const privateKey = (await openpgp.key.readArmored(priv_key_rsa)).keys[0]; await privateKey.decrypt('hello world'); // Set second user to prefer aes128. We should select this user by default, since it was created later. publicKey.users[1].selfCertifications[0].preferredSymmetricAlgorithms = [openpgp.enums.symmetric.aes128]; - const sessionKey = await openpgp.generateSessionKey({ publicKeys: publicKey }); - expect(sessionKey.algorithm).to.equal('aes128'); + const encrypted = await openpgp.encrypt({message: openpgp.message.fromText('hello'), publicKeys: publicKey, privateKeys: privateKey, armor: false}); + expect(encrypted.message.packets[0].sessionKeyAlgorithm).to.equal('aes128'); }); - it('Generate session key - primary user', async function() { - const publicKey = await openpgp.readArmoredKey(multi_uid_key); - const privateKey = await openpgp.readArmoredKey(priv_key_rsa); + it('Encrypt - primary user', async function() { + let publicKey = (await openpgp.key.readArmored(multi_uid_key)).keys[0]; + const privateKey = (await openpgp.key.readArmored(priv_key_rsa)).keys[0]; await privateKey.decrypt('hello world'); // Set first user to primary. We should select this user by default. publicKey.users[0].selfCertifications[0].isPrimaryUserID = true; // Set first user to prefer aes128. publicKey.users[0].selfCertifications[0].preferredSymmetricAlgorithms = [openpgp.enums.symmetric.aes128]; - const sessionKey = await openpgp.generateSessionKey({ publicKeys: publicKey }); - expect(sessionKey.algorithm).to.equal('aes128'); + const encrypted = await openpgp.encrypt({message: openpgp.message.fromText('hello'), publicKeys: publicKey, privateKeys: privateKey, armor: false}); + expect(encrypted.message.packets[0].sessionKeyAlgorithm).to.equal('aes128'); }); - it('Generate session key - specific user', async function() { - const publicKey = await openpgp.readArmoredKey(multi_uid_key); - const privateKey = await openpgp.readArmoredKey(priv_key_rsa); + it('Encrypt - specific user', async function() { + let publicKey = (await openpgp.key.readArmored(multi_uid_key)).keys[0]; + const privateKey = (await openpgp.key.readArmored(priv_key_rsa)).keys[0]; await privateKey.decrypt('hello world'); // Set first user to primary. We won't select this user, this is to test that. publicKey.users[0].selfCertifications[0].isPrimaryUserID = true; // Set second user to prefer aes128. We will select this user. publicKey.users[1].selfCertifications[0].preferredSymmetricAlgorithms = [openpgp.enums.symmetric.aes128]; - const sessionKey = await openpgp.generateSessionKey({ publicKeys: publicKey, toUserIds: { name: 'Test User', email: 'b@c.com' } }); - expect(sessionKey.algorithm).to.equal('aes128'); - await openpgp.encrypt({ message: openpgp.Message.fromText('hello'), publicKeys: publicKey, privateKeys: privateKey, toUserIds: { name: 'Test User', email: 'b@c.com' }, armor: false }); - await expect(openpgp.encrypt({ message: openpgp.Message.fromText('hello'), publicKeys: publicKey, privateKeys: privateKey, toUserIds: { name: 'Test User', email: 'c@c.com' }, armor: false })).to.be.rejectedWith('Could not find user that matches that user ID'); - }); - - it('Fails to encrypt to User ID-less key', async function() { - const publicKey = await openpgp.readArmoredKey(uidlessKey); - expect(publicKey.users.length).to.equal(0); - const privateKey = await openpgp.readArmoredKey(uidlessKey); - await privateKey.decrypt('correct horse battery staple'); - await expect(openpgp.encrypt({ message: openpgp.Message.fromText('hello'), publicKeys: publicKey, privateKeys: privateKey, armor: false })).to.be.rejectedWith('Could not find primary user'); + const encrypted = await openpgp.encrypt({message: openpgp.message.fromText('hello'), publicKeys: publicKey, privateKeys: privateKey, toUserIds: {name: 'Test User', email: 'b@c.com'}, armor: false}); + expect(encrypted.message.packets[0].sessionKeyAlgorithm).to.equal('aes128'); + await expect(openpgp.encrypt({message: openpgp.message.fromText('hello'), publicKeys: publicKey, privateKeys: privateKey, toUserIds: {name: 'Test User', email: 'c@c.com'}, armor: false})).to.be.rejectedWith('Could not find user that matches that user ID'); }); it('Sign - specific user', async function() { - const publicKey = await openpgp.readArmoredKey(multi_uid_key); - const privateKey = await openpgp.readArmoredKey(priv_key_rsa); + let publicKey = (await openpgp.key.readArmored(multi_uid_key)).keys[0]; + const privateKey = (await openpgp.key.readArmored(priv_key_rsa)).keys[0]; await privateKey.decrypt('hello world'); - const privateKeyClone = await openpgp.readArmoredKey(priv_key_rsa); + const privateKeyClone = (await openpgp.key.readArmored(priv_key_rsa)).keys[0]; // Duplicate user privateKey.users.push(privateKeyClone.users[0]); // Set first user to primary. We won't select this user, this is to test that. privateKey.users[0].selfCertifications[0].isPrimaryUserID = true; // Change userid of the first user so that we don't select it. This also makes this user invalid. - privateKey.users[0].userId = openpgp.UserIDPacket.fromObject({ name: 'Test User', email: 'b@c.com' }); + privateKey.users[0].userId.parse('Test User '); // Set second user to prefer aes128. We will select this user. privateKey.users[1].selfCertifications[0].preferredHashAlgorithms = [openpgp.enums.hash.sha512]; - const signed = await openpgp.sign({ message: openpgp.Message.fromText('hello'), privateKeys: privateKey, fromUserIds: { name: 'Test McTestington', email: 'test@example.com' }, armor: false }); - const signature = await openpgp.readMessage(signed); - expect(signature.packets[0].hashAlgorithm).to.equal(openpgp.enums.hash.sha512); - const encrypted = await openpgp.encrypt({ message: openpgp.Message.fromText('hello'), passwords: 'test', privateKeys: privateKey, fromUserIds: { name: 'Test McTestington', email: 'test@example.com' }, armor: false }); - const { signatures } = await openpgp.decrypt({ message: await openpgp.readMessage(encrypted), passwords: 'test' }); - expect(signatures[0].signature.packets[0].hashAlgorithm).to.equal(openpgp.enums.hash.sha512); - await expect(openpgp.encrypt({ message: openpgp.Message.fromText('hello'), publicKeys: publicKey, privateKeys: privateKey, fromUserIds: { name: 'Not Test McTestington', email: 'test@example.com' }, armor: false })).to.be.rejectedWith('Could not find user that matches that user ID'); + const signed = await openpgp.sign({message: openpgp.cleartext.fromText('hello'), privateKeys: privateKey, fromUserIds: {name: 'Test McTestington', email: 'test@example.com'}, armor: false}); + expect(signed.message.signature.packets[0].hashAlgorithm).to.equal(openpgp.enums.hash.sha512); + const encrypted = await openpgp.encrypt({message: openpgp.message.fromText('hello'), publicKeys: publicKey, privateKeys: privateKey, fromUserIds: {name: 'Test McTestington', email: 'test@example.com'}, detached: true, armor: false}); + expect(encrypted.signature.packets[0].hashAlgorithm).to.equal(openpgp.enums.hash.sha512); + await expect(openpgp.encrypt({message: openpgp.message.fromText('hello'), publicKeys: publicKey, privateKeys: privateKey, fromUserIds: {name: 'Not Test McTestington', email: 'test@example.com'}, detached: true, armor: false})).to.be.rejectedWith('Could not find user that matches that user ID'); }); it('Find a valid subkey binding signature among many invalid ones', async function() { - const key = await openpgp.readArmoredKey(valid_binding_sig_among_many_expired_sigs_pub); + const key = (await openpgp.key.readArmored(valid_binding_sig_among_many_expired_sigs_pub)).keys[0]; expect(await key.getEncryptionKey()).to.not.be.null; }); it('Selects the most recent subkey binding signature', async function() { - const key = await openpgp.readArmoredKey(multipleBindingSignatures); + const key = (await openpgp.key.readArmored(multipleBindingSignatures)).keys[0]; expect((await key.subKeys[0].getExpirationTime(key.primaryKey)).toISOString()).to.equal('2015-10-18T07:41:30.000Z'); }); it('Selects the most recent non-expired subkey binding signature', async function() { - const key = await openpgp.readArmoredKey(multipleBindingSignatures); + const key = (await openpgp.key.readArmored(multipleBindingSignatures)).keys[0]; key.subKeys[0].bindingSignatures[1].signatureNeverExpires = false; key.subKeys[0].bindingSignatures[1].signatureExpirationTime = 0; expect((await key.subKeys[0].getExpirationTime(key.primaryKey)).toISOString()).to.equal('2018-09-07T06:03:37.000Z'); }); it('Selects the most recent valid subkey binding signature', async function() { - const key = await openpgp.readArmoredKey(multipleBindingSignatures); + const key = (await openpgp.key.readArmored(multipleBindingSignatures)).keys[0]; key.subKeys[0].bindingSignatures[1].signatureData[0]++; expect((await key.subKeys[0].getExpirationTime(key.primaryKey)).toISOString()).to.equal('2018-09-07T06:03:37.000Z'); }); it('Handles a key with no valid subkey binding signatures gracefully', async function() { - const key = await openpgp.readArmoredKey(multipleBindingSignatures); + const key = (await openpgp.key.readArmored(multipleBindingSignatures)).keys[0]; key.subKeys[0].bindingSignatures[0].signatureData[0]++; key.subKeys[0].bindingSignatures[1].signatureData[0]++; expect(await key.subKeys[0].getExpirationTime(key.primaryKey)).to.be.null; }); it('Reject encryption with revoked primary user', async function() { - const key = await openpgp.readArmoredKey(pub_revoked_subkeys); - return openpgp.encrypt({ publicKeys: [key], message: openpgp.Message.fromText('random data') }).then(() => { + const key = (await openpgp.key.readArmored(pub_revoked_subkeys)).keys[0]; + return openpgp.encrypt({publicKeys: [key], message: openpgp.message.fromText('random data')}).then(() => { throw new Error('encryptSessionKey should not encrypt with revoked public key'); }).catch(function(error) { expect(error.message).to.equal('Error encrypting message: Primary user is revoked'); @@ -3452,10 +3285,10 @@ VYGdb3eNlV8CfoEC }); it('Reject encryption with revoked subkey', async function() { - const key = await openpgp.readArmoredKey(pub_revoked_subkeys); + const key = (await openpgp.key.readArmored(pub_revoked_subkeys)).keys[0]; key.revocationSignatures = []; key.users[0].revocationSignatures = []; - return openpgp.encrypt({ publicKeys: [key], message: openpgp.Message.fromText('random data'), date: new Date(1386842743000) }).then(() => { + return openpgp.encrypt({publicKeys: [key], message: openpgp.message.fromText('random data'), date: new Date(1386842743000)}).then(() => { throw new Error('encryptSessionKey should not encrypt with revoked public key'); }).catch(function(error) { expect(error.message).to.equal('Error encrypting message: Could not find valid encryption key packet in key ' + key.getKeyId().toHex() + ': Subkey is revoked'); @@ -3463,8 +3296,8 @@ VYGdb3eNlV8CfoEC }); it('Reject encryption with key revoked with appended revocation cert', async function() { - const key = await openpgp.readArmoredKey(pub_revoked_with_cert); - return openpgp.encrypt({ publicKeys: [key], message: openpgp.Message.fromText('random data') }).then(() => { + const key = (await openpgp.key.readArmored(pub_revoked_with_cert)).keys[0]; + return openpgp.encrypt({publicKeys: [key], message: openpgp.message.fromText('random data')}).then(() => { throw new Error('encryptSessionKey should not encrypt with revoked public key'); }).catch(function(error) { expect(error.message).to.equal('Error encrypting message: Primary key is revoked'); @@ -3472,8 +3305,8 @@ VYGdb3eNlV8CfoEC }); it('Merge key with another key with non-ID user attributes', async function() { - const key = await openpgp.readArmoredKey(mergeKey1); - const updateKey = await openpgp.readArmoredKey(mergeKey2); + const key = (await openpgp.key.readArmored(mergeKey1)).keys[0]; + const updateKey = (await openpgp.key.readArmored(mergeKey2)).keys[0]; expect(key).to.exist; expect(updateKey).to.exist; expect(key.users).to.have.length(1); @@ -3487,275 +3320,190 @@ VYGdb3eNlV8CfoEC it("Should throw when trying to encrypt a key that's already encrypted", async function() { await expect((async function() { - const { privateKeyArmored } = await openpgp.generateKey({ userIds: [{ email: 'hello@user.com' }], passphrase: 'pass' }); - const k = await openpgp.readArmoredKey(privateKeyArmored); + let { privateKeyArmored } = await openpgp.generateKey({ userIds: [{ email: 'hello@user.com' }], passphrase: 'pass', numBits: openpgp.util.getWebCryptoAll() ? 2048 : 512 }); + let { keys: [k] } = await openpgp.key.readArmored(privateKeyArmored); await k.decrypt('pass'); await k.encrypt('pass'); await k.encrypt('pass'); })()).to.be.rejectedWith('Key packet is already encrypted'); }); +}); - describe('addSubkey functionality testing', function() { - const rsaBits = 1024; - const rsaOpt = { type: 'rsa' }; - let minRsaBits; - beforeEach(function() { - minRsaBits = openpgp.config.minRsaBits; - openpgp.config.minRsaBits = rsaBits; - }); - afterEach(function() { - openpgp.config.minRsaBits = minRsaBits; - }); - - it('create and add a new rsa subkey to stored rsa key', async function() { - const privateKey = await openpgp.readArmoredKey(priv_key_rsa); - await privateKey.decrypt('hello world'); - const total = privateKey.subKeys.length; - let newPrivateKey = await privateKey.addSubkey(rsaOpt); - const armoredKey = newPrivateKey.armor(); - newPrivateKey = await openpgp.readArmoredKey(armoredKey); - const subKey = newPrivateKey.subKeys[total]; - expect(subKey).to.exist; - expect(newPrivateKey.subKeys.length).to.be.equal(total + 1); - const subkeyN = subKey.keyPacket.publicParams.n; - const pkN = privateKey.primaryKey.publicParams.n; - expect(subkeyN.length).to.be.equal(pkN.length); - expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsaEncryptSign'); - expect(subKey.getAlgorithmInfo().bits).to.be.equal(privateKey.getAlgorithmInfo().bits); - await subKey.verify(newPrivateKey.primaryKey); - }); - - it('Add a new default subkey to an rsaSign key', async function() { - const userId = { name: 'test', email: 'a@b.com' }; - const opt = { type: 'rsa', rsaBits, userIds: [userId], subkeys: [] }; - const { key } = await openpgp.generateKey(opt); - expect(key.subKeys).to.have.length(0); - key.keyPacket.algorithm = "rsaSign"; - const newKey = await key.addSubkey(); - expect(newKey.subKeys[0].getAlgorithmInfo().algorithm).to.equal('rsaEncryptSign'); - }); - - it('Add a new default subkey to an ecc key', async function() { - const userId = { name: 'test', email: 'a@b.com' }; - const opt = { type: 'ecc', userIds: [userId], subkeys: [] }; - const { key } = await openpgp.generateKey(opt); - expect(key.subKeys).to.have.length(0); - const newKey = await key.addSubkey(); - expect(newKey.subKeys[0].getAlgorithmInfo().algorithm).to.equal('ecdh'); - expect(newKey.subKeys[0].getAlgorithmInfo().curve).to.equal('curve25519'); - }); - - it('Add a new default subkey to a dsa key', async function() { - const key = await openpgp.readArmoredKey(dsaPrivateKey); - const total = key.subKeys.length; - const newKey = await key.addSubkey(); - expect(newKey.subKeys[total].getAlgorithmInfo().algorithm).to.equal('rsaEncryptSign'); - expect(newKey.subKeys[total].getAlgorithmInfo().bits).to.equal(Math.max(key.getAlgorithmInfo().bits, openpgp.config.minRsaBits)); - }); - - it('should throw when trying to encrypt a subkey separately from key', async function() { - const privateKey = await openpgp.readArmoredKey(priv_key_rsa); - await privateKey.decrypt('hello world'); - const opt = { rsaBits: rsaBits, passphrase: 'subkey passphrase' }; - await expect(privateKey.addSubkey(opt)).to.be.rejectedWith('Subkey could not be encrypted here, please encrypt whole key'); - }); - - it('encrypt and decrypt key with added subkey', async function() { - const privateKey = await openpgp.readArmoredKey(priv_key_rsa); - await privateKey.decrypt('hello world'); - const total = privateKey.subKeys.length; - let newPrivateKey = await privateKey.addSubkey(rsaOpt); - newPrivateKey = await openpgp.readArmoredKey(newPrivateKey.armor()); - await newPrivateKey.encrypt('12345678'); - const armoredKey = newPrivateKey.armor(); - const importedPrivateKey = await openpgp.readArmoredKey(armoredKey); - await importedPrivateKey.decrypt('12345678'); - const subKey = importedPrivateKey.subKeys[total]; - expect(subKey).to.exist; - expect(importedPrivateKey.subKeys.length).to.be.equal(total + 1); - await subKey.verify(importedPrivateKey.primaryKey); - }); - - it('create and add a new ec subkey to a ec key', async function() { - const userId = { name: 'test', email: 'a@b.com' }; - const opt = { curve: 'curve25519', userIds: [userId], subkeys:[] }; - const privateKey = (await openpgp.generateKey(opt)).key; - const total = privateKey.subKeys.length; - const opt2 = { curve: 'curve25519', userIds: [userId], sign: true }; - let newPrivateKey = await privateKey.addSubkey(opt2); - const subKey1 = newPrivateKey.subKeys[total]; - await newPrivateKey.encrypt('12345678'); - const armoredKey = newPrivateKey.armor(); - newPrivateKey = await openpgp.readArmoredKey(armoredKey); - await newPrivateKey.decrypt('12345678'); - const subKey = newPrivateKey.subKeys[total]; - expect(subKey.isDecrypted()).to.be.true; - expect(subKey1.getKeyId().toHex()).to.be.equal(subKey.getKeyId().toHex()); - expect(subKey).to.exist; - expect(newPrivateKey.subKeys.length).to.be.equal(total + 1); - const subkeyOid = subKey.keyPacket.publicParams.oid; - const pkOid = privateKey.primaryKey.publicParams.oid; - expect(subkeyOid.getName()).to.be.equal(pkOid.getName()); - expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('eddsa'); - await subKey.verify(privateKey.primaryKey); - }); - - it('create and add a new ecdsa subkey to a eddsa key', async function() { - const userId = { name: 'test', email: 'a@b.com' }; - const opt = { curve: 'ed25519', userIds: [userId], subkeys:[] }; - const privateKey = (await openpgp.generateKey(opt)).key; - const total = privateKey.subKeys.length; - let newPrivateKey = await privateKey.addSubkey({ curve: 'p256', sign: true }); - newPrivateKey = await openpgp.readArmoredKey(newPrivateKey.armor()); - const subKey = newPrivateKey.subKeys[total]; - expect(subKey).to.exist; - expect(newPrivateKey.subKeys.length).to.be.equal(total + 1); - expect(newPrivateKey.getAlgorithmInfo().curve).to.be.equal('ed25519'); - expect(subKey.getAlgorithmInfo().curve).to.be.equal('p256'); - expect(newPrivateKey.getAlgorithmInfo().algorithm).to.be.equal('eddsa'); - expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('ecdsa'); - - await subKey.verify(privateKey.primaryKey); - }); - - it('create and add a new ecc subkey to a rsa key', async function() { - const privateKey = await openpgp.readArmoredKey(priv_key_rsa); - await privateKey.decrypt('hello world'); - const total = privateKey.subKeys.length; - const opt2 = { type: 'ecc', curve: 'curve25519' }; - let newPrivateKey = await privateKey.addSubkey(opt2); - const armoredKey = newPrivateKey.armor(); - newPrivateKey = await openpgp.readArmoredKey(armoredKey); - expect(newPrivateKey.subKeys.length).to.be.equal(total + 1); - const subKey = newPrivateKey.subKeys[total]; - expect(subKey).to.exist; - expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('ecdh'); - expect(subKey.getAlgorithmInfo().curve).to.be.equal(openpgp.enums.curve.curve25519); - await subKey.verify(privateKey.primaryKey); - }); - - it('create and add a new rsa subkey to a ecc key', async function() { - const userId = { name: 'test', email: 'a@b.com' }; - const opt = { curve: 'ed25519', userIds: [userId], subkeys:[] }; - const privateKey = (await openpgp.generateKey(opt)).key; - const total = privateKey.subKeys.length; - let newPrivateKey = await privateKey.addSubkey({ type: 'rsa' }); - const armoredKey = newPrivateKey.armor(); - newPrivateKey = await openpgp.readArmoredKey(armoredKey); - const subKey = newPrivateKey.subKeys[total]; - expect(subKey).to.exist; - expect(newPrivateKey.subKeys.length).to.be.equal(total + 1); - expect(subKey.getAlgorithmInfo().bits).to.be.equal(4096); - expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsaEncryptSign'); - await subKey.verify(privateKey.primaryKey); - }); - - it('create and add a new rsa subkey to a dsa key', async function() { - const privateKey = await openpgp.readArmoredKey(dsaPrivateKey); - const total = privateKey.subKeys.length; - let newPrivateKey = await privateKey.addSubkey({ type: 'rsa', rsaBits: 2048 }); - newPrivateKey = await openpgp.readArmoredKey(newPrivateKey.armor()); - expect(newPrivateKey.subKeys.length).to.be.equal(total + 1); - const subKey = newPrivateKey.subKeys[total]; - expect(subKey).to.exist; - expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsaEncryptSign'); - expect(subKey.getAlgorithmInfo().bits).to.be.equal(2048); - await subKey.verify(privateKey.primaryKey); - }); - - it('sign/verify data with the new subkey correctly using curve25519', async function() { - const userId = { name: 'test', email: 'a@b.com' }; - const opt = { curve: 'curve25519', userIds: [userId], subkeys:[] }; - const privateKey = (await openpgp.generateKey(opt)).key; - const total = privateKey.subKeys.length; - const opt2 = { sign: true }; - let newPrivateKey = await privateKey.addSubkey(opt2); - const armoredKey = newPrivateKey.armor(); - newPrivateKey = await openpgp.readArmoredKey(armoredKey); - const subKey = newPrivateKey.subKeys[total]; - const subkeyOid = subKey.keyPacket.publicParams.oid; - const pkOid = newPrivateKey.primaryKey.publicParams.oid; - expect(subkeyOid.getName()).to.be.equal(pkOid.getName()); - expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('eddsa'); - await subKey.verify(newPrivateKey.primaryKey); - expect(await newPrivateKey.getSigningKey()).to.be.equal(subKey); - const signed = await openpgp.sign({ message: openpgp.Message.fromText('the data to signed'), privateKeys: newPrivateKey, armor:false }); - const message = await openpgp.readMessage(signed); - const { signatures } = await openpgp.verify({ message, publicKeys: [newPrivateKey.toPublic()] }); - expect(signatures).to.exist; - expect(signatures.length).to.be.equal(1); - expect(signatures[0].keyid.toHex()).to.be.equal(subKey.getKeyId().toHex()); - expect(await signatures[0].verified).to.be.true; - }); - - it('encrypt/decrypt data with the new subkey correctly using curve25519', async function() { - const userId = { name: 'test', email: 'a@b.com' }; - const vData = 'the data to encrypted!'; - const opt = { curve: 'curve25519', userIds: [userId], subkeys:[] }; - const privateKey = (await openpgp.generateKey(opt)).key; - const total = privateKey.subKeys.length; - let newPrivateKey = await privateKey.addSubkey(); - const armoredKey = newPrivateKey.armor(); - newPrivateKey = await openpgp.readArmoredKey(armoredKey); - const subKey = newPrivateKey.subKeys[total]; - const publicKey = newPrivateKey.toPublic(); - await subKey.verify(newPrivateKey.primaryKey); - expect(await newPrivateKey.getEncryptionKey()).to.be.equal(subKey); - const encrypted = await openpgp.encrypt({ message: openpgp.Message.fromText(vData), publicKeys: publicKey, armor:false }); - expect(encrypted).to.be.exist; - const message = await openpgp.readMessage(encrypted); - const pkSessionKeys = message.packets.filterByTag(openpgp.enums.packet.publicKeyEncryptedSessionKey); - expect(pkSessionKeys).to.exist; - expect(pkSessionKeys.length).to.be.equal(1); - expect(pkSessionKeys[0].publicKeyId.toHex()).to.be.equals(subKey.keyPacket.getKeyId().toHex()); - const decrypted = await openpgp.decrypt({ message, privateKeys: newPrivateKey }); - expect(decrypted).to.exist; - expect(decrypted.data).to.be.equal(vData); - }); - - it('sign/verify data with the new subkey correctly using rsa', async function() { - const privateKey = await openpgp.readArmoredKey(priv_key_rsa); - await privateKey.decrypt('hello world'); - const total = privateKey.subKeys.length; - const opt2 = { sign: true, rsaBits: rsaBits }; - let newPrivateKey = await privateKey.addSubkey(opt2); - const armoredKey = newPrivateKey.armor(); - newPrivateKey = await openpgp.readArmoredKey(armoredKey); - const subKey = newPrivateKey.subKeys[total]; - expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsaEncryptSign'); - await subKey.verify(newPrivateKey.primaryKey); - expect(await newPrivateKey.getSigningKey()).to.be.equal(subKey); - const signed = await openpgp.sign({ message: openpgp.Message.fromText('the data to signed'), privateKeys: newPrivateKey, armor:false }); - const message = await openpgp.readMessage(signed); - const { signatures } = await openpgp.verify({ message, publicKeys: [newPrivateKey.toPublic()] }); - expect(signatures).to.exist; - expect(signatures.length).to.be.equal(1); - expect(signatures[0].keyid.toHex()).to.be.equal(subKey.getKeyId().toHex()); - expect(await signatures[0].verified).to.be.true; - }); - - it('encrypt/decrypt data with the new subkey correctly using rsa', async function() { - const privateKey = await openpgp.readArmoredKey(priv_key_rsa); - await privateKey.decrypt('hello world'); - const total = privateKey.subKeys.length; - let newPrivateKey = await privateKey.addSubkey(rsaOpt); - const armoredKey = newPrivateKey.armor(); - newPrivateKey = await openpgp.readArmoredKey(armoredKey); - const subKey = newPrivateKey.subKeys[total]; - const publicKey = newPrivateKey.toPublic(); - const vData = 'the data to encrypted!'; - expect(await newPrivateKey.getEncryptionKey()).to.be.equal(subKey); - const encrypted = await openpgp.encrypt({ message: openpgp.Message.fromText(vData), publicKeys: publicKey, armor:false }); - expect(encrypted).to.be.exist; - const message = await openpgp.readMessage(encrypted); - const pkSessionKeys = message.packets.filterByTag(openpgp.enums.packet.publicKeyEncryptedSessionKey); - expect(pkSessionKeys).to.exist; - expect(pkSessionKeys.length).to.be.equal(1); - expect(pkSessionKeys[0].publicKeyId.toHex()).to.be.equals(subKey.keyPacket.getKeyId().toHex()); - const decrypted = await openpgp.decrypt({ message, privateKeys: newPrivateKey }); - expect(decrypted).to.exist; - expect(decrypted.data).to.be.equal(vData); - }); +describe('addSubkey functionality testing', function(){ + let rsaBits; + let rsaOpt = {}; + if (openpgp.util.getWebCryptoAll()) { + rsaBits = 2048; + rsaOpt = { rsaBits: rsaBits }; + } + it('create and add a new rsa subkey to stored rsa key', async function() { + const privateKey = (await openpgp.key.readArmored(priv_key_rsa)).keys[0]; + await privateKey.decrypt('hello world'); + const total = privateKey.subKeys.length; + let newPrivateKey = await privateKey.addSubkey(rsaOpt); + const armoredKey = newPrivateKey.armor(); + newPrivateKey = (await openpgp.key.readArmored(armoredKey)).keys[0]; + const subKey = newPrivateKey.subKeys[total]; + expect(subKey).to.exist; + expect(newPrivateKey.subKeys.length).to.be.equal(total+1); + const subkeyN = subKey.keyPacket.params[0]; + const pkN = privateKey.primaryKey.params[0]; + expect(subkeyN.byteLength()).to.be.equal(rsaBits ? (rsaBits / 8) : pkN.byteLength()); + expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsa_encrypt_sign'); + expect(subKey.getAlgorithmInfo().rsaBits).to.be.equal(rsaBits || privateKey.getAlgorithmInfo().rsaBits); + await subKey.verify(newPrivateKey.primaryKey); + }); + + it('should throw when trying to encrypt a subkey separately from key', async function() { + const privateKey = (await openpgp.key.readArmored(priv_key_rsa)).keys[0]; + await privateKey.decrypt('hello world'); + const opt = { rsaBits: rsaBits, passphrase: 'subkey passphrase'}; + await expect(privateKey.addSubkey(opt)).to.be.rejectedWith('Subkey could not be encrypted here, please encrypt whole key'); }); + it('encrypt and decrypt key with added subkey', async function() { + const privateKey = (await openpgp.key.readArmored(priv_key_rsa)).keys[0]; + await privateKey.decrypt('hello world'); + const total = privateKey.subKeys.length; + let newPrivateKey = await privateKey.addSubkey(rsaOpt); + newPrivateKey = (await openpgp.key.readArmored(newPrivateKey.armor())).keys[0]; + await newPrivateKey.encrypt('12345678'); + const armoredKey = newPrivateKey.armor(); + let importedPrivateKey = (await openpgp.key.readArmored(armoredKey)).keys[0]; + await importedPrivateKey.decrypt('12345678'); + const subKey = importedPrivateKey.subKeys[total]; + expect(subKey).to.exist; + expect(importedPrivateKey.subKeys.length).to.be.equal(total+1); + await subKey.verify(importedPrivateKey.primaryKey); + }); + + it('create and add a new ec subkey to a ec key', async function() { + const userId = 'test '; + const opt = {curve: 'curve25519', userIds: [userId], subkeys:[]}; + const privateKey = (await openpgp.generateKey(opt)).key; + const total = privateKey.subKeys.length; + const opt2 = {curve: 'curve25519', userIds: [userId], sign: true}; + let newPrivateKey = await privateKey.addSubkey(opt2); + const subKey1 = newPrivateKey.subKeys[total]; + await newPrivateKey.encrypt('12345678'); + const armoredKey = newPrivateKey.armor(); + newPrivateKey = (await openpgp.key.readArmored(armoredKey)).keys[0]; + await newPrivateKey.decrypt('12345678'); + const subKey = newPrivateKey.subKeys[total]; + expect(subKey.isDecrypted()).to.be.true; + expect(subKey1.getKeyId().toHex()).to.be.equal(subKey.getKeyId().toHex()); + expect(subKey).to.exist; + expect(newPrivateKey.subKeys.length).to.be.equal(total+1); + const subkeyOid = subKey.keyPacket.params[0]; + const pkOid = privateKey.primaryKey.params[0]; + expect(subkeyOid.getName()).to.be.equal(pkOid.getName()); + expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('eddsa'); + await subKey.verify(privateKey.primaryKey); + }); + + it('create and add a new ec subkey to a rsa key', async function() { + const privateKey = (await openpgp.key.readArmored(priv_key_rsa)).keys[0]; + await privateKey.decrypt('hello world'); + const total = privateKey.subKeys.length; + const opt2 = {curve: 'curve25519'}; + let newPrivateKey = await privateKey.addSubkey(opt2); + const armoredKey = newPrivateKey.armor(); + newPrivateKey = (await openpgp.key.readArmored(armoredKey)).keys[0]; + const subKey = newPrivateKey.subKeys[total]; + expect(subKey).to.exist; + expect(newPrivateKey.subKeys.length).to.be.equal(total+1); + expect(subKey.keyPacket.params[0].getName()).to.be.equal(openpgp.enums.curve.curve25519); + expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('ecdh'); + await subKey.verify(privateKey.primaryKey); + }); + + it('sign/verify data with the new subkey correctly using curve25519', async function() { + const userId = 'test '; + const opt = {curve: 'curve25519', userIds: [userId], subkeys:[]}; + const privateKey = (await openpgp.generateKey(opt)).key; + const total = privateKey.subKeys.length; + const opt2 = {sign: true}; + let newPrivateKey = await privateKey.addSubkey(opt2); + const armoredKey = newPrivateKey.armor(); + newPrivateKey = (await openpgp.key.readArmored(armoredKey)).keys[0]; + const subKey = newPrivateKey.subKeys[total]; + const subkeyOid = subKey.keyPacket.params[0]; + const pkOid = newPrivateKey.primaryKey.params[0]; + expect(subkeyOid.getName()).to.be.equal(pkOid.getName()); + expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('eddsa'); + await subKey.verify(newPrivateKey.primaryKey); + expect(await newPrivateKey.getSigningKey()).to.be.equal(subKey); + const signed = await openpgp.sign({message: openpgp.cleartext.fromText('the data to signed'), privateKeys: newPrivateKey, armor:false}); + const verified = await signed.message.verify([newPrivateKey.toPublic()]); + expect(verified).to.exist; + expect(verified.length).to.be.equal(1); + expect(await verified[0].keyid).to.be.equal(subKey.getKeyId()); + expect(await verified[0].verified).to.be.true; + }); + + it('encrypt/decrypt data with the new subkey correctly using curve25519', async function() { + const userId = 'test '; + const vData = 'the data to encrypted!'; + const opt = {curve: 'curve25519', userIds: [userId], subkeys:[]}; + const privateKey = (await openpgp.generateKey(opt)).key; + const total = privateKey.subKeys.length; + let newPrivateKey = await privateKey.addSubkey(); + const armoredKey = newPrivateKey.armor(); + newPrivateKey = (await openpgp.key.readArmored(armoredKey)).keys[0]; + const subKey = newPrivateKey.subKeys[total]; + const publicKey = newPrivateKey.toPublic(); + await subKey.verify(newPrivateKey.primaryKey); + expect(await newPrivateKey.getEncryptionKey()).to.be.equal(subKey); + const encrypted = await openpgp.encrypt({message: openpgp.message.fromText(vData), publicKeys: publicKey, armor:false}); + expect(encrypted.message).to.be.exist; + const pkSessionKeys = encrypted.message.packets.filterByTag(openpgp.enums.packet.publicKeyEncryptedSessionKey); + expect(pkSessionKeys).to.exist; + expect(pkSessionKeys.length).to.be.equal(1); + expect(pkSessionKeys[0].publicKeyId.toHex()).to.be.equals(subKey.keyPacket.getKeyId().toHex()); + const decrypted = await openpgp.decrypt({message: encrypted.message, privateKeys: newPrivateKey}) + expect(decrypted).to.exist; + expect(decrypted.data).to.be.equal(vData); + }); + + it('sign/verify data with the new subkey correctly using rsa', async function() { + const privateKey = (await openpgp.key.readArmored(priv_key_rsa)).keys[0]; + await privateKey.decrypt('hello world'); + const total = privateKey.subKeys.length; + const opt2 = { sign: true, rsaBits: rsaBits }; + let newPrivateKey = await privateKey.addSubkey(opt2); + const armoredKey = newPrivateKey.armor(); + newPrivateKey = (await openpgp.key.readArmored(armoredKey)).keys[0]; + const subKey = newPrivateKey.subKeys[total]; + expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsa_encrypt_sign'); + await subKey.verify(newPrivateKey.primaryKey); + expect(await newPrivateKey.getSigningKey()).to.be.equal(subKey); + const signed = await openpgp.sign({message: openpgp.cleartext.fromText('the data to signed'), privateKeys: newPrivateKey, armor:false}); + const verified = await signed.message.verify([newPrivateKey.toPublic()]); + expect(verified).to.exist; + expect(verified.length).to.be.equal(1); + expect(await verified[0].keyid).to.be.equal(subKey.getKeyId()); + expect(await verified[0].verified).to.be.true; + }); + + it('encrypt/decrypt data with the new subkey correctly using rsa', async function() { + const privateKey = (await openpgp.key.readArmored(priv_key_rsa)).keys[0]; + await privateKey.decrypt('hello world'); + const total = privateKey.subKeys.length; + let newPrivateKey = await privateKey.addSubkey(rsaOpt); + const armoredKey = newPrivateKey.armor(); + newPrivateKey = (await openpgp.key.readArmored(armoredKey)).keys[0]; + const subKey = newPrivateKey.subKeys[total]; + const publicKey = newPrivateKey.toPublic(); + const vData = 'the data to encrypted!'; + expect(await newPrivateKey.getEncryptionKey()).to.be.equal(subKey); + const encrypted = await openpgp.encrypt({message: openpgp.message.fromText(vData), publicKeys: publicKey, armor:false}); + expect(encrypted.message).to.be.exist; + const pkSessionKeys = encrypted.message.packets.filterByTag(openpgp.enums.packet.publicKeyEncryptedSessionKey); + expect(pkSessionKeys).to.exist; + expect(pkSessionKeys.length).to.be.equal(1); + expect(pkSessionKeys[0].publicKeyId.toHex()).to.be.equals(subKey.keyPacket.getKeyId().toHex()); + const decrypted = await openpgp.decrypt({message: encrypted.message, privateKeys: newPrivateKey}) + expect(decrypted).to.exist; + expect(decrypted.data).to.be.equal(vData); + }); }); diff --git a/test/general/keyring.js b/test/general/keyring.js index 822db274..f8eb4e9f 100644 --- a/test/general/keyring.js +++ b/test/general/keyring.js @@ -1,4 +1,4 @@ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); const chai = require('chai'); @@ -6,7 +6,7 @@ const { expect } = chai; const keyring = new openpgp.Keyring(); -module.exports = () => describe("Keyring", async function() { +describe("Keyring", async function() { const user = 'whiteout.test@t-online.de'; const passphrase = 'asdf'; const keySize = 512; @@ -45,66 +45,66 @@ module.exports = () => describe("Keyring", async function() { const subkeyFingerP2 = '2a20c371141e000833848d85f47c5210a8cc2740'; const pubkey2 = ['-----BEGIN PGP PUBLIC KEY BLOCK-----', - 'Version: GnuPG v2.0.22 (GNU/Linux)', - '', - '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', - 'og2umGfGng==', - '=v3+L', - '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); + 'Version: GnuPG v2.0.22 (GNU/Linux)', + '', + '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', + 'og2umGfGng==', + '=v3+L', + '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); const user3 = 'plain@email.org'; const keyFingerP3 = 'f9972bf320a86a93c6614711ed241e1de755d53c'; const pubkey3 = ['-----BEGIN PGP PUBLIC KEY BLOCK-----', - '', - 'xo0EVe6wawEEAKG4LDE9946jdvvbfVTF9qWtOyxHYjb40z7hgcZsPEGd6QfN', - 'XbfNJBeQ5S9j/2jRu8NwBgdXIpMp4QwB2Q/cEp1rbw5kUVuRbhfsb2BzuiBr', - 'Q5jHa5oZSGbbLWRoOXTvJH8VE2gbKSj/km1VaXzq2Qmv+YIHxav1it7vNmg5', - 'E2kBABEBAAHND3BsYWluQGVtYWlsLm9yZ8K1BBABCAApBQJV7rBrBgsJCAcD', - 'AgkQ7SQeHedV1TwEFQgCCgMWAgECGQECGwMCHgEAAGJmBACVJPoFtW96UkIW', - 'GX1bgW99c4K87Me+5ZCHqPOdXFpRinAPBdJT9vkBWLb/aOQQCDWJvdVXKFLD', - 'FCbSBjcohR71n6145F5im8b0XzXnKh+MRRv/0UHiHGtB/Pkg38jbLeXbVfCM', - '9JJm+s+PFef+8wN84sEtD/MX2cj61teuPf2VEs6NBFXusGsBBACoJW/0y5Ea', - 'FH0nJOuoenrEBZkFtGbdwo8A4ufCCrm9ppFHVVnw4uTPH9dOjw8IAnNy7wA8', - '8yZCkreQ491em09knR7k2YdJccWwW8mGRILHQDDEPetZO1dSVW+MA9X7Pcle', - 'wbFEHCIkWEgymn3zenie1LXIljPzizHje5vWBrSlFwARAQABwp8EGAEIABMF', - 'AlXusGsJEO0kHh3nVdU8AhsMAACB2AP/eRJFAVTyiP5MnMjsSBuNMNBp1X0Y', - '+RrWDpO9H929+fm9oFTedohf/Ja5w9hsRk2VzjLOXe/uHdrcgaBmAdFunbvv', - 'IWneczohBvLOarevZj1J+H3Ej/DVF2W7kJZLpvPfh7eo0biClS/GQUVw1rlE', - 'ph10hhUaSJ326LsFJccT3jk=', - '=4jat', - '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); + '', + 'xo0EVe6wawEEAKG4LDE9946jdvvbfVTF9qWtOyxHYjb40z7hgcZsPEGd6QfN', + 'XbfNJBeQ5S9j/2jRu8NwBgdXIpMp4QwB2Q/cEp1rbw5kUVuRbhfsb2BzuiBr', + 'Q5jHa5oZSGbbLWRoOXTvJH8VE2gbKSj/km1VaXzq2Qmv+YIHxav1it7vNmg5', + 'E2kBABEBAAHND3BsYWluQGVtYWlsLm9yZ8K1BBABCAApBQJV7rBrBgsJCAcD', + 'AgkQ7SQeHedV1TwEFQgCCgMWAgECGQECGwMCHgEAAGJmBACVJPoFtW96UkIW', + 'GX1bgW99c4K87Me+5ZCHqPOdXFpRinAPBdJT9vkBWLb/aOQQCDWJvdVXKFLD', + 'FCbSBjcohR71n6145F5im8b0XzXnKh+MRRv/0UHiHGtB/Pkg38jbLeXbVfCM', + '9JJm+s+PFef+8wN84sEtD/MX2cj61teuPf2VEs6NBFXusGsBBACoJW/0y5Ea', + 'FH0nJOuoenrEBZkFtGbdwo8A4ufCCrm9ppFHVVnw4uTPH9dOjw8IAnNy7wA8', + '8yZCkreQ491em09knR7k2YdJccWwW8mGRILHQDDEPetZO1dSVW+MA9X7Pcle', + 'wbFEHCIkWEgymn3zenie1LXIljPzizHje5vWBrSlFwARAQABwp8EGAEIABMF', + 'AlXusGsJEO0kHh3nVdU8AhsMAACB2AP/eRJFAVTyiP5MnMjsSBuNMNBp1X0Y', + '+RrWDpO9H929+fm9oFTedohf/Ja5w9hsRk2VzjLOXe/uHdrcgaBmAdFunbvv', + 'IWneczohBvLOarevZj1J+H3Ej/DVF2W7kJZLpvPfh7eo0biClS/GQUVw1rlE', + 'ph10hhUaSJ326LsFJccT3jk=', + '=4jat', + '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); it('Import key pair', async function() { await keyring.load(); @@ -135,7 +135,7 @@ module.exports = () => describe("Keyring", async function() { it('publicKeys.getForId() - valid id', function() { const key = keyring.publicKeys.getForId(keyId); - expect(key).to.exist.and.be.an.instanceof(openpgp.Key); + expect(key).to.exist.and.be.an.instanceof(openpgp.key.Key); expect(key.getKeyId().toHex()).equals(keyId); }); @@ -146,7 +146,7 @@ module.exports = () => describe("Keyring", async function() { it('privateKeys.getForId() - valid id', function() { const key = keyring.privateKeys.getForId(keyId); - expect(key).to.exist.and.be.an.instanceof(openpgp.Key); + expect(key).to.exist.and.be.an.instanceof(openpgp.key.Key); expect(key.getKeyId().toHex()).equals(keyId); }); @@ -157,7 +157,7 @@ module.exports = () => describe("Keyring", async function() { it('publicKeys.getForId() - deep, including subkeys - subkey id', function() { const key = keyring.publicKeys.getForId(subkeyId2, true); - expect(key).to.exist.and.be.an.instanceof(openpgp.Key); + expect(key).to.exist.and.be.an.instanceof(openpgp.key.Key); expect(key.getKeyId().toHex()).equals(keyId2); }); @@ -179,7 +179,7 @@ module.exports = () => describe("Keyring", async function() { it('publicKeys.getForId() - valid fingerprint', function() { const key = keyring.publicKeys.getForId(keyFingerP2); - expect(key).to.exist.and.be.an.instanceof(openpgp.Key); + expect(key).to.exist.and.be.an.instanceof(openpgp.key.Key); expect(key.getKeyId().toHex()).equals(keyId2); }); @@ -190,7 +190,7 @@ module.exports = () => describe("Keyring", async function() { it('publicKeys.getForId() - deep, including subkeys - subkey fingerprint', function() { const key = keyring.publicKeys.getForId(subkeyFingerP2, true); - expect(key).to.exist.and.be.an.instanceof(openpgp.Key); + expect(key).to.exist.and.be.an.instanceof(openpgp.key.Key); expect(key.getKeyId().toHex()).equals(keyId2); }); @@ -250,7 +250,7 @@ module.exports = () => describe("Keyring", async function() { it('publicKeys.removeForId() - valid id', function() { const key = keyring.publicKeys.removeForId(keyId); - expect(key).to.exist.and.be.an.instanceof(openpgp.Key); + expect(key).to.exist.and.be.an.instanceof(openpgp.key.Key); expect(key.getKeyId().toHex()).equals(keyId); expect(keyring.publicKeys.keys).to.exist.and.have.length(1); }); @@ -263,7 +263,7 @@ module.exports = () => describe("Keyring", async function() { it('publicKeys.removeForId() - valid fingerprint', function() { const key = keyring.publicKeys.removeForId(keyFingerP2); - expect(key).to.exist.and.be.an.instanceof(openpgp.Key); + expect(key).to.exist.and.be.an.instanceof(openpgp.key.Key); expect(key.getKeyId().toHex()).equals(keyId2); expect(keyring.publicKeys.keys).to.be.empty; }); @@ -273,14 +273,14 @@ module.exports = () => describe("Keyring", async function() { const localstore2 = new openpgp.Keyring.localstore('my-custom-prefix-'); const localstore3 = new openpgp.Keyring.localstore(); await localstore3.storePublic([]); - const key = await openpgp.readArmoredKey(pubkey); + const key = (await openpgp.key.readArmored(pubkey)).keys[0]; await localstore1.storePublic([key]); expect((await localstore2.loadPublic())[0].getKeyId().equals(key.getKeyId())).to.be.true; expect(await localstore3.loadPublic()).to.have.length(0); }); it('emptying keyring and storing removes keys', async function() { - const key = await openpgp.readArmoredKey(pubkey); + const key = (await openpgp.key.readArmored(pubkey)).keys[0]; const localstore = new openpgp.Keyring.localstore('remove-prefix-'); diff --git a/test/general/oid.js b/test/general/oid.js index 22c812d2..69611140 100644 --- a/test/general/oid.js +++ b/test/general/oid.js @@ -1,9 +1,10 @@ -const OID = require('../../src/type/oid'); -const util = require('../../src/util'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); const expect = require('chai').expect; -module.exports = () => describe('Oid tests', function() { +describe('Oid tests', function() { + const OID = openpgp.OID; + const util = openpgp.util; const p256_oid = new Uint8Array([0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07]); const p384_oid = new Uint8Array([0x2B, 0x81, 0x04, 0x00, 0x22]); const p521_oid = new Uint8Array([0x2B, 0x81, 0x04, 0x00, 0x23]); @@ -14,25 +15,25 @@ module.exports = () => describe('Oid tests', function() { expect(oid).to.exist; expect(oid.oid).to.exist; expect(oid.oid).to.have.length(data.length); - expect(oid.toHex()).to.equal(util.uint8ArrayToHex(data)); + expect(oid.toHex()).to.equal(util.Uint8Array_to_hex(data)); }); }); it('Reading and writing', function() { const oids = [p256_oid, p384_oid, p521_oid]; oids.forEach(function (data) { - data = util.concatUint8Array([new Uint8Array([data.length]), data]); + data = openpgp.util.concatUint8Array([new Uint8Array([data.length]), data]); const oid = new OID(); expect(oid.read(data)).to.equal(data.length); expect(oid.oid).to.exist; expect(oid.oid).to.have.length(data.length-1); - expect(oid.toHex()).to.equal(util.uint8ArrayToHex(data.subarray(1))); + expect(oid.toHex()).to.equal(util.Uint8Array_to_hex(data.subarray(1))); const result = oid.write(); expect(result).to.exist; expect(result).to.have.length(data.length); expect(result[0]).to.equal(data.length-1); expect( - util.uint8ArrayToHex(result.subarray(1)) - ).to.equal(util.uint8ArrayToHex(data.subarray(1))); + util.Uint8Array_to_hex(result.subarray(1)) + ).to.equal(util.Uint8Array_to_hex(data.subarray(1))); }); }); }); diff --git a/test/general/openpgp.js b/test/general/openpgp.js index e398f18f..b19b3193 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -1,11 +1,9 @@ /* globals tryTests: true */ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); -const crypto = require('../../src/crypto'); -const random = require('../../src/crypto/random'); -const util = require('../../src/util'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); const spy = require('sinon/lib/sinon/spy'); +const stub = require('sinon/lib/sinon/stub'); const input = require('./testInputs.js'); const chai = require('chai'); chai.use(require('chai-as-promised')); @@ -376,71 +374,6 @@ EQr2Mx42THr260IFYp5E/rIA =oA0b -----END PGP PRIVATE KEY BLOCK-----`; -const mismatchingKeyParams = `-----BEGIN PGP PRIVATE KEY BLOCK----- -Version: OpenPGP.js v4.7.0 -Comment: https://openpgpjs.org - -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 -0NubenKyljKtyHyoU+S+TqUyx7gf5A== -=Lj9k ------END PGP PRIVATE KEY BLOCK----- -`; - const rsaPrivateKeyPKCS1 = `-----BEGIN PGP PRIVATE KEY BLOCK----- xcLYBF7yFJcBCACv2ad3tpfA8agLV+7ZO+7vWAS8f4CgCLsW2fvyIG0X3to9 @@ -503,40 +436,6 @@ IMq6OV/eCedB8bF4bqoU+zGdGh+XwJkoYVVF6DtG+gIcceHUjC0eXHw= -----END PGP PRIVATE KEY BLOCK----- `; -const gnuDummyKeySigningSubkey = ` ------BEGIN PGP PRIVATE KEY BLOCK----- -Version: OpenPGP.js VERSION -Comment: https://openpgpjs.org - -xZUEWCC+hwEEALu8GwefswqZLoiKJk1Nd1yKmVWBL1ypV35FN0gCjI1NyyJX -UfQZDdC2h0494OVAM2iqKepqht3tH2DebeFLnc2ivvIFmQJZDnH2/0nFG2gC -rSySWHUjVfbMSpmTaXpit8EX/rjNauGOdbePbezOSsAhW7R9pBdtDjPnq2Zm -vDXXABEBAAH+B2UAR05VAc0JR05VIER1bW15wrgEEwECACIFAlggvocCGwMG -CwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEJ3XHFanUJgCeMYD/2zKefpl -clQoBdDPJKCYJm8IhuWuoF8SnHAsbhD+U42Gbm+2EATTPj0jyGPkZzl7a0th -S2rSjQ4JF0Ktgdr9585haknpGwr31t486KxXOY4AEsiBmRyvTbaQegwKaQ+C -/0JQYo/XKpsaX7PMDBB9SNFSa8NkhxYseLaB7gbM8w+Lx8EYBFggvpwBBADF -YeeJwp6MAVwVwXX/eBRKBIft6LC4E9czu8N2AbOW97WjWNtXi3OuM32OwKXq -vSck8Mx8FLOAuvVq41NEboeknhptw7HzoQMB35q8NxA9lvvPd0+Ef+BvaVB6 -NmweHttt45LxYxLMdXdGoIt3wn/HBY81HnMqfV/KnggZ+imJ0wARAQABAAP7 -BA56WdHzb53HIzYgWZl04H3BJdB4JU6/FJo0yHpjeWRQ46Q7w2WJzjHS6eBB -G+OhGzjAGYK7AUr8wgjqMq6LQHt2f80N/nWLusZ00a4lcMd7rvoHLWwRj80a -RzviOvvhP7kZY1TrhbS+Sl+BWaNIDOxS2maEkxexztt4GEl2dWUCAMoJvyFm -qPVqVx2Yug29vuJsDcr9XwnjrYI8PtszJI8Fr+5rKgWE3GJumheaXaug60dr -mLMXdvT/0lj3sXquqR0CAPoZ1Mn7GaUKjPVJ7CiJ/UjqSurrGhruA5ikhehQ -vUB+v4uIl7ICcX8zfiP+SMhWY9qdkmOvLSSSMcTkguMfe68B/j/qf2en5OHy -6NJgMIjMrBHvrf34f6pxw5p10J6nxjooZQxV0P+9MoTHWsy0r6Er8IOSSTGc -WyWJ8wmSqiq/dZSoJcLAfQQYAQIACQUCWCC+nAIbAgCoCRCd1xxWp1CYAp0g -BBkBAgAGBQJYIL6cAAoJEOYZSGiVA/C9CT4D/2Vq2dKxHmzn/UD1MWSLXUbN -ISd8tvHjoVg52RafdgHFmg9AbE0DW8ifwaai7FkifD0IXiN04nER3MuVhAn1 -gtMu03m1AQyX/X39tHz+otpwBn0g57NhFbHFmzKfr/+N+XsDRj4VXn13hhqM -qQR8i1wgiWBUFJbpP5M1BPdH4Qfkcn8D/j8A3QKYGGETa8bNOdVTRU+sThXr -imOfWu58V1yWCmLE1kK66qkqmgRVUefqacF/ieMqNmsAY+zmR9D4fg2wzu/d -nPjJXp1670Vlzg7oT5XVYnfys7x4GLHsbaOSjXToILq+3GwI9UjNjtpobcfm -mNG2ibD6lftLOtDsVSDY8a6a -=KjxQ ------END PGP PRIVATE KEY BLOCK----- -`; function withCompression(tests) { const compressionTypes = Object.keys(openpgp.enums.compression).map(k => openpgp.enums.compression[k]); @@ -553,8 +452,8 @@ function withCompression(tests) { let decompressSpy; beforeEach(function () { - compressSpy = spy(openpgp.CompressedDataPacket.prototype, 'compress'); - decompressSpy = spy(openpgp.CompressedDataPacket.prototype, 'decompress'); + compressSpy = spy(openpgp.packet.Compressed.prototype, 'compress'); + decompressSpy = spy(openpgp.packet.Compressed.prototype, 'decompress'); }); afterEach(function () { @@ -568,6 +467,11 @@ function withCompression(tests) { return options; }, function() { + // Disable the call expectations when using the web worker because it's not possible to spy on what functions get called. + if (openpgp.getWorker()) { + return; + } + if (compression === openpgp.enums.compression.uncompressed) { expect(compressSpy.called).to.be.false; expect(decompressSpy.called).to.be.false; @@ -584,7 +488,41 @@ function withCompression(tests) { }); } -module.exports = () => describe('OpenPGP.js public api tests', function() { +describe('OpenPGP.js public api tests', function() { + + let rsaGenStub; + let rsaGenValue = openpgp.crypto.publicKey.rsa.generate(openpgp.util.getWebCryptoAll() ? 2048 : 512, "10001"); + + beforeEach(function() { + rsaGenStub = stub(openpgp.crypto.publicKey.rsa, 'generate'); + rsaGenStub.returns(rsaGenValue); + }); + + afterEach(function() { + rsaGenStub.restore(); + }); + + describe('initWorker, getWorker, destroyWorker - unit tests', function() { + afterEach(function() { + openpgp.destroyWorker(); // cleanup worker in case of failure + }); + + it('should work', async function() { + const workerStub = { + postMessage: function() {}, + terminate: function() {} + }; + await Promise.all([ + openpgp.initWorker({ + workers: [workerStub] + }), + workerStub.onmessage({ data: { event: 'loaded' } }) + ]); + expect(openpgp.getWorker()).to.exist; + openpgp.destroyWorker(); + expect(openpgp.getWorker()).to.not.exist; + }); + }); describe('generateKey - validate user ids', function() { it('should fail for invalid user name', async function() { @@ -592,7 +530,7 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { userIds: [{ name: {}, email: 'text@example.com' }] }; const test = openpgp.generateKey(opt); - await expect(test).to.eventually.be.rejectedWith(/Invalid user ID format/); + await expect(test).to.eventually.be.rejectedWith(/Invalid user id format/); }); it('should fail for invalid user email address', async function() { @@ -600,7 +538,7 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { userIds: [{ name: 'Test User', email: 'textexample.com' }] }; const test = openpgp.generateKey(opt); - await expect(test).to.eventually.be.rejectedWith(/Invalid user ID format/); + await expect(test).to.eventually.be.rejectedWith(/Invalid user id format/); }); it('should fail for invalid user email address', async function() { @@ -608,39 +546,61 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { userIds: [{ name: 'Test User', email: 'text@examplecom' }] }; const test = openpgp.generateKey(opt); - await expect(test).to.eventually.be.rejectedWith(/Invalid user ID format/); + await expect(test).to.eventually.be.rejectedWith(/Invalid user id format/); }); - it('should fail for string user ID', async function() { + it('should fail for invalid string user id', async function() { const opt = { - userIds: 'Test User ' + userIds: ['Test User text@example.com>'] }; const test = openpgp.generateKey(opt); - await expect(test).to.eventually.be.rejectedWith(/Invalid user ID format/); + await expect(test).to.eventually.be.rejectedWith(/Invalid user id format/); + }); + + it('should fail for invalid single string user id', async function() { + const opt = { + userIds: 'Test User text@example.com>' + }; + const test = openpgp.generateKey(opt); + await expect(test).to.eventually.be.rejectedWith(/Invalid user id format/); + }); + + it('should work for valid single string user id', function() { + const opt = { + userIds: 'Test User ' + }; + return openpgp.generateKey(opt); + }); + + it('should work for valid string user id', function() { + const opt = { + userIds: ['Test User '] + }; + return openpgp.generateKey(opt); }); - it('should work for valid single user ID object', function() { + it('should work for valid single user id hash', function() { const opt = { userIds: { name: 'Test User', email: 'text@example.com' } }; return openpgp.generateKey(opt); }); - it('should work for array of user ID objects', function() { + it('should work for valid single user id hash', function() { const opt = { userIds: [{ name: 'Test User', email: 'text@example.com' }] }; return openpgp.generateKey(opt); }); - it('should work for undefined name', function() { + it('should work for an empty name', function() { const opt = { userIds: { email: 'text@example.com' } }; return openpgp.generateKey(opt); }); - it('should work for an undefined email address', function() { + it('should work for an empty email address', function() { const opt = { userIds: { name: 'Test User' } }; @@ -649,48 +609,117 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { }); describe('generateKey - unit tests', function() { + let keyGenStub; + let keyObjStub; + let getWebCryptoAllStub; + + beforeEach(function() { + keyObjStub = { + armor: function() { + return 'priv_key'; + }, + toPublic: function() { + return { + armor: function() { + return 'pub_key'; + } + }; + }, + getRevocationCertificate: function() {} + }; + keyGenStub = stub(openpgp.key, 'generate'); + keyGenStub.returns(resolves(keyObjStub)); + getWebCryptoAllStub = stub(openpgp.util, 'getWebCryptoAll'); + }); + + afterEach(function() { + keyGenStub.restore(); + openpgp.destroyWorker(); + getWebCryptoAllStub.restore(); + }); + it('should have default params set', function() { - const now = util.normalizeDate(new Date()); + const now = openpgp.util.normalizeDate(new Date()); const opt = { userIds: { name: 'Test User', email: 'text@example.com' }, passphrase: 'secret', - date: now + date: now, + subkeys: [] }; - return openpgp.generateKey(opt).then(async function(newKey) { + return openpgp.generateKey(opt).then(function(newKey) { + expect(keyGenStub.withArgs({ + userIds: [{ name: 'Test User', email: 'text@example.com' }], + passphrase: 'secret', + rsaBits: 2048, + keyExpirationTime: 0, + curve: "", + date: now, + subkeys: [] + }).calledOnce).to.be.true; expect(newKey.key).to.exist; - expect(newKey.key.users.length).to.equal(1); - expect(newKey.key.users[0].userId.name).to.equal('Test User'); - expect(newKey.key.users[0].userId.email).to.equal('text@example.com'); - expect(newKey.key.getAlgorithmInfo().rsaBits).to.equal(undefined); - expect(newKey.key.getAlgorithmInfo().curve).to.equal('ed25519'); - expect(+newKey.key.getCreationTime()).to.equal(+now); - expect(await newKey.key.getExpirationTime()).to.equal(Infinity); - expect(newKey.key.subKeys.length).to.equal(1); - expect(newKey.key.subKeys[0].getAlgorithmInfo().rsaBits).to.equal(undefined); - expect(newKey.key.subKeys[0].getAlgorithmInfo().curve).to.equal('curve25519'); - expect(+newKey.key.subKeys[0].getCreationTime()).to.equal(+now); - expect(await newKey.key.subKeys[0].getExpirationTime()).to.equal(Infinity); expect(newKey.privateKeyArmored).to.exist; expect(newKey.publicKeyArmored).to.exist; }); }); + + it('should delegate to async proxy', async function() { + const workerStub = { + postMessage: function() {}, + terminate: function() {} + }; + await Promise.all([ + openpgp.initWorker({ + workers: [workerStub] + }), + workerStub.onmessage({ data: { event: 'loaded' } }) + ]); + const proxyGenStub = stub(openpgp.getWorker(), 'delegate'); + getWebCryptoAllStub.returns(); + + const opt = { + userIds: { name: 'Test User', email: 'text@example.com' }, + passphrase: 'secret', + subkeys: [] + }; + openpgp.generateKey(opt); + expect(proxyGenStub.calledOnce).to.be.true; + expect(keyGenStub.calledOnce).to.be.false; + }); }); describe('generateKey - integration tests', function() { - let useNativeVal; + let 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() { + openpgp.config.use_native = false; + openpgp.destroyWorker(); + const opt = { + userIds: [{ name: 'Test User', email: 'text@example.com' }], + numBits: 512 + }; + + return openpgp.generateKey(opt).then(function(newKey) { + expect(newKey.key.getUserIds()[0]).to.equal('Test User '); + expect(newKey.publicKeyArmored).to.match(/^-----BEGIN PGP PUBLIC/); + expect(newKey.privateKeyArmored).to.match(/^-----BEGIN PGP PRIVATE/); + }); }); - it('should work in JS', function() { - openpgp.config.useNative = false; + it('should work in JS (with worker)', async function() { + openpgp.config.use_native = false; + await openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); const opt = { userIds: [{ name: 'Test User', email: 'text@example.com' }], + numBits: 512 }; return openpgp.generateKey(opt).then(function(newKey) { @@ -701,10 +730,12 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { }); it('should work in with native crypto', function() { - openpgp.config.useNative = true; + openpgp.config.use_native = true; const opt = { userIds: [{ name: 'Test User', email: 'text@example.com' }], + numBits: 512 }; + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys return openpgp.generateKey(opt).then(function(newKey) { expect(newKey.key.getUserIds()[0]).to.equal('Test User '); @@ -724,256 +755,198 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { let privateKey; let publicKey; let publicKeyNoAEAD; - let useNativeVal; - let aeadProtectVal; - let aeadModeVal; - let aeadChunkSizeByteVal; - let v5KeysVal; - let privateKeyMismatchingParams; + let zero_copyVal; + let use_nativeVal; + let aead_protectVal; + let aead_modeVal; + let aead_chunk_size_byteVal; + let v5_keysVal; beforeEach(async function() { - publicKey = await openpgp.readArmoredKey(pub_key); - publicKeyNoAEAD = await openpgp.readArmoredKey(pub_key); - privateKey = await openpgp.readArmoredKey(priv_key); - privateKey_2000_2008 = await openpgp.readArmoredKey(priv_key_2000_2008); - publicKey_2000_2008 = privateKey_2000_2008.toPublic(); - privateKey_2038_2045 = await openpgp.readArmoredKey(priv_key_2038_2045); - publicKey_2038_2045 = privateKey_2038_2045.toPublic(); - privateKey_1337 = await openpgp.readArmoredKey(priv_key_expires_1337); - publicKey_1337 = privateKey_1337.toPublic(); - privateKeyMismatchingParams = await openpgp.readArmoredKey(mismatchingKeyParams); - - useNativeVal = openpgp.config.useNative; - aeadProtectVal = openpgp.config.aeadProtect; - aeadModeVal = openpgp.config.aeadMode; - aeadChunkSizeByteVal = openpgp.config.aeadChunkSizeByte; - v5KeysVal = openpgp.config.v5Keys; + publicKey = await openpgp.key.readArmored(pub_key); + expect(publicKey.keys).to.have.length(1); + expect(publicKey.err).to.not.exist; + publicKeyNoAEAD = await openpgp.key.readArmored(pub_key); + privateKey = await openpgp.key.readArmored(priv_key); + expect(privateKey.keys).to.have.length(1); + expect(privateKey.err).to.not.exist; + privateKey_2000_2008 = await openpgp.key.readArmored(priv_key_2000_2008); + expect(privateKey_2000_2008.keys).to.have.length(1); + expect(privateKey_2000_2008.err).to.not.exist; + publicKey_2000_2008 = { keys: [ privateKey_2000_2008.keys[0].toPublic() ] }; + privateKey_2038_2045 = await openpgp.key.readArmored(priv_key_2038_2045); + expect(privateKey_2038_2045.keys).to.have.length(1); + expect(privateKey_2038_2045.err).to.not.exist; + publicKey_2038_2045 = { keys: [ privateKey_2038_2045.keys[0].toPublic() ] }; + privateKey_1337 = await openpgp.key.readArmored(priv_key_expires_1337); + expect(privateKey_1337.keys).to.have.length(1); + expect(privateKey_1337.err).to.not.exist; + publicKey_1337 = { keys: [ privateKey_1337.keys[0].toPublic() ] }; + zero_copyVal = openpgp.config.zero_copy; + use_nativeVal = openpgp.config.use_native; + aead_protectVal = openpgp.config.aead_protect; + aead_modeVal = openpgp.config.aead_mode; + aead_chunk_size_byteVal = openpgp.config.aead_chunk_size_byte; + v5_keysVal = openpgp.config.v5_keys; }); afterEach(function() { - openpgp.config.useNative = useNativeVal; - openpgp.config.aeadProtect = aeadProtectVal; - openpgp.config.aeadMode = aeadModeVal; - openpgp.config.aeadChunkSizeByte = aeadChunkSizeByteVal; - openpgp.config.v5Keys = v5KeysVal; + openpgp.config.zero_copy = zero_copyVal; + openpgp.config.use_native = use_nativeVal; + openpgp.config.aead_protect = aead_protectVal; + openpgp.config.aead_mode = aead_modeVal; + openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteVal; + openpgp.config.v5_keys = v5_keysVal; }); it('Configuration', async function() { - const showCommentVal = openpgp.config.showComment; - const showVersionVal = openpgp.config.showVersion; - const commentStringVal = openpgp.config.commentString; + openpgp.config.show_version = false; + openpgp.config.commentstring = 'different'; + if (openpgp.getWorker()) { // init again to trigger config event + await openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); + } + return openpgp.encrypt({ publicKeys:publicKey.keys, message:openpgp.message.fromText(plaintext) }).then(function(encrypted) { + expect(encrypted.data).to.exist; + expect(encrypted.data).not.to.match(/^Version:/); + expect(encrypted.data).to.match(/Comment: different/); + }); + }); + it('Test multiple workers', async function() { + openpgp.config.show_version = false; + openpgp.config.commentstring = 'different'; + if (!openpgp.getWorker()) { + return; + } + const { workers } = openpgp.getWorker(); try { - const encryptedDefault = await openpgp.encrypt({ publicKeys:publicKey, message:openpgp.Message.fromText(plaintext) }); - expect(encryptedDefault).to.exist; - expect(encryptedDefault).not.to.match(/^Version:/); - expect(encryptedDefault).not.to.match(/^Comment:/); - - openpgp.config.showComment = true; - openpgp.config.commentString = 'different'; - const encryptedWithComment = await openpgp.encrypt({ publicKeys:publicKey, message:openpgp.Message.fromText(plaintext) }); - expect(encryptedWithComment).to.exist; - expect(encryptedWithComment).not.to.match(/^Version:/); - expect(encryptedWithComment).to.match(/Comment: different/); + await privateKey.keys[0].decrypt(passphrase) + await openpgp.initWorker({path: '../dist/openpgp.worker.js', workers, n: 2}); + + const workerTest = (_, index) => { + const plaintext = input.createSomeMessage() + index; + return openpgp.encrypt({ + publicKeys: publicKey.keys, + data: plaintext + }).then(function (encrypted) { + expect(encrypted.data).to.exist; + expect(encrypted.data).not.to.match(/^Version:/); + expect(encrypted.data).to.match(/Comment: different/); + return openpgp.decrypt({ + privateKeys: privateKey.keys[0], + message: openpgp.message.readArmored(encrypted.data) + }); + }).then(function (decrypted) { + expect(decrypted.data).to.equal(plaintext); + }); + }; + await Promise.all(Array(10).fill(null).map(workerTest)); } finally { - openpgp.config.showComment = showCommentVal; - openpgp.config.showVersion = showVersionVal; - openpgp.config.commentString = commentStringVal; + await openpgp.initWorker({path: '../dist/openpgp.worker.js', workers, n: 1 }); } }); it('Decrypting key with wrong passphrase rejected', async function () { - await expect(privateKey.decrypt('wrong passphrase')).to.eventually.be.rejectedWith('Incorrect key passphrase'); + await expect(privateKey.keys[0].decrypt('wrong passphrase')).to.eventually.be.rejectedWith('Incorrect key passphrase'); }); - it('Can decrypt key with correct passphrase', async function () { - expect(privateKey.isDecrypted()).to.be.false; - await privateKey.decrypt(passphrase); - expect(privateKey.isDecrypted()).to.be.true; + it('Decrypting key with correct passphrase returns true', async function () { + expect(await privateKey.keys[0].decrypt(passphrase)).to.be.true; }); describe('decryptKey', function() { - it('should work for correct passphrase', async function() { - const originalKey = await openpgp.readArmoredKey(privateKey.armor()); + it('should work for correct passphrase', function() { return openpgp.decryptKey({ - privateKey: privateKey, + privateKey: privateKey.keys[0], passphrase: passphrase }).then(function(unlocked){ - expect(unlocked.getKeyId().toHex()).to.equal(privateKey.getKeyId().toHex()); - expect(unlocked.subKeys[0].getKeyId().toHex()).to.equal(privateKey.subKeys[0].getKeyId().toHex()); - expect(unlocked.isDecrypted()).to.be.true; - expect(unlocked.keyPacket.privateParams).to.not.be.null; - // original key should be unchanged - expect(privateKey.isDecrypted()).to.be.false; - expect(privateKey.keyPacket.privateParams).to.be.null; - originalKey.subKeys[0].getKeyId(); // fill in keyid - expect(privateKey).to.deep.equal(originalKey); + expect(unlocked.key.getKeyId().toHex()).to.equal(privateKey.keys[0].getKeyId().toHex()); + expect(unlocked.key.isDecrypted()).to.be.true; }); }); - it('should fail for incorrect passphrase', async function() { - const originalKey = await openpgp.readArmoredKey(privateKey.armor()); + it('should fail for incorrect passphrase', function() { return openpgp.decryptKey({ - privateKey: privateKey, + privateKey: privateKey.keys[0], passphrase: 'incorrect' }).then(function() { throw new Error('Should not decrypt with incorrect passphrase'); }).catch(function(error){ expect(error.message).to.match(/Incorrect key passphrase/); - // original key should be unchanged - expect(privateKey.isDecrypted()).to.be.false; - expect(privateKey.keyPacket.privateParams).to.be.null; - expect(privateKey).to.deep.equal(originalKey); - }); - }); - - it('should fail for corrupted key', async function() { - const originalKey = await openpgp.readArmoredKey(privateKeyMismatchingParams.armor()); - return openpgp.decryptKey({ - privateKey: privateKeyMismatchingParams, - passphrase: 'userpass' - }).then(function() { - throw new Error('Should not decrypt corrupted key'); - }).catch(function(error) { - expect(error.message).to.match(/Key is invalid/); - expect(privateKeyMismatchingParams.isDecrypted()).to.be.false; - expect(privateKeyMismatchingParams.keyPacket.privateParams).to.be.null; - expect(privateKeyMismatchingParams).to.deep.equal(originalKey); - }); - }); - }); - - describe('encryptKey', function() { - it('should not change original key', async function() { - const { privateKeyArmored } = await openpgp.generateKey({ userIds: [{ name: 'test', email: 'test@test.com' }] }); - // read both keys from armored data to make sure all fields are exactly the same - const key = await openpgp.readArmoredKey(privateKeyArmored); - const originalKey = await openpgp.readArmoredKey(privateKeyArmored); - return openpgp.encryptKey({ - privateKey: key, - passphrase: passphrase - }).then(function(locked){ - expect(locked.getKeyId().toHex()).to.equal(key.getKeyId().toHex()); - expect(locked.subKeys[0].getKeyId().toHex()).to.equal(key.subKeys[0].getKeyId().toHex()); - expect(locked.isDecrypted()).to.be.false; - expect(locked.keyPacket.privateParams).to.be.null; - // original key should be unchanged - expect(key.isDecrypted()).to.be.true; - expect(key.keyPacket.privateParams).to.not.be.null; - originalKey.subKeys[0].getKeyId(); // fill in keyid - expect(key).to.deep.equal(originalKey); - }); - }); - - it('encrypted key can be decrypted', async function() { - const { key } = await openpgp.generateKey({ userIds: [{ name: 'test', email: 'test@test.com' }] }); - const locked = await openpgp.encryptKey({ - privateKey: key, - passphrase: passphrase - }); - expect(locked.isDecrypted()).to.be.false; - const unlocked = await openpgp.decryptKey({ - privateKey: locked, - passphrase: passphrase - }); - expect(unlocked.isDecrypted()).to.be.true; - }); - - it('should support multiple passphrases', async function() { - const { key } = await openpgp.generateKey({ userIds: [{ name: 'test', email: 'test@test.com' }] }); - const passphrases = ['123', '456']; - const locked = await openpgp.encryptKey({ - privateKey: key, - passphrase: passphrases }); - expect(locked.isDecrypted()).to.be.false; - await expect(openpgp.decryptKey({ - privateKey: locked, - passphrase: passphrases[0] - })).to.eventually.be.rejectedWith(/Incorrect key passphrase/); - const unlocked = await openpgp.decryptKey({ - privateKey: locked, - passphrase: passphrases - }); - expect(unlocked.isDecrypted()).to.be.true; - }); - - it('should encrypt gnu-dummy key', async function() { - const key = await openpgp.readArmoredKey(gnuDummyKeySigningSubkey); - const locked = await openpgp.encryptKey({ - privateKey: key, - passphrase: passphrase - }); - expect(key.isDecrypted()).to.be.true; - expect(locked.isDecrypted()).to.be.false; - expect(locked.primaryKey.isDummy()).to.be.true; - const unlocked = await openpgp.decryptKey({ - privateKey: locked, - passphrase: passphrase - }); - expect(key.isDecrypted()).to.be.true; - expect(unlocked.isDecrypted()).to.be.true; - expect(unlocked.primaryKey.isDummy()).to.be.true; }); }); it('Calling decrypt with not decrypted key leads to exception', async function() { const encOpt = { - message: openpgp.Message.fromText(plaintext), - publicKeys: publicKey + message: openpgp.message.fromText(plaintext), + publicKeys: publicKey.keys }; const decOpt = { - privateKeys: privateKey + privateKeys: privateKey.keys[0] }; const encrypted = await openpgp.encrypt(encOpt); - decOpt.message = await openpgp.readArmoredMessage(encrypted); + decOpt.message = await openpgp.message.readArmored(encrypted.data); await expect(openpgp.decrypt(decOpt)).to.be.rejectedWith('Error decrypting message: Private key is not decrypted.'); }); tryTests('CFB mode (asm.js)', tests, { - if: true, + if: !(typeof window !== 'undefined' && window.Worker), beforeEach: function() { - openpgp.config.aeadProtect = false; + openpgp.config.aead_protect = false; + } + }); + + tryTests('CFB mode (asm.js, worker)', tests, { + if: typeof window !== 'undefined' && window.Worker, + before: async function() { + await openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); + }, + beforeEach: function() { + openpgp.config.aead_protect = false; + }, + after: function() { + openpgp.destroyWorker(); } }); tryTests('GCM mode (V5 keys)', tests, { if: true, beforeEach: function() { - openpgp.config.aeadProtect = true; - openpgp.config.aeadMode = openpgp.enums.aead.experimentalGcm; - openpgp.config.v5Keys = true; + openpgp.config.aead_protect = true; + openpgp.config.aead_mode = openpgp.enums.aead.experimental_gcm; + openpgp.config.v5_keys = true; // Monkey-patch AEAD feature flag - publicKey.users[0].selfCertifications[0].features = [7]; - publicKey_2000_2008.users[0].selfCertifications[0].features = [7]; - publicKey_2038_2045.users[0].selfCertifications[0].features = [7]; + publicKey.keys[0].users[0].selfCertifications[0].features = [7]; + publicKey_2000_2008.keys[0].users[0].selfCertifications[0].features = [7]; + publicKey_2038_2045.keys[0].users[0].selfCertifications[0].features = [7]; } }); tryTests('EAX mode (small chunk size)', tests, { if: true, beforeEach: function() { - openpgp.config.aeadProtect = true; - openpgp.config.aeadChunkSizeByte = 0; + openpgp.config.aead_protect = true; + openpgp.config.aead_chunk_size_byte = 0; // Monkey-patch AEAD feature flag - publicKey.users[0].selfCertifications[0].features = [7]; - publicKey_2000_2008.users[0].selfCertifications[0].features = [7]; - publicKey_2038_2045.users[0].selfCertifications[0].features = [7]; + publicKey.keys[0].users[0].selfCertifications[0].features = [7]; + publicKey_2000_2008.keys[0].users[0].selfCertifications[0].features = [7]; + publicKey_2038_2045.keys[0].users[0].selfCertifications[0].features = [7]; } }); tryTests('OCB mode', tests, { if: !openpgp.config.ci, beforeEach: function() { - openpgp.config.aeadProtect = true; - openpgp.config.aeadMode = openpgp.enums.aead.ocb; + openpgp.config.aead_protect = true; + openpgp.config.aead_mode = openpgp.enums.aead.ocb; // Monkey-patch AEAD feature flag - publicKey.users[0].selfCertifications[0].features = [7]; - publicKey_2000_2008.users[0].selfCertifications[0].features = [7]; - publicKey_2038_2045.users[0].selfCertifications[0].features = [7]; + publicKey.keys[0].users[0].selfCertifications[0].features = [7]; + publicKey_2000_2008.keys[0].users[0].selfCertifications[0].features = [7]; + publicKey_2038_2045.keys[0].users[0].selfCertifications[0].features = [7]; } }); @@ -984,7 +957,7 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { let decryptedPrivateKey; beforeEach(async function() { if (!decryptedPrivateKey) { - await privateKey.decrypt(passphrase); + expect(await privateKey.keys[0].decrypt(passphrase)).to.be.true; decryptedPrivateKey = privateKey; } privateKey = decryptedPrivateKey; @@ -994,13 +967,11 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { return openpgp.encryptSessionKey({ data: sk, algorithm: 'aes128', - publicKeys: publicKey, - armor: false - }).then(async function(encrypted) { - const message = await openpgp.readMessage(encrypted); + publicKeys: publicKey.keys + }).then(function(encrypted) { return openpgp.decryptSessionKeys({ - message, - privateKeys: privateKey + message: encrypted.message, + privateKeys: privateKey.keys[0] }); }).then(function(decrypted) { expect(decrypted[0].data).to.deep.equal(sk); @@ -1011,12 +982,10 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { return openpgp.encryptSessionKey({ data: sk, algorithm: 'aes128', - passwords: password1, - armor: false - }).then(async function(encrypted) { - const message = await openpgp.readMessage(encrypted); + passwords: password1 + }).then(function(encrypted) { return openpgp.decryptSessionKeys({ - message, + message: encrypted.message, passwords: password1 }); }).then(function(decrypted) { @@ -1028,14 +997,12 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { return openpgp.encryptSessionKey({ data: sk, algorithm: 'aes128', - publicKeys: publicKey, - armor: false + publicKeys: publicKey.keys }).then(async function(encrypted) { - const message = await openpgp.readMessage(encrypted); - const invalidPrivateKey = await openpgp.readArmoredKey(priv_key); + const invalidPrivateKey = (await openpgp.key.readArmored(priv_key)).keys[0]; invalidPrivateKey.subKeys[0].bindingSignatures = []; return openpgp.decryptSessionKeys({ - message, + message: encrypted.message, privateKeys: invalidPrivateKey }).then(() => { throw new Error('Should not decrypt with invalid key'); @@ -1045,86 +1012,119 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { }); }); - it('roundtrip workflow: encrypt, decryptSessionKeys, decrypt with pgp key pair', async function () { - const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromText(plaintext), - publicKeys: publicKey - }); - const decryptedSessionKeys = await openpgp.decryptSessionKeys({ - message: await openpgp.readArmoredMessage(encrypted), - privateKeys: privateKey - }); - const decrypted = await openpgp.decrypt({ - message: await openpgp.readArmoredMessage(encrypted), - sessionKeys: decryptedSessionKeys[0] + it('roundtrip workflow: encrypt, decryptSessionKeys, decrypt with pgp key pair', function () { + let msgAsciiArmored; + return openpgp.encrypt({ + message: openpgp.message.fromText(plaintext), + publicKeys: publicKey.keys + }).then(async function (encrypted) { + msgAsciiArmored = encrypted.data; + return openpgp.decryptSessionKeys({ + message: await openpgp.message.readArmored(msgAsciiArmored), + privateKeys: privateKey.keys[0] + }); + + }).then(async function (decryptedSessionKeys) { + const message = await openpgp.message.readArmored(msgAsciiArmored); + return openpgp.decrypt({ + sessionKeys: decryptedSessionKeys[0], + message + }); + }).then(function (decrypted) { + expect(decrypted.data).to.equal(plaintext); }); - expect(decrypted.data).to.equal(plaintext); }); - it('roundtrip workflow: encrypt, decryptSessionKeys, decrypt with pgp key pair -- trailing spaces', async function () { + it('roundtrip workflow: encrypt, decryptSessionKeys, decrypt with pgp key pair -- trailing spaces', function () { const plaintext = 'space: \nspace and tab: \t\nno trailing space\n \ntab:\t\ntab and space:\t '; - const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromText(plaintext), - publicKeys: publicKey - }); - const decryptedSessionKeys = await openpgp.decryptSessionKeys({ - message: await openpgp.readArmoredMessage(encrypted), - privateKeys: privateKey - }); - const decrypted = await openpgp.decrypt({ - message: await openpgp.readArmoredMessage(encrypted), - sessionKeys: decryptedSessionKeys[0] + let msgAsciiArmored; + return openpgp.encrypt({ + message: openpgp.message.fromText(plaintext), + publicKeys: publicKey.keys + }).then(async function (encrypted) { + msgAsciiArmored = encrypted.data; + return openpgp.decryptSessionKeys({ + message: await openpgp.message.readArmored(msgAsciiArmored), + privateKeys: privateKey.keys[0] + }); + + }).then(async function (decryptedSessionKeys) { + const message = await openpgp.message.readArmored(msgAsciiArmored); + return openpgp.decrypt({ + sessionKeys: decryptedSessionKeys[0], + message + }); + }).then(function (decrypted) { + expect(decrypted.data).to.equal(plaintext); }); - expect(decrypted.data).to.equal(plaintext); }); - it('roundtrip workflow: encrypt, decryptSessionKeys, decrypt with password', async function () { - const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromText(plaintext), - passwords: password1 - }); - const decryptedSessionKeys = await openpgp.decryptSessionKeys({ - message: await openpgp.readArmoredMessage(encrypted), + it('roundtrip workflow: encrypt, decryptSessionKeys, decrypt with password', function () { + let msgAsciiArmored; + return openpgp.encrypt({ + message: openpgp.message.fromText(plaintext), passwords: password1 + }).then(async function (encrypted) { + msgAsciiArmored = encrypted.data; + return openpgp.decryptSessionKeys({ + message: await openpgp.message.readArmored(msgAsciiArmored), + passwords: password1 + }); + + }).then(async function (decryptedSessionKeys) { + return openpgp.decrypt({ + sessionKeys: decryptedSessionKeys[0], + message: await openpgp.message.readArmored(msgAsciiArmored) + }); + + }).then(function (decrypted) { + expect(decrypted.data).to.equal(plaintext); }); - const decrypted = await openpgp.decrypt({ - message: await openpgp.readArmoredMessage(encrypted), - sessionKeys: decryptedSessionKeys[0] - }); - expect(decrypted.data).to.equal(plaintext); }); - it('roundtrip workflow: encrypt with multiple passwords, decryptSessionKeys, decrypt with multiple passwords', async function () { - const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromText(plaintext), - passwords: [password1, password2] - }); - const decryptedSessionKeys = await openpgp.decryptSessionKeys({ - message: await openpgp.readArmoredMessage(encrypted), + it('roundtrip workflow: encrypt with multiple passwords, decryptSessionKeys, decrypt with multiple passwords', function () { + let msgAsciiArmored; + return openpgp.encrypt({ + message: openpgp.message.fromText(plaintext), passwords: [password1, password2] + }).then(async function (encrypted) { + msgAsciiArmored = encrypted.data; + return openpgp.decryptSessionKeys({ + message: await openpgp.message.readArmored(msgAsciiArmored), + passwords: [password1, password2] + }); + + }).then(async function (decryptedSessionKeys) { + return openpgp.decrypt({ + sessionKeys: decryptedSessionKeys, + message: await openpgp.message.readArmored(msgAsciiArmored) + }); + + }).then(function (decrypted) { + expect(decrypted.data).to.equal(plaintext); }); - const decrypted = await openpgp.decrypt({ - message: await openpgp.readArmoredMessage(encrypted), - sessionKeys: decryptedSessionKeys[0] - }); - expect(decrypted.data).to.equal(plaintext); }); - it('roundtrip workflow: encrypt twice with one password, decryptSessionKeys, only one session key', async function () { - const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromText(plaintext), + it('roundtrip workflow: encrypt twice with one password, decryptSessionKeys, only one session key', function () { + let msgAsciiArmored; + return openpgp.encrypt({ + message: openpgp.message.fromText(plaintext), passwords: [password1, password1] + }).then(async function (encrypted) { + msgAsciiArmored = encrypted.data; + return openpgp.decryptSessionKeys({ + message: await openpgp.message.readArmored(msgAsciiArmored), + passwords: password1 + }); + }).then(async function (decryptedSessionKeys) { + expect(decryptedSessionKeys.length).to.equal(1); + return openpgp.decrypt({ + sessionKeys: decryptedSessionKeys, + message: await openpgp.message.readArmored(msgAsciiArmored) + }); + }).then(function (decrypted) { + expect(decrypted.data).to.equal(plaintext); }); - const decryptedSessionKeys = await openpgp.decryptSessionKeys({ - message: await openpgp.readArmoredMessage(encrypted), - passwords: password1 - }); - expect(decryptedSessionKeys.length).to.equal(1); - const decrypted = await openpgp.decrypt({ - message: await openpgp.readArmoredMessage(encrypted), - sessionKeys: decryptedSessionKeys[0] - }); - expect(decrypted.data).to.equal(plaintext); }); }); @@ -1144,7 +1144,7 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { let decryptedPrivateKey; beforeEach(async function() { if (!decryptedPrivateKey) { - await privateKey.decrypt(passphrase); + expect(await privateKey.keys[0].decrypt(passphrase)).to.be.true; decryptedPrivateKey = privateKey; } privateKey = decryptedPrivateKey; @@ -1152,15 +1152,15 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { it('should encrypt then decrypt', function () { const encOpt = { - message: openpgp.Message.fromText(plaintext), - publicKeys: publicKey + message: openpgp.message.fromText(plaintext), + publicKeys: publicKey.keys }; const decOpt = { - privateKeys: privateKey + privateKeys: privateKey.keys }; return openpgp.encrypt(encOpt).then(async function (encrypted) { - expect(encrypted).to.match(/^-----BEGIN PGP MESSAGE/); - decOpt.message = await openpgp.readArmoredMessage(encrypted); + expect(encrypted.data).to.match(/^-----BEGIN PGP MESSAGE/); + decOpt.message = await openpgp.message.readArmored(encrypted.data); return openpgp.decrypt(decOpt); }).then(function (decrypted) { expect(decrypted.data).to.equal(plaintext); @@ -1170,19 +1170,19 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { }); it('should encrypt then decrypt with multiple private keys', async function () { - const privKeyDE = await openpgp.readArmoredKey(priv_key_de); + const privKeyDE = (await openpgp.key.readArmored(priv_key_de)).keys[0]; await privKeyDE.decrypt(passphrase); const encOpt = { - message: openpgp.Message.fromText(plaintext), - publicKeys: publicKey + message: openpgp.message.fromText(plaintext), + publicKeys: publicKey.keys }; const decOpt = { - privateKeys: [privKeyDE, privateKey] + privateKeys: [privKeyDE, privateKey.keys[0]] }; return openpgp.encrypt(encOpt).then(async function (encrypted) { - expect(encrypted).to.match(/^-----BEGIN PGP MESSAGE/); - decOpt.message = await openpgp.readArmoredMessage(encrypted); + expect(encrypted.data).to.match(/^-----BEGIN PGP MESSAGE/); + decOpt.message = await openpgp.message.readArmored(encrypted.data); return openpgp.decrypt(decOpt); }).then(function (decrypted) { expect(decrypted.data).to.equal(plaintext); @@ -1193,16 +1193,16 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { it('should encrypt then decrypt with wildcard', function () { const encOpt = { - message: openpgp.Message.fromText(plaintext), - publicKeys: publicKey, + message: openpgp.message.fromText(plaintext), + publicKeys: publicKey.keys, wildcard: true }; const decOpt = { - privateKeys: privateKey + privateKeys: privateKey.keys }; return openpgp.encrypt(encOpt).then(async function (encrypted) { - expect(encrypted).to.match(/^-----BEGIN PGP MESSAGE/); - decOpt.message = await openpgp.readArmoredMessage(encrypted); + expect(encrypted.data).to.match(/^-----BEGIN PGP MESSAGE/); + decOpt.message = await openpgp.message.readArmored(encrypted.data); return openpgp.decrypt(decOpt); }).then(function (decrypted) { expect(decrypted.data).to.equal(plaintext); @@ -1212,20 +1212,20 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { }); it('should encrypt then decrypt with wildcard with multiple private keys', async function () { - const privKeyDE = await openpgp.readArmoredKey(priv_key_de); + const privKeyDE = (await openpgp.key.readArmored(priv_key_de)).keys[0]; await privKeyDE.decrypt(passphrase); const encOpt = { - message: openpgp.Message.fromText(plaintext), - publicKeys: publicKey, + message: openpgp.message.fromText(plaintext), + publicKeys: publicKey.keys, wildcard: true }; const decOpt = { - privateKeys: [privKeyDE, privateKey] + privateKeys: [privKeyDE, privateKey.keys[0]] }; return openpgp.encrypt(encOpt).then(async function (encrypted) { - expect(encrypted).to.match(/^-----BEGIN PGP MESSAGE/); - decOpt.message = await openpgp.readArmoredMessage(encrypted); + expect(encrypted.data).to.match(/^-----BEGIN PGP MESSAGE/); + decOpt.message = await openpgp.message.readArmored(encrypted.data); return openpgp.decrypt(decOpt); }).then(function (decrypted) { expect(decrypted.data).to.equal(plaintext); @@ -1234,41 +1234,44 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { }); }); - it('should encrypt then decrypt using returned session key', async function () { - const sessionKey = await openpgp.generateSessionKey({ - publicKeys: publicKey - }); - const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromText(plaintext), - sessionKey - }); - expect(encrypted).to.match(/^-----BEGIN PGP MESSAGE/); - const decrypted = await openpgp.decrypt({ - message: await openpgp.readArmoredMessage(encrypted), - sessionKeys: sessionKey + it('should encrypt then decrypt using returned session key', function () { + const encOpt = { + message: openpgp.message.fromText(plaintext), + publicKeys: publicKey.keys, + returnSessionKey: true + }; + + return openpgp.encrypt(encOpt).then(async function (encrypted) { + expect(encrypted.data).to.match(/^-----BEGIN PGP MESSAGE/); + const decOpt = { + sessionKeys: encrypted.sessionKey, + message: await openpgp.message.readArmored(encrypted.data) + }; + return openpgp.decrypt(decOpt); + }).then(function (decrypted) { + expect(decrypted.data).to.equal(plaintext); + expect(decrypted.signatures).to.exist; + expect(decrypted.signatures.length).to.equal(0); }); - expect(decrypted.data).to.equal(plaintext); - expect(decrypted.signatures).to.exist; - expect(decrypted.signatures.length).to.equal(0); }); it('should encrypt using custom session key and decrypt using session key', async function () { const sessionKey = { - data: await crypto.generateSessionKey('aes256'), + data: await openpgp.crypto.generateSessionKey('aes256'), algorithm: 'aes256' }; const encOpt = { - message: openpgp.Message.fromText(plaintext), + message: openpgp.message.fromText(plaintext), sessionKey: sessionKey, - publicKeys: publicKey + publicKeys: publicKey.keys }; const decOpt = { sessionKeys: sessionKey }; return openpgp.encrypt(encOpt).then(async function (encrypted) { - expect(encrypted).to.match(/^-----BEGIN PGP MESSAGE/); - decOpt.message = await openpgp.readArmoredMessage(encrypted); - expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.AEADEncryptedData)).to.equal(false); + expect(encrypted.data).to.match(/^-----BEGIN PGP MESSAGE/); + decOpt.message = await openpgp.message.readArmored(encrypted.data); + expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.symEncryptedAEADProtected)).to.equal(false); return openpgp.decrypt(decOpt); }).then(function (decrypted) { expect(decrypted.data).to.equal(plaintext); @@ -1277,21 +1280,21 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { it('should encrypt using custom session key and decrypt using private key', async function () { const sessionKey = { - data: await crypto.generateSessionKey('aes128'), + data: await openpgp.crypto.generateSessionKey('aes128'), algorithm: 'aes128' }; const encOpt = { - message: openpgp.Message.fromText(plaintext), + message: openpgp.message.fromText(plaintext), sessionKey: sessionKey, - publicKeys: publicKey + publicKeys: publicKey.keys }; const decOpt = { - privateKeys: privateKey + privateKeys: privateKey.keys[0] }; return openpgp.encrypt(encOpt).then(async function (encrypted) { - expect(encrypted).to.match(/^-----BEGIN PGP MESSAGE/); - decOpt.message = await openpgp.readArmoredMessage(encrypted); - expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.AEADEncryptedData)).to.equal(false); + expect(encrypted.data).to.match(/^-----BEGIN PGP MESSAGE/); + decOpt.message = await openpgp.message.readArmored(encrypted.data); + expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.symEncryptedAEADProtected)).to.equal(false); return openpgp.decrypt(decOpt); }).then(function (decrypted) { expect(decrypted.data).to.equal(plaintext); @@ -1300,22 +1303,22 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { it('should encrypt/sign and decrypt/verify', function () { const encOpt = { - message: openpgp.Message.fromText(plaintext), - publicKeys: publicKey, - privateKeys: privateKey + message: openpgp.message.fromText(plaintext), + publicKeys: publicKey.keys, + privateKeys: privateKey.keys }; const decOpt = { - privateKeys: privateKey, - publicKeys: publicKey + privateKeys: privateKey.keys[0], + publicKeys: publicKey.keys }; return openpgp.encrypt(encOpt).then(async function (encrypted) { - decOpt.message = await openpgp.readArmoredMessage(encrypted); - expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.AEADEncryptedData)).to.equal(openpgp.config.aeadProtect); + decOpt.message = await openpgp.message.readArmored(encrypted.data); + expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.symEncryptedAEADProtected)).to.equal(openpgp.config.aead_protect); return openpgp.decrypt(decOpt); }).then(async function (decrypted) { expect(decrypted.data).to.equal(plaintext); expect(decrypted.signatures[0].valid).to.be.true; - const signingKey = await privateKey.getSigningKey(); + const signingKey = await privateKey.keys[0].getSigningKey(); expect(decrypted.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); @@ -1323,22 +1326,22 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { it('should encrypt/sign and decrypt/verify (no AEAD support)', function () { const encOpt = { - message: openpgp.Message.fromText(plaintext), - publicKeys: publicKeyNoAEAD, - privateKeys: privateKey + message: openpgp.message.fromText(plaintext), + publicKeys: publicKeyNoAEAD.keys, + privateKeys: privateKey.keys }; const decOpt = { - privateKeys: privateKey, - publicKeys: publicKeyNoAEAD + privateKeys: privateKey.keys[0], + publicKeys: publicKeyNoAEAD.keys }; return openpgp.encrypt(encOpt).then(async function (encrypted) { - decOpt.message = await openpgp.readArmoredMessage(encrypted); - expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.AEADEncryptedData)).to.equal(false); + decOpt.message = await openpgp.message.readArmored(encrypted.data); + expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.symEncryptedAEADProtected)).to.equal(false); return openpgp.decrypt(decOpt); }).then(async function (decrypted) { expect(decrypted.data).to.equal(plaintext); expect(decrypted.signatures[0].valid).to.be.true; - const signingKey = await privateKey.getSigningKey(); + const signingKey = await privateKey.keys[0].getSigningKey(); expect(decrypted.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); @@ -1347,147 +1350,189 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { it('should encrypt/sign and decrypt/verify with generated key', function () { const genOpt = { userIds: [{ name: 'Test User', email: 'text@example.com' }], + numBits: 512 }; + if (openpgp.util.getWebCryptoAll()) { genOpt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys return openpgp.generateKey(genOpt).then(async function(newKey) { - const newPublicKey = await openpgp.readArmoredKey(newKey.publicKeyArmored); - const newPrivateKey = await openpgp.readArmoredKey(newKey.privateKeyArmored); + const newPublicKey = await openpgp.key.readArmored(newKey.publicKeyArmored); + const newPrivateKey = await openpgp.key.readArmored(newKey.privateKeyArmored); const encOpt = { - message: openpgp.Message.fromText(plaintext), - publicKeys: newPublicKey, - privateKeys: newPrivateKey + message: openpgp.message.fromText(plaintext), + publicKeys: newPublicKey.keys, + privateKeys: newPrivateKey.keys }; const decOpt = { - privateKeys: newPrivateKey, - publicKeys: newPublicKey + privateKeys: newPrivateKey.keys[0], + publicKeys: newPublicKey.keys }; return openpgp.encrypt(encOpt).then(async function (encrypted) { - decOpt.message = await openpgp.readArmoredMessage(encrypted); - expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.AEADEncryptedData)).to.equal(openpgp.config.aeadProtect); + decOpt.message = await openpgp.message.readArmored(encrypted.data); + expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.symEncryptedAEADProtected)).to.equal(openpgp.config.aead_protect); return openpgp.decrypt(decOpt); }).then(async function (decrypted) { expect(decrypted.data).to.equal(plaintext); expect(decrypted.signatures[0].valid).to.be.true; - const signingKey = await newPrivateKey.getSigningKey(); + const signingKey = await newPrivateKey.keys[0].getSigningKey(); expect(decrypted.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); }); }); - it('should encrypt/sign and decrypt/verify with generated key and detached signatures', async function () { - const newKey = await openpgp.generateKey({ + it('should encrypt/sign and decrypt/verify with generated key and detached signatures', function () { + const genOpt = { userIds: [{ name: 'Test User', email: 'text@example.com' }], - }); - const newPublicKey = await openpgp.readArmoredKey(newKey.publicKeyArmored); - const newPrivateKey = await openpgp.readArmoredKey(newKey.privateKeyArmored); + numBits: 512 + }; + if (openpgp.util.getWebCryptoAll()) { genOpt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys - const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromText(plaintext), - publicKeys: newPublicKey - }); - const signed = await openpgp.sign({ - message: openpgp.Message.fromText(plaintext), - privateKeys: newPrivateKey, - detached: true - }); - const message = await openpgp.readArmoredMessage(encrypted); - expect(!!message.packets.findPacket(openpgp.enums.packet.AEADEncryptedData)).to.equal(openpgp.config.aeadProtect); - const decrypted = await openpgp.decrypt({ - message, - signature: await openpgp.readArmoredSignature(signed), - privateKeys: newPrivateKey, - publicKeys: newPublicKey + return openpgp.generateKey(genOpt).then(async function(newKey) { + const newPublicKey = await openpgp.key.readArmored(newKey.publicKeyArmored); + const newPrivateKey = await openpgp.key.readArmored(newKey.privateKeyArmored); + + const encOpt = { + message: openpgp.message.fromText(plaintext), + publicKeys: newPublicKey.keys, + privateKeys: newPrivateKey.keys, + detached: true + }; + const decOpt = { + privateKeys: newPrivateKey.keys[0], + publicKeys: newPublicKey.keys + }; + return openpgp.encrypt(encOpt).then(async function (encrypted) { + decOpt.message = await openpgp.message.readArmored(encrypted.data); + decOpt.signature = await openpgp.signature.readArmored(encrypted.signature); + expect(!!decOpt.message.packets.findPacket(openpgp.enums.packet.symEncryptedAEADProtected)).to.equal(openpgp.config.aead_protect); + return openpgp.decrypt(decOpt); + }).then(async function (decrypted) { + expect(decrypted.data).to.equal(plaintext); + expect(decrypted.signatures[0].valid).to.be.true; + const signingKey = await newPrivateKey.keys[0].getSigningKey(); + expect(decrypted.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); + expect(decrypted.signatures[0].signature.packets.length).to.equal(1); + }); }); - expect(decrypted.data).to.equal(plaintext); - expect(decrypted.signatures[0].valid).to.be.true; - const signingKey = await newPrivateKey.getSigningKey(); - expect(decrypted.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); - expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); it('should encrypt/sign and decrypt/verify with null string input', function () { const encOpt = { - message: openpgp.Message.fromText(''), - publicKeys: publicKey, - privateKeys: privateKey + message: openpgp.message.fromText(''), + publicKeys: publicKey.keys, + privateKeys: privateKey.keys }; const decOpt = { - privateKeys: privateKey, - publicKeys: publicKey + privateKeys: privateKey.keys[0], + publicKeys: publicKey.keys }; return openpgp.encrypt(encOpt).then(async function (encrypted) { - decOpt.message = await openpgp.readArmoredMessage(encrypted); + decOpt.message = await openpgp.message.readArmored(encrypted.data); return openpgp.decrypt(decOpt); }).then(async function (decrypted) { expect(decrypted.data).to.equal(''); expect(decrypted.signatures[0].valid).to.be.true; - const signingKey = await privateKey.getSigningKey(); + const signingKey = await privateKey.keys[0].getSigningKey(); expect(decrypted.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); }); - it('should encrypt/sign and decrypt/verify with detached signatures', async function () { - const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromText(plaintext), - publicKeys: publicKey - }); - const signed = await openpgp.sign({ - message: openpgp.Message.fromText(plaintext), - privateKeys: privateKey, + it('should encrypt/sign and decrypt/verify with detached signatures', function () { + const encOpt = { + message: openpgp.message.fromText(plaintext), + publicKeys: publicKey.keys, + privateKeys: privateKey.keys, detached: true + }; + const decOpt = { + privateKeys: privateKey.keys[0], + publicKeys: publicKey.keys + }; + return openpgp.encrypt(encOpt).then(async function (encrypted) { + decOpt.message = await openpgp.message.readArmored(encrypted.data); + decOpt.signature = await openpgp.signature.readArmored(encrypted.signature); + return openpgp.decrypt(decOpt); + }).then(async function (decrypted) { + expect(decrypted.data).to.equal(plaintext); + expect(decrypted.signatures[0].valid).to.be.true; + const signingKey = await privateKey.keys[0].getSigningKey(); + expect(decrypted.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); + expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); - const decrypted = await openpgp.decrypt({ - message: await openpgp.readArmoredMessage(encrypted), - signature: await openpgp.readArmoredSignature(signed), - privateKeys: privateKey, - publicKeys: publicKey + }); + + it('should encrypt and decrypt/verify with detached signature input and detached flag set for encryption', function () { + const signOpt = { + message: openpgp.message.fromText(plaintext), + privateKeys: privateKey.keys[0], + detached: true + }; + + const encOpt = { + message: openpgp.message.fromText(plaintext), + publicKeys: publicKey.keys, + detached: true + }; + + const decOpt = { + privateKeys: privateKey.keys[0], + publicKeys: publicKey.keys[0] + }; + + return openpgp.sign(signOpt).then(async function (signed) { + encOpt.signature = await openpgp.signature.readArmored(signed.signature); + return openpgp.encrypt(encOpt); + }).then(async function (encrypted) { + decOpt.message = await openpgp.message.readArmored(encrypted.data); + decOpt.signature = await openpgp.signature.readArmored(encrypted.signature); + return openpgp.decrypt(decOpt); + }).then(async function (decrypted) { + expect(decrypted.data).to.equal(plaintext); + expect(decrypted.signatures[0].valid).to.be.true; + const signingKey = await privateKey.keys[0].getSigningKey(); + expect(decrypted.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); + expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); - expect(decrypted.data).to.equal(plaintext); - expect(decrypted.signatures[0].valid).to.be.true; - const signingKey = await privateKey.getSigningKey(); - expect(decrypted.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); - expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); - it('should encrypt and decrypt/verify with detached signature as input for encryption', async function () { + it('should encrypt and decrypt/verify with detached signature as input and detached flag not set for encryption', async function () { const plaintext = "  \t┍ͤ޵၂༫዇◧˘˻ᙑ᎚⏴ំந⛑nٓኵΉⅶ⋋ŵ⋲΂ͽᣏ₅ᄶɼ┋⌔û᬴Ƚᔡᧅ≃ṱἆ⃷݂૿ӌ᰹෇ٹჵ⛇໶⛌  \t\n한국어/조선말"; - const privKeyDE = await openpgp.readArmoredKey(priv_key_de); + const privKeyDE = (await openpgp.key.readArmored(priv_key_de)).keys[0]; await privKeyDE.decrypt(passphrase); - const pubKeyDE = await openpgp.readArmoredKey(pub_key_de); + const pubKeyDE = (await openpgp.key.readArmored(pub_key_de)).keys[0]; const signOpt = { - message: openpgp.Message.fromText(plaintext), + message: openpgp.message.fromText(plaintext), privateKeys: privKeyDE, detached: true }; const encOpt = { - message: openpgp.Message.fromText(plaintext), - publicKeys: publicKey, - privateKeys: privateKey + message: openpgp.message.fromText(plaintext), + publicKeys: publicKey.keys, + privateKeys: privateKey.keys[0] }; const decOpt = { - privateKeys: privateKey, - publicKeys: [publicKey, pubKeyDE] + privateKeys: privateKey.keys[0], + publicKeys: [publicKey.keys[0], pubKeyDE] }; return openpgp.sign(signOpt).then(async function (signed) { - encOpt.signature = await openpgp.readArmoredSignature(signed); + encOpt.signature = await openpgp.signature.readArmored(signed.signature); return openpgp.encrypt(encOpt); }).then(async function (encrypted) { - decOpt.message = await openpgp.readArmoredMessage(encrypted); + decOpt.message = await openpgp.message.readArmored(encrypted.data); return openpgp.decrypt(decOpt); }).then(async function (decrypted) { let signingKey; expect(decrypted.data).to.equal(plaintext); expect(decrypted.signatures[0].valid).to.be.true; - signingKey = await privateKey.getSigningKey(); + signingKey = await privateKey.keys[0].getSigningKey(); expect(decrypted.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); expect(decrypted.signatures[1].valid).to.be.true; @@ -1497,33 +1542,67 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { }); }); - it('should fail to encrypt and decrypt/verify with detached signature as input for encryption with wrong public key', async function () { + it('should fail to encrypt and decrypt/verify with detached signature input and detached flag set for encryption with wrong public key', async function () { + const signOpt = { + message: openpgp.message.fromText(plaintext), + privateKeys: privateKey.keys, + detached: true + }; + + const encOpt = { + message: openpgp.message.fromText(plaintext), + publicKeys: publicKey.keys, + detached: true + }; + + const decOpt = { + privateKeys: privateKey.keys[0], + publicKeys: (await openpgp.key.readArmored(wrong_pubkey)).keys + }; + + return openpgp.sign(signOpt).then(async function (signed) { + encOpt.signature = await openpgp.signature.readArmored(signed.signature); + return openpgp.encrypt(encOpt); + }).then(async function (encrypted) { + decOpt.message = await openpgp.message.readArmored(encrypted.data); + decOpt.signature = await openpgp.signature.readArmored(encrypted.signature); + return openpgp.decrypt(decOpt); + }).then(async function (decrypted) { + expect(decrypted.data).to.equal(plaintext); + expect(decrypted.signatures[0].valid).to.be.null; + const signingKey = await privateKey.keys[0].getSigningKey(); + expect(decrypted.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); + expect(decrypted.signatures[0].signature.packets.length).to.equal(1); + }); + }); + + it('should fail to encrypt and decrypt/verify with detached signature as input and detached flag not set for encryption with wrong public key', async function () { const signOpt = { - message: openpgp.Message.fromText(plaintext), - privateKeys: privateKey, + message: openpgp.message.fromText(plaintext), + privateKeys: privateKey.keys, detached: true }; const encOpt = { - message: openpgp.Message.fromText(plaintext), - publicKeys: publicKey + message: openpgp.message.fromText(plaintext), + publicKeys: publicKey.keys }; const decOpt = { - privateKeys: privateKey, - publicKeys: await openpgp.readArmoredKey(wrong_pubkey) + privateKeys: privateKey.keys[0], + publicKeys: (await openpgp.key.readArmored(wrong_pubkey)).keys }; return openpgp.sign(signOpt).then(async function (signed) { - encOpt.signature = await openpgp.readArmoredSignature(signed); + encOpt.signature = await openpgp.signature.readArmored(signed.signature); return openpgp.encrypt(encOpt); }).then(async function (encrypted) { - decOpt.message = await openpgp.readArmoredMessage(encrypted); + decOpt.message = await openpgp.message.readArmored(encrypted.data); return openpgp.decrypt(decOpt); }).then(async function (decrypted) { expect(decrypted.data).to.equal(plaintext); expect(decrypted.signatures[0].valid).to.be.null; - const signingKey = await privateKey.getSigningKey(); + const signingKey = await privateKey.keys[0].getSigningKey(); expect(decrypted.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); @@ -1531,21 +1610,21 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { it('should fail to verify decrypted data with wrong public pgp key', async function () { const encOpt = { - message: openpgp.Message.fromText(plaintext), - publicKeys: publicKey, - privateKeys: privateKey + message: openpgp.message.fromText(plaintext), + publicKeys: publicKey.keys, + privateKeys: privateKey.keys }; const decOpt = { - privateKeys: privateKey, - publicKeys: await openpgp.readArmoredKey(wrong_pubkey) + privateKeys: privateKey.keys[0], + publicKeys: (await openpgp.key.readArmored(wrong_pubkey)).keys }; return openpgp.encrypt(encOpt).then(async function (encrypted) { - decOpt.message = await openpgp.readArmoredMessage(encrypted); + decOpt.message = await openpgp.message.readArmored(encrypted.data); return openpgp.decrypt(decOpt); }).then(async function (decrypted) { expect(decrypted.data).to.equal(plaintext); expect(decrypted.signatures[0].valid).to.be.null; - const signingKey = await privateKey.getSigningKey(); + const signingKey = await privateKey.keys[0].getSigningKey(); expect(decrypted.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); @@ -1553,21 +1632,21 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { it('should fail to verify decrypted null string with wrong public pgp key', async function () { const encOpt = { - message: openpgp.Message.fromText(''), - publicKeys: publicKey, - privateKeys: privateKey + message: openpgp.message.fromText(''), + publicKeys: publicKey.keys, + privateKeys: privateKey.keys }; const decOpt = { - privateKeys: privateKey, - publicKeys: await openpgp.readArmoredKey(wrong_pubkey) + privateKeys: privateKey.keys[0], + publicKeys: (await openpgp.key.readArmored(wrong_pubkey)).keys }; return openpgp.encrypt(encOpt).then(async function (encrypted) { - decOpt.message = await openpgp.readArmoredMessage(encrypted); + decOpt.message = await openpgp.message.readArmored(encrypted.data); return openpgp.decrypt(decOpt); }).then(async function (decrypted) { expect(decrypted.data).to.equal(''); expect(decrypted.signatures[0].valid).to.be.null; - const signingKey = await privateKey.getSigningKey(); + const signingKey = await privateKey.keys[0].getSigningKey(); expect(decrypted.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); @@ -1575,73 +1654,74 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { it('should successfully decrypt signed message without public keys to verify', async function () { const encOpt = { - message: openpgp.Message.fromText(plaintext), - publicKeys: publicKey, - privateKeys: privateKey + message: openpgp.message.fromText(plaintext), + publicKeys: publicKey.keys, + privateKeys: privateKey.keys }; const decOpt = { - privateKeys: privateKey + privateKeys: privateKey.keys[0] }; return openpgp.encrypt(encOpt).then(async function (encrypted) { - decOpt.message = await openpgp.readArmoredMessage(encrypted); + decOpt.message = await openpgp.message.readArmored(encrypted.data); return openpgp.decrypt(decOpt); }).then(async function (decrypted) { expect(decrypted.data).to.equal(plaintext); expect(decrypted.signatures[0].valid).to.be.null; - const signingKey = await privateKey.getSigningKey(); + const signingKey = await privateKey.keys[0].getSigningKey(); expect(decrypted.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); }); it('should fail to verify decrypted data with wrong public pgp key with detached signatures', async function () { - const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromText(plaintext), - publicKeys: publicKey - }); - const signed = await openpgp.sign({ - message: openpgp.Message.fromText(plaintext), - privateKeys: privateKey, + const encOpt = { + message: openpgp.message.fromText(plaintext), + publicKeys: publicKey.keys, + privateKeys: privateKey.keys, detached: true + }; + const decOpt = { + privateKeys: privateKey.keys[0], + publicKeys: (await openpgp.key.readArmored(wrong_pubkey)).keys + }; + return openpgp.encrypt(encOpt).then(async function (encrypted) { + decOpt.message = await openpgp.message.readArmored(encrypted.data); + decOpt.signature = await openpgp.signature.readArmored(encrypted.signature); + return openpgp.decrypt(decOpt); + }).then(async function (decrypted) { + expect(decrypted.data).to.equal(plaintext); + expect(decrypted.signatures[0].valid).to.be.null; + const signingKey = await privateKey.keys[0].getSigningKey(); + expect(decrypted.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); + expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); - const decrypted = await openpgp.decrypt({ - message: await openpgp.readArmoredMessage(encrypted), - signature: await openpgp.readArmoredSignature(signed), - privateKeys: privateKey, - publicKeys: await openpgp.readArmoredKey(wrong_pubkey) - }); - expect(decrypted.data).to.equal(plaintext); - expect(decrypted.signatures[0].valid).to.be.null; - const signingKey = await privateKey.getSigningKey(); - expect(decrypted.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); - expect(decrypted.signatures[0].signature.packets.length).to.equal(1); }); it('should encrypt and decrypt/verify both signatures when signed with two private keys', async function () { - const privKeyDE = await openpgp.readArmoredKey(priv_key_de); + const privKeyDE = (await openpgp.key.readArmored(priv_key_de)).keys[0]; await privKeyDE.decrypt(passphrase); - const pubKeyDE = await openpgp.readArmoredKey(pub_key_de); + const pubKeyDE = (await openpgp.key.readArmored(pub_key_de)).keys[0]; const encOpt = { - message: openpgp.Message.fromText(plaintext), - publicKeys: publicKey, - privateKeys: [privateKey, privKeyDE] + message: openpgp.message.fromText(plaintext), + publicKeys: publicKey.keys, + privateKeys: [privateKey.keys[0], privKeyDE] }; const decOpt = { - privateKeys: privateKey, - publicKeys: [publicKey, pubKeyDE] + privateKeys: privateKey.keys[0], + publicKeys: [publicKey.keys[0], pubKeyDE] }; return openpgp.encrypt(encOpt).then(async function (encrypted) { - decOpt.message = await openpgp.readArmoredMessage(encrypted); + decOpt.message = await openpgp.message.readArmored(encrypted.data); return openpgp.decrypt(decOpt); }).then(async function (decrypted) { let signingKey; expect(decrypted.data).to.equal(plaintext); expect(decrypted.signatures[0].valid).to.be.true; - signingKey = await privateKey.getSigningKey(); + signingKey = await privateKey.keys[0].getSigningKey(); expect(decrypted.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); expect(decrypted.signatures[1].valid).to.be.true; @@ -1653,8 +1733,8 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { it('should fail to decrypt modified message', async function() { const { privateKeyArmored } = await openpgp.generateKey({ curve: 'curve25519', userIds: [{ email: 'test@email.com' }] }); - const key = await openpgp.readArmoredKey(privateKeyArmored); - const data = await openpgp.encrypt({ message: openpgp.Message.fromBinary(new Uint8Array(500)), publicKeys: [key.toPublic()] }); + const { keys: [key] } = await openpgp.key.readArmored(privateKeyArmored); + const { data } = await openpgp.encrypt({ message: openpgp.message.fromBinary(new Uint8Array(500)), publicKeys: [key.toPublic()] }); let badSumEncrypted = data.replace(/\n=[a-zA-Z0-9/+]{4}/, '\n=aaaa'); if (badSumEncrypted === data) { // checksum was already =aaaa badSumEncrypted = data.replace(/\n=[a-zA-Z0-9/+]{4}/, '\n=bbbb'); @@ -1664,12 +1744,17 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { } const badBodyEncrypted = data.replace(/\n=([a-zA-Z0-9/+]{4})/, 'aaa\n=$1'); for (let allow_streaming = 1; allow_streaming >= 0; allow_streaming--) { - openpgp.config.allowUnauthenticatedStream = !!allow_streaming; + openpgp.config.allow_unauthenticated_stream = !!allow_streaming; + if (openpgp.getWorker()) { + openpgp.getWorker().workers.forEach(worker => { + worker.postMessage({ event: 'configure', config: openpgp.config }); + }); + } await Promise.all([badSumEncrypted, badBodyEncrypted].map(async (encrypted, i) => { await Promise.all([ encrypted, openpgp.stream.toStream(encrypted), - new openpgp.stream.ReadableStream({ + new ReadableStream({ start() { this.remaining = encrypted.split('\n'); }, @@ -1685,7 +1770,7 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { ].map(async (encrypted, j) => { let stepReached = 0; try { - const message = await openpgp.readArmoredMessage(encrypted); + const message = await openpgp.message.readArmored(encrypted); stepReached = 1; const { data: decrypted } = await openpgp.decrypt({ message: message, privateKeys: [key] }); stepReached = 2; @@ -1694,7 +1779,7 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { expect(e.message).to.match(/Ascii armor integrity check on message failed/); expect(stepReached).to.equal( j === 0 ? 0 : - (openpgp.config.aeadChunkSizeByte === 0 && (j === 2 || util.detectNode() || util.getHardwareConcurrency() < 8)) || (!openpgp.config.aeadProtect && openpgp.config.allowUnauthenticatedStream) ? 2 : + (openpgp.config.aead_chunk_size_byte === 0 && (j === 2 || openpgp.util.detectNode() || openpgp.util.getHardwareConcurrency() < 8)) || (!openpgp.config.aead_protect && openpgp.config.allow_unauthenticated_stream) ? 2 : 1 ); return; @@ -1704,33 +1789,24 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { })); } }); - - it('should fail to decrypt unarmored message with garbage data appended', async function() { - const { key } = await openpgp.generateKey({ userIds: {} }); - const message = await openpgp.encrypt({ message: openpgp.Message.fromText('test'), publicKeys: key, privateKeys: key, armor: false }); - const encrypted = util.concat([message, new Uint8Array([11])]); - await expect( - openpgp.decrypt({ message: await openpgp.readMessage(encrypted), privateKeys: key, publicKeys: key }) - ).to.be.rejectedWith('Error during parsing. This message / key probably does not conform to a valid OpenPGP format.'); - }); }); describe('ELG / DSA encrypt, decrypt, sign, verify', function() { it('round trip test', async function () { - const pubKeyDE = await openpgp.readArmoredKey(pub_key_de); - const privKeyDE = await openpgp.readArmoredKey(priv_key_de); + const pubKeyDE = (await openpgp.key.readArmored(pub_key_de)).keys[0]; + const privKeyDE = (await openpgp.key.readArmored(priv_key_de)).keys[0]; await privKeyDE.decrypt(passphrase); pubKeyDE.users[0].selfCertifications[0].features = [7]; // Monkey-patch AEAD feature flag return openpgp.encrypt({ publicKeys: pubKeyDE, privateKeys: privKeyDE, - message: openpgp.Message.fromText(plaintext) + message: openpgp.message.fromText(plaintext) }).then(async function (encrypted) { return openpgp.decrypt({ privateKeys: privKeyDE, publicKeys: pubKeyDE, - message: await openpgp.readArmoredMessage(encrypted) + message: await openpgp.message.readArmored(encrypted.data) }); }).then(async function (decrypted) { expect(decrypted.data).to.exist; @@ -1796,9 +1872,9 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'); it('Decrypt message', async function() { - const privKey = await openpgp.readArmoredKey(priv_key); + const privKey = (await openpgp.key.readArmored(priv_key)).keys[0]; await privKey.decrypt('1234'); - const message = await openpgp.readArmoredMessage(pgp_msg); + const message = await openpgp.message.readArmored(pgp_msg); return openpgp.decrypt({ privateKeys:privKey, message:message }).then(function(decrypted) { expect(decrypted.data).to.equal('hello 3des\n'); @@ -1811,14 +1887,14 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { it('should encrypt and decrypt with one password', function () { const encOpt = { - message: openpgp.Message.fromText(plaintext), + message: openpgp.message.fromText(plaintext), passwords: password1 }; const decOpt = { passwords: password1 }; return openpgp.encrypt(encOpt).then(async function (encrypted) { - decOpt.message = await openpgp.readArmoredMessage(encrypted); + decOpt.message = await openpgp.message.readArmored(encrypted.data); return openpgp.decrypt(decOpt); }).then(function (decrypted) { expect(decrypted.data).to.equal(plaintext); @@ -1828,14 +1904,14 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { it('should encrypt and decrypt with two passwords', function () { const encOpt = { - message: openpgp.Message.fromText(plaintext), + message: openpgp.message.fromText(plaintext), passwords: [password1, password2] }; const decOpt = { passwords: password2 }; return openpgp.encrypt(encOpt).then(async function (encrypted) { - decOpt.message = await openpgp.readArmoredMessage(encrypted); + decOpt.message = await openpgp.message.readArmored(encrypted.data); return openpgp.decrypt(decOpt); }).then(function (decrypted) { expect(decrypted.data).to.equal(plaintext); @@ -1845,15 +1921,15 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { it('should encrypt and decrypt with password and not ascii armor', function () { const encOpt = { - message: openpgp.Message.fromText(plaintext), + message: openpgp.message.fromText(plaintext), passwords: password1, armor: false }; const decOpt = { passwords: password1 }; - return openpgp.encrypt(encOpt).then(async function (encrypted) { - decOpt.message = await openpgp.readMessage(encrypted); + return openpgp.encrypt(encOpt).then(function (encrypted) { + decOpt.message = encrypted.message; return openpgp.decrypt(decOpt); }).then(function (decrypted) { expect(decrypted.data).to.equal(plaintext); @@ -1861,9 +1937,10 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { }); }); - it('should encrypt and decrypt with binary data', function () { + it('should encrypt and decrypt with binary data and transferable objects', function () { + openpgp.config.zero_copy = true; // activate transferable objects const encOpt = { - message: openpgp.Message.fromBinary(new Uint8Array([0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01])), + message: openpgp.message.fromBinary(new Uint8Array([0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01])), passwords: password1, armor: false }; @@ -1871,10 +1948,17 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { passwords: password1, format: 'binary' }; - return openpgp.encrypt(encOpt).then(async function (encrypted) { - decOpt.message = await openpgp.readMessage(encrypted); + return openpgp.encrypt(encOpt).then(function (encrypted) { + decOpt.message = encrypted.message; return openpgp.decrypt(decOpt); }).then(function (decrypted) { + if (openpgp.getWorker()) { + if (navigator.userAgent.indexOf('Safari') !== -1 && (navigator.userAgent.indexOf('Version/11.1') !== -1 || (navigator.userAgent.match(/Chrome\/(\d+)/) || [])[1] < 56)) { + expect(encOpt.message.packets[0].data.byteLength).to.equal(8); // browser doesn't support transfering buffers + } else { + expect(encOpt.message.packets[0].data.byteLength).to.equal(0); // transferred buffer should be empty + } + } expect(decrypted.data).to.deep.equal(new Uint8Array([0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01])); expect(decrypted.signatures.length).to.equal(0); }); @@ -1885,14 +1969,14 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { withCompression(function (modifyCompressionEncryptOptions, verifyCompressionDecrypted) { it('should encrypt and decrypt with one password', function () { const encOpt = modifyCompressionEncryptOptions({ - message: openpgp.Message.fromText(plaintext), + message: openpgp.message.fromText(plaintext), passwords: password1 }); const decOpt = { passwords: password1 }; return openpgp.encrypt(encOpt).then(async function (encrypted) { - decOpt.message = await openpgp.readArmoredMessage(encrypted); + decOpt.message = await openpgp.message.readArmored(encrypted.data); return openpgp.decrypt(decOpt); }).then(function (decrypted) { expect(decrypted.data).to.equal(plaintext); @@ -1904,12 +1988,10 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { it('Streaming encrypt and decrypt small message roundtrip', async function() { let plaintext = []; let i = 0; - const useNativeStream = (() => { try { new global.ReadableStream(); return true; } catch (e) { return false; } })(); - const ReadableStream = useNativeStream ? global.ReadableStream : openpgp.stream.ReadableStream; const data = new ReadableStream({ async pull(controller) { if (i++ < 4) { - let randomBytes = await random.getRandomBytes(10); + let randomBytes = await openpgp.crypto.random.getRandomBytes(10); controller.enqueue(randomBytes); plaintext.push(randomBytes.slice()); } else { @@ -1918,19 +2000,19 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { } }); const encrypted = await openpgp.encrypt(modifyCompressionEncryptOptions({ - message: openpgp.Message.fromBinary(data), - passwords: ['test'] + message: openpgp.message.fromBinary(data), + passwords: ['test'], })); - expect(openpgp.stream.isStream(encrypted)).to.equal(useNativeStream ? 'web' : 'ponyfill'); - const message = await openpgp.readArmoredMessage(encrypted); + const msgAsciiArmored = encrypted.data; + const message = await openpgp.message.readArmored(msgAsciiArmored); const decrypted = await openpgp.decrypt({ passwords: ['test'], message, format: 'binary' }); - expect(openpgp.stream.isStream(decrypted.data)).to.equal(useNativeStream ? 'web' : 'ponyfill'); - expect(await openpgp.stream.readToEnd(decrypted.data)).to.deep.equal(util.concatUint8Array(plaintext)); + expect(openpgp.util.isStream(decrypted.data)).to.equal('web'); + expect(await openpgp.stream.readToEnd(decrypted.data)).to.deep.equal(openpgp.util.concatUint8Array(plaintext)); }); }); }); @@ -1953,55 +2035,55 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { let decryptedPrivateKey; beforeEach(async function() { if (!decryptedPrivateKey) { - await privateKey.decrypt(passphrase); + expect(await privateKey.keys[0].decrypt(passphrase)).to.be.true; decryptedPrivateKey = privateKey; } privateKey = decryptedPrivateKey; }); - it('should sign and verify cleartext message', function () { - const message = openpgp.CleartextMessage.fromText(plaintext); + it('should sign and verify cleartext data', function () { + const message = openpgp.cleartext.fromText(plaintext); const signOpt = { message, - privateKeys: privateKey + privateKeys: privateKey.keys }; const verifyOpt = { - publicKeys: publicKey + publicKeys: publicKey.keys }; return openpgp.sign(signOpt).then(async function (signed) { - expect(signed).to.match(/-----BEGIN PGP SIGNED MESSAGE-----/); - verifyOpt.message = await openpgp.readArmoredCleartextMessage(signed); + expect(signed.data).to.match(/-----BEGIN PGP SIGNED MESSAGE-----/); + verifyOpt.message = await openpgp.cleartext.readArmored(signed.data); return openpgp.verify(verifyOpt); }).then(async function (verified) { expect(verified.data).to.equal(plaintext.replace(/[ \t]+$/mg, '')); expect(verified.signatures[0].valid).to.be.true; - const signingKey = await privateKey.getSigningKey(); + const signingKey = await privateKey.keys[0].getSigningKey(); expect(verified.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); expect(verified.signatures[0].signature.packets.length).to.equal(1); }); }); - it('should sign and verify cleartext message with multiple private keys', async function () { - const privKeyDE = await openpgp.readArmoredKey(priv_key_de); + it('should sign and verify cleartext data with multiple private keys', async function () { + const privKeyDE = (await openpgp.key.readArmored(priv_key_de)).keys[0]; await privKeyDE.decrypt(passphrase); - const message = openpgp.CleartextMessage.fromText(plaintext); + const message = openpgp.cleartext.fromText(plaintext); const signOpt = { message, - privateKeys: [privateKey, privKeyDE] + privateKeys: [privateKey.keys[0], privKeyDE] }; const verifyOpt = { - publicKeys: [publicKey, privKeyDE.toPublic()] + publicKeys: [publicKey.keys[0], privKeyDE.toPublic()] }; return openpgp.sign(signOpt).then(async function (signed) { - expect(signed).to.match(/-----BEGIN PGP SIGNED MESSAGE-----/); - verifyOpt.message = await openpgp.readArmoredCleartextMessage(signed); + expect(signed.data).to.match(/-----BEGIN PGP SIGNED MESSAGE-----/); + verifyOpt.message = await openpgp.cleartext.readArmored(signed.data); return openpgp.verify(verifyOpt); }).then(async function (verified) { let signingKey; expect(verified.data).to.equal(plaintext.replace(/[ \t]+$/mg, '')); expect(verified.signatures[0].valid).to.be.true; - signingKey = await privateKey.getSigningKey(); + signingKey = await privateKey.keys[0].getSigningKey(); expect(verified.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); expect(verified.signatures[0].signature.packets.length).to.equal(1); expect(verified.signatures[1].valid).to.be.true; @@ -2011,144 +2093,144 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { }); }); - it('should sign and verify data with detached signatures', function () { - const message = openpgp.Message.fromText(plaintext); + it('should sign and verify cleartext data with detached signatures', function () { + const message = openpgp.cleartext.fromText(plaintext); const signOpt = { message, - privateKeys: privateKey, + privateKeys: privateKey.keys, detached: true }; const verifyOpt = { message, - publicKeys: publicKey + publicKeys: publicKey.keys }; return openpgp.sign(signOpt).then(async function (signed) { - verifyOpt.signature = await openpgp.readArmoredSignature(signed); + verifyOpt.signature = await openpgp.signature.readArmored(signed.signature); return openpgp.verify(verifyOpt); }).then(async function (verified) { - expect(verified.data).to.equal(plaintext); + expect(verified.data).to.equal(plaintext.replace(/[ \t]+$/mg, '')); expect(verified.signatures[0].valid).to.be.true; - const signingKey = await privateKey.getSigningKey(); + const signingKey = await privateKey.keys[0].getSigningKey(); expect(verified.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); expect(verified.signatures[0].signature.packets.length).to.equal(1); }); }); - it('should sign and fail to verify cleartext message with wrong public pgp key', async function () { - const message = openpgp.CleartextMessage.fromText(plaintext); + it('should sign and fail to verify cleartext data with wrong public pgp key', async function () { + const message = openpgp.cleartext.fromText(plaintext); const signOpt = { message, - privateKeys: privateKey + privateKeys: privateKey.keys }; const verifyOpt = { - publicKeys: await openpgp.readArmoredKey(wrong_pubkey) + publicKeys: (await openpgp.key.readArmored(wrong_pubkey)).keys }; return openpgp.sign(signOpt).then(async function (signed) { - verifyOpt.message = await openpgp.readArmoredCleartextMessage(signed); + verifyOpt.message = await openpgp.cleartext.readArmored(signed.data); return openpgp.verify(verifyOpt); }).then(async function (verified) { expect(verified.data).to.equal(plaintext.replace(/[ \t]+$/mg, '')); expect(verified.signatures[0].valid).to.be.null; - const signingKey = await privateKey.getSigningKey(); + const signingKey = await privateKey.keys[0].getSigningKey(); expect(verified.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); expect(verified.signatures[0].signature.packets.length).to.equal(1); }); }); - it('should sign and fail to verify data with wrong public pgp key with detached signature', async function () { - const message = openpgp.Message.fromText(plaintext); + it('should sign and fail to verify cleartext data with wrong public pgp key with detached signature', async function () { + const message = openpgp.cleartext.fromText(plaintext); const signOpt = { message, - privateKeys: privateKey, + privateKeys: privateKey.keys, detached: true }; const verifyOpt = { message, - publicKeys: await openpgp.readArmoredKey(wrong_pubkey) + publicKeys: (await openpgp.key.readArmored(wrong_pubkey)).keys }; return openpgp.sign(signOpt).then(async function (signed) { - verifyOpt.signature = await openpgp.readArmoredSignature(signed); + verifyOpt.signature = await openpgp.signature.readArmored(signed.signature); return openpgp.verify(verifyOpt); }).then(async function (verified) { - expect(verified.data).to.equal(plaintext); + expect(verified.data).to.equal(plaintext.replace(/[ \t]+$/mg, '')); expect(verified.signatures[0].valid).to.be.null; - const signingKey = await privateKey.getSigningKey(); + const signingKey = await privateKey.keys[0].getSigningKey(); expect(verified.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); expect(verified.signatures[0].signature.packets.length).to.equal(1); }); }); - it('should sign and verify data and not armor', function () { - const message = openpgp.Message.fromText(plaintext); + it('should sign and verify cleartext data and not armor', function () { + const message = openpgp.cleartext.fromText(plaintext); const signOpt = { message, - privateKeys: privateKey, + privateKeys: privateKey.keys, armor: false }; const verifyOpt = { - publicKeys: publicKey + publicKeys: publicKey.keys }; - return openpgp.sign(signOpt).then(async function (signed) { - verifyOpt.message = await openpgp.readMessage(signed); + return openpgp.sign(signOpt).then(function (signed) { + verifyOpt.message = signed.message; return openpgp.verify(verifyOpt); }).then(async function (verified) { - expect(verified.data).to.equal(plaintext); + expect(verified.data).to.equal(plaintext.replace(/[ \t]+$/mg, '')); expect(verified.signatures[0].valid).to.be.true; - const signingKey = await privateKey.getSigningKey(); + const signingKey = await privateKey.keys[0].getSigningKey(); expect(verified.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); expect(verified.signatures[0].signature.packets.length).to.equal(1); }); }); - it('should sign and verify data and not armor with detached signatures', function () { - const start = util.normalizeDate(); - const message = openpgp.Message.fromText(plaintext); + it('should sign and verify cleartext data and not armor with detached signatures', function () { + const start = openpgp.util.normalizeDate(); + const message = openpgp.cleartext.fromText(plaintext); const signOpt = { message, - privateKeys: privateKey, + privateKeys: privateKey.keys, detached: true, armor: false }; const verifyOpt = { message, - publicKeys: publicKey + publicKeys: publicKey.keys }; - return openpgp.sign(signOpt).then(async function (signed) { - verifyOpt.signature = await openpgp.readSignature(signed); + return openpgp.sign(signOpt).then(function (signed) { + verifyOpt.signature = signed.signature; return openpgp.verify(verifyOpt); }).then(async function (verified) { - expect(verified.data).to.equal(plaintext); - expect(+verified.signatures[0].signature.packets[0].created).to.be.lte(+util.normalizeDate()); + expect(verified.data).to.equal(plaintext.replace(/[ \t]+$/mg, '')); + expect(+verified.signatures[0].signature.packets[0].created).to.be.lte(+openpgp.util.normalizeDate()); expect(+verified.signatures[0].signature.packets[0].created).to.be.gte(+start); expect(verified.signatures[0].valid).to.be.true; - const signingKey = await privateKey.getSigningKey(); + const signingKey = await privateKey.keys[0].getSigningKey(); expect(verified.signatures[0].keyid.toHex()).to.equal(signingKey.getKeyId().toHex()); expect(verified.signatures[0].signature.packets.length).to.equal(1); }); }); - it('should sign and verify data with a date in the past', function () { - const message = openpgp.Message.fromText(plaintext); + it('should sign and verify cleartext data with a date in the past', function () { + const message = openpgp.cleartext.fromText(plaintext); const past = new Date(2000); const signOpt = { message, - privateKeys: privateKey_1337, + privateKeys: privateKey_1337.keys, detached: true, date: past, armor: false }; const verifyOpt = { message, - publicKeys: publicKey_1337, + publicKeys: publicKey_1337.keys, date: past }; - return openpgp.sign(signOpt).then(async function (signed) { - verifyOpt.signature = await openpgp.readSignature(signed); + return openpgp.sign(signOpt).then(function (signed) { + verifyOpt.signature = signed.signature; return openpgp.verify(verifyOpt).then(async function (verified) { expect(+verified.signatures[0].signature.packets[0].created).to.equal(+past); - expect(verified.data).to.equal(plaintext); + expect(verified.data).to.equal(plaintext.replace(/[ \t]+$/mg, '')); expect(verified.signatures[0].valid).to.be.true; - expect(await privateKey_1337.getSigningKey(verified.signatures[0].keyid, past)) + expect(await signOpt.privateKeys[0].getSigningKey(verified.signatures[0].keyid, past)) .to.be.not.null; expect(verified.signatures[0].signature.packets.length).to.equal(1); // now check with expiration checking disabled @@ -2156,9 +2238,9 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { return openpgp.verify(verifyOpt); }).then(async function (verified) { expect(+verified.signatures[0].signature.packets[0].created).to.equal(+past); - expect(verified.data).to.equal(plaintext); + expect(verified.data).to.equal(plaintext.replace(/[ \t]+$/mg, '')); expect(verified.signatures[0].valid).to.be.true; - expect(await privateKey_1337.getSigningKey(verified.signatures[0].keyid, null)) + expect(await signOpt.privateKeys[0].getSigningKey(verified.signatures[0].keyid, null)) .to.be.not.null; expect(verified.signatures[0].signature.packets.length).to.equal(1); }); @@ -2169,26 +2251,25 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { const future = new Date(2040, 5, 5, 5, 5, 5, 0); const data = new Uint8Array([3, 14, 15, 92, 65, 35, 59]); const signOpt = { - message: openpgp.Message.fromBinary(data), - privateKeys: privateKey_2038_2045, + message: openpgp.message.fromBinary(data), + privateKeys: privateKey_2038_2045.keys, detached: true, date: future, armor: false }; const verifyOpt = { - publicKeys: publicKey_2038_2045, - date: future, - format: 'binary' + publicKeys: publicKey_2038_2045.keys, + date: future }; - return openpgp.sign(signOpt).then(async function (signed) { - verifyOpt.message = openpgp.Message.fromBinary(data); - verifyOpt.signature = await openpgp.readSignature(signed); + return openpgp.sign(signOpt).then(function (signed) { + verifyOpt.message = openpgp.message.fromBinary(data); + verifyOpt.signature = signed.signature; return openpgp.verify(verifyOpt); }).then(async function (verified) { expect(+verified.signatures[0].signature.packets[0].created).to.equal(+future); expect([].slice.call(verified.data)).to.deep.equal([].slice.call(data)); expect(verified.signatures[0].valid).to.be.true; - expect(await privateKey_2038_2045.getSigningKey(verified.signatures[0].keyid, future)) + expect(await signOpt.privateKeys[0].getSigningKey(verified.signatures[0].keyid, future)) .to.be.not.null; expect(verified.signatures[0].signature.packets.length).to.equal(1); }); @@ -2197,26 +2278,23 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { it('should sign and verify binary data without one-pass signature', function () { const data = new Uint8Array([3, 14, 15, 92, 65, 35, 59]); const signOpt = { - message: openpgp.Message.fromBinary(data), - privateKeys: privateKey, + message: openpgp.message.fromBinary(data), + privateKeys: privateKey.keys, armor: false }; const verifyOpt = { - publicKeys: publicKey, - format: 'binary' + publicKeys: publicKey.keys }; - return openpgp.sign(signOpt).then(async function (signed) { - const message = await openpgp.readMessage(signed); - message.packets.concat(await openpgp.stream.readToEnd(message.packets.stream, _ => _)); - const packets = new openpgp.PacketList(); - packets.push(message.packets.findPacket(openpgp.enums.packet.signature)); - packets.push(message.packets.findPacket(openpgp.enums.packet.literalData)); - verifyOpt.message = new openpgp.Message(packets); + return openpgp.sign(signOpt).then(function (signed) { + const packets = new openpgp.packet.List(); + packets.push(signed.message.packets.findPacket(openpgp.enums.packet.signature)); + packets.push(signed.message.packets.findPacket(openpgp.enums.packet.literal)); + verifyOpt.message = new openpgp.message.Message(packets); return openpgp.verify(verifyOpt); }).then(async function (verified) { expect([].slice.call(verified.data)).to.deep.equal([].slice.call(data)); expect(verified.signatures[0].valid).to.be.true; - expect(await privateKey.getSigningKey(verified.signatures[0].keyid)) + expect(await signOpt.privateKeys[0].getSigningKey(verified.signatures[0].keyid)) .to.be.not.null; expect(verified.signatures[0].signature.packets.length).to.equal(1); }); @@ -2225,50 +2303,49 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { it('should streaming sign and verify binary data without one-pass signature', function () { const data = new Uint8Array([3, 14, 15, 92, 65, 35, 59]); const signOpt = { - message: openpgp.Message.fromBinary(data), - privateKeys: privateKey, + message: openpgp.message.fromBinary(data), + privateKeys: privateKey.keys, armor: false, streaming: 'web' }; const verifyOpt = { - publicKeys: publicKey, - streaming: 'web', - format: 'binary' + publicKeys: publicKey.keys, + streaming: 'web' }; - const useNativeStream = (() => { try { new global.ReadableStream(); return true; } catch (e) { return false; } })(); - return openpgp.sign(signOpt).then(async function (signed) { - expect(openpgp.stream.isStream(signed)).to.equal(useNativeStream ? 'web' : 'ponyfill'); - const message = await openpgp.readMessage(signed); - message.packets.concat(await openpgp.stream.readToEnd(message.packets.stream, _ => _)); - const packets = new openpgp.PacketList(); - packets.push(message.packets.findPacket(openpgp.enums.packet.signature)); - packets.push(message.packets.findPacket(openpgp.enums.packet.literalData)); - verifyOpt.message = new openpgp.Message(packets); + return openpgp.sign(signOpt).then(function (signed) { + const packets = new openpgp.packet.List(); + packets.push(signed.message.packets.findPacket(openpgp.enums.packet.signature)); + packets.push(signed.message.packets.findPacket(openpgp.enums.packet.literal)); + verifyOpt.message = new openpgp.message.Message(packets); return openpgp.verify(verifyOpt); }).then(async function (verified) { - expect(openpgp.stream.isStream(verified.data)).to.equal(useNativeStream ? 'web' : 'ponyfill'); + expect(openpgp.stream.isStream(verified.data)).to.equal('web'); expect([].slice.call(await openpgp.stream.readToEnd(verified.data))).to.deep.equal([].slice.call(data)); expect(await verified.signatures[0].verified).to.be.true; - expect(await privateKey.getSigningKey(verified.signatures[0].keyid)) + expect(await signOpt.privateKeys[0].getSigningKey(verified.signatures[0].keyid)) .to.be.not.null; expect((await verified.signatures[0].signature).packets.length).to.equal(1); }); }); - it('should encrypt and decrypt data with a date in the future', function () { + it('should encrypt and decrypt cleartext data with a date in the future', function () { const future = new Date(2040, 5, 5, 5, 5, 5, 0); const encryptOpt = { - message: openpgp.Message.fromText(plaintext, undefined, future), - publicKeys: publicKey_2038_2045, + message: openpgp.message.fromText(plaintext, undefined, future), + publicKeys: publicKey_2038_2045.keys, date: future, armor: false }; + const decryptOpt = { + privateKeys: privateKey_2038_2045.keys, + date: future + }; - return openpgp.encrypt(encryptOpt).then(async function (encrypted) { - const message = await openpgp.readMessage(encrypted); - return message.decrypt([privateKey_2038_2045]); + return openpgp.encrypt(encryptOpt).then(function (encrypted) { + decryptOpt.message = encrypted.message; + return encrypted.message.decrypt(decryptOpt.privateKeys); }).then(async function (packets) { - const literals = packets.packets.filterByTag(openpgp.enums.packet.literalData); + const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); expect(literals.length).to.equal(1); expect(+literals[0].date).to.equal(+future); expect(await openpgp.stream.readToEnd(packets.getText())).to.equal(plaintext); @@ -2279,45 +2356,48 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { const past = new Date(2005, 5, 5, 5, 5, 5, 0); const data = new Uint8Array([3, 14, 15, 92, 65, 35, 59]); const encryptOpt = { - message: openpgp.Message.fromBinary(data, undefined, past), - publicKeys: publicKey_2000_2008, + message: openpgp.message.fromBinary(data, undefined, past), + publicKeys: publicKey_2000_2008.keys, date: past, armor: false }; + const decryptOpt = { + privateKeys: privateKey_2000_2008.keys, + date: past + }; - return openpgp.encrypt(encryptOpt).then(async function (encrypted) { - const message = await openpgp.readMessage(encrypted); - return message.decrypt([privateKey_2000_2008]); + return openpgp.encrypt(encryptOpt).then(function (encrypted) { + decryptOpt.message = encrypted.message; + return encrypted.message.decrypt(decryptOpt.privateKeys); }).then(async function (packets) { - const literals = packets.packets.filterByTag(openpgp.enums.packet.literalData); + const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); expect(literals.length).to.equal(1); expect(+literals[0].date).to.equal(+past); expect(await openpgp.stream.readToEnd(packets.getLiteralData())).to.deep.equal(data); }); }); - it('should sign, encrypt and decrypt, verify data with a date in the past', function () { + it('should sign, encrypt and decrypt, verify cleartext data with a date in the past', function () { const past = new Date(2005, 5, 5, 5, 5, 5, 0); const encryptOpt = { - message: openpgp.Message.fromText(plaintext, undefined, past), - publicKeys: publicKey_2000_2008, - privateKeys: privateKey_2000_2008, + message: openpgp.message.fromText(plaintext, undefined, past), + publicKeys: publicKey_2000_2008.keys, + privateKeys: privateKey_2000_2008.keys, date: past, armor: false }; - return openpgp.encrypt(encryptOpt).then(async function (encrypted) { - const message = await openpgp.readMessage(encrypted); - return message.decrypt([privateKey_2000_2008]); - }).then(async function (message) { - const literals = message.packets.filterByTag(openpgp.enums.packet.literalData); + return openpgp.encrypt(encryptOpt).then(function (encrypted) { + return encrypted.message.decrypt(encryptOpt.privateKeys); + }).then(async function (packets) { + const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); expect(literals.length).to.equal(1); expect(+literals[0].date).to.equal(+past); - const signatures = await message.verify([publicKey_2000_2008], past); - expect(await openpgp.stream.readToEnd(message.getText())).to.equal(plaintext); + const signatures = await packets.verify(encryptOpt.publicKeys, past); + expect(await openpgp.stream.readToEnd(packets.getText())).to.equal(plaintext); expect(+(await signatures[0].signature).packets[0].created).to.equal(+past); expect(await signatures[0].verified).to.be.true; - expect(await privateKey_2000_2008.getSigningKey(signatures[0].keyid, past)) + expect(await encryptOpt.privateKeys[0].getSigningKey(signatures[0].keyid, past)) .to.be.not.null; expect((await signatures[0].signature).packets.length).to.equal(1); }); @@ -2327,26 +2407,25 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { const future = new Date(2040, 5, 5, 5, 5, 5, 0); const data = new Uint8Array([3, 14, 15, 92, 65, 35, 59]); const encryptOpt = { - message: openpgp.Message.fromBinary(data, undefined, future), - publicKeys: publicKey_2038_2045, - privateKeys: privateKey_2038_2045, + message: openpgp.message.fromBinary(data, undefined, future), + publicKeys: publicKey_2038_2045.keys, + privateKeys: privateKey_2038_2045.keys, date: future, armor: false }; - return openpgp.encrypt(encryptOpt).then(async function (encrypted) { - const message = await openpgp.readMessage(encrypted); - return message.decrypt([privateKey_2038_2045]); - }).then(async function (message) { - const literals = message.packets.filterByTag(openpgp.enums.packet.literalData); + return openpgp.encrypt(encryptOpt).then(function (encrypted) { + return encrypted.message.decrypt(encryptOpt.privateKeys); + }).then(async function (packets) { + const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); expect(literals.length).to.equal(1); expect(literals[0].format).to.equal('binary'); expect(+literals[0].date).to.equal(+future); - const signatures = await message.verify([publicKey_2038_2045], future); - expect(await openpgp.stream.readToEnd(message.getLiteralData())).to.deep.equal(data); + const signatures = await packets.verify(encryptOpt.publicKeys, future); + expect(await openpgp.stream.readToEnd(packets.getLiteralData())).to.deep.equal(data); expect(+(await signatures[0].signature).packets[0].created).to.equal(+future); expect(await signatures[0].verified).to.be.true; - expect(await privateKey_2038_2045.getSigningKey(signatures[0].keyid, future)) + expect(await encryptOpt.privateKeys[0].getSigningKey(signatures[0].keyid, future)) .to.be.not.null; expect((await signatures[0].signature).packets.length).to.equal(1); }); @@ -2356,26 +2435,25 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { const future = new Date(2040, 5, 5, 5, 5, 5, 0); const data = new Uint8Array([3, 14, 15, 92, 65, 35, 59]); const encryptOpt = { - message: openpgp.Message.fromBinary(data, undefined, future, 'mime'), - publicKeys: publicKey_2038_2045, - privateKeys: privateKey_2038_2045, + message: openpgp.message.fromBinary(data, undefined, future, 'mime'), + publicKeys: publicKey_2038_2045.keys, + privateKeys: privateKey_2038_2045.keys, date: future, armor: false }; - return openpgp.encrypt(encryptOpt).then(async function (encrypted) { - const message = await openpgp.readMessage(encrypted); - return message.decrypt([privateKey_2038_2045]); - }).then(async function (message) { - const literals = message.packets.filterByTag(openpgp.enums.packet.literalData); + return openpgp.encrypt(encryptOpt).then(function (encrypted) { + return encrypted.message.decrypt(encryptOpt.privateKeys); + }).then(async function (packets) { + const literals = packets.packets.filterByTag(openpgp.enums.packet.literal); expect(literals.length).to.equal(1); expect(literals[0].format).to.equal('mime'); expect(+literals[0].date).to.equal(+future); - const signatures = await message.verify([publicKey_2038_2045], future); - expect(await openpgp.stream.readToEnd(message.getLiteralData())).to.deep.equal(data); + const signatures = await packets.verify(encryptOpt.publicKeys, future); + expect(await openpgp.stream.readToEnd(packets.getLiteralData())).to.deep.equal(data); expect(+(await signatures[0].signature).packets[0].created).to.equal(+future); expect(await signatures[0].verified).to.be.true; - expect(await privateKey_2038_2045.getSigningKey(signatures[0].keyid, future)) + expect(await encryptOpt.privateKeys[0].getSigningKey(signatures[0].keyid, future)) .to.be.not.null; expect((await signatures[0].signature).packets.length).to.equal(1); }); @@ -2383,10 +2461,10 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { it('should fail to encrypt with revoked key', function() { return openpgp.revokeKey({ - key: privateKey + key: privateKey.keys[0] }).then(function(revKey) { return openpgp.encrypt({ - message: openpgp.Message.fromText(plaintext), + message: openpgp.message.fromText(plaintext), publicKeys: revKey.publicKey }).then(function() { throw new Error('Should not encrypt with revoked key'); @@ -2397,13 +2475,13 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { }); it('should fail to encrypt with revoked subkey', async function() { - const pubKeyDE = await openpgp.readArmoredKey(pub_key_de); - const privKeyDE = await openpgp.readArmoredKey(priv_key_de); + const pubKeyDE = (await openpgp.key.readArmored(pub_key_de)).keys[0]; + const privKeyDE = (await openpgp.key.readArmored(priv_key_de)).keys[0]; await privKeyDE.decrypt(passphrase); return privKeyDE.subKeys[0].revoke(privKeyDE.primaryKey).then(function(revSubKey) { pubKeyDE.subKeys[0] = revSubKey; return openpgp.encrypt({ - message: openpgp.Message.fromText(plaintext), + message: openpgp.message.fromText(plaintext), publicKeys: pubKeyDE }).then(function() { throw new Error('Should not encrypt with revoked subkey'); @@ -2414,16 +2492,16 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { }); it('should decrypt with revoked subkey', async function() { - const pubKeyDE = await openpgp.readArmoredKey(pub_key_de); - const privKeyDE = await openpgp.readArmoredKey(priv_key_de); + const pubKeyDE = (await openpgp.key.readArmored(pub_key_de)).keys[0]; + const privKeyDE = (await openpgp.key.readArmored(priv_key_de)).keys[0]; await privKeyDE.decrypt(passphrase); const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromText(plaintext), + message: openpgp.message.fromText(plaintext), publicKeys: pubKeyDE }); privKeyDE.subKeys[0] = await privKeyDE.subKeys[0].revoke(privKeyDE.primaryKey); const decOpt = { - message: await openpgp.readArmoredMessage(encrypted), + message: await openpgp.message.readArmored(encrypted.data), privateKeys: privKeyDE }; const decrypted = await openpgp.decrypt(decOpt); @@ -2431,18 +2509,18 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { }); it('should not decrypt with corrupted subkey', async function() { - const pubKeyDE = await openpgp.readArmoredKey(pub_key_de); - const privKeyDE = await openpgp.readArmoredKey(priv_key_de); + const pubKeyDE = (await openpgp.key.readArmored(pub_key_de)).keys[0]; + const privKeyDE = (await openpgp.key.readArmored(priv_key_de)).keys[0]; // corrupt the public key params - privKeyDE.subKeys[0].keyPacket.publicParams.p[0]++; + privKeyDE.subKeys[0].keyPacket.params[0].data[0]++; // validation will not check the decryption subkey and will succeed await privKeyDE.decrypt(passphrase); const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromText(plaintext), + message: openpgp.message.fromText(plaintext), publicKeys: pubKeyDE }); const decOpt = { - message: await openpgp.readArmoredMessage(encrypted), + message: await openpgp.message.readArmored(encrypted.data), privateKeys: privKeyDE }; // binding signature is invalid @@ -2450,7 +2528,7 @@ module.exports = () => describe('OpenPGP.js public api tests', function() { }); it('RSA decryption with PKCS1 padding of wrong length should fail', async function() { - const key = await openpgp.readArmoredKey(rsaPrivateKeyPKCS1); + const key = (await openpgp.key.readArmored(rsaPrivateKeyPKCS1)).keys[0]; // the paddings of these messages are prefixed by 0x02 and 0x000002 instead of 0x0002 // the code should discriminate between these cases by checking the length of the padded plaintext const padding02 = `-----BEGIN PGP MESSAGE----- @@ -2483,13 +2561,13 @@ J9I8AcH94nE77JUtCm7s1kOlo0EIshZsAqJwGveDGdAuabfViVwVxG4I24M6 -----END PGP MESSAGE-----`; const decOpt02 = { - message: await openpgp.readArmoredMessage(padding02), + message: await openpgp.message.readArmored(padding02), privateKeys: key }; await expect(openpgp.decrypt(decOpt02)).to.be.rejectedWith(/Decryption error/); const decOpt000002 = { - message: await openpgp.readArmoredMessage(padding000002), + message: await openpgp.message.readArmored(padding000002), privateKeys: key }; await expect(openpgp.decrypt(decOpt000002)).to.be.rejectedWith(/Decryption error/); @@ -2497,7 +2575,7 @@ J9I8AcH94nE77JUtCm7s1kOlo0EIshZsAqJwGveDGdAuabfViVwVxG4I24M6 it('should decrypt with two passwords message which GPG fails on', async function() { const decOpt = { - message: await openpgp.readArmoredMessage(twoPasswordGPGFail), + message: await openpgp.message.readArmored(twoPasswordGPGFail), passwords: password2 }; return openpgp.decrypt(decOpt).then(function(decrypted) { @@ -2507,33 +2585,59 @@ J9I8AcH94nE77JUtCm7s1kOlo0EIshZsAqJwGveDGdAuabfViVwVxG4I24M6 }); it('should decrypt with three passwords', async function() { - const messageBinary = util.hexToUint8Array('c32e04090308125231fe38b0255f60a7f319fc4959c147c7af33817ceb4cf159a00f2efa17b7921961f6ead025c77588d2430166fe9395cd58e9b69a67a30470e2d31bf0bbbb31c7eca31fb9015dddf70c6957036b093d104cbf0b26e218113e69c4fa89dda97a61d0cba364efa77d5144c5b9b701'); - const message = await openpgp.readMessage(messageBinary); + const messageBinary = openpgp.util.b64_to_Uint8Array('wy4ECQMIElIx/jiwJV9gp/MZ/ElZwUfHrzOBfOtM8VmgDy76F7eSGWH26tAlx3WI0kMBZv6Tlc1Y6baaZ6MEcOLTG/C7uzHH7KMfuQFd3fcMaVcDawk9EEy/CybiGBE+acT6id2pemHQy6Nk76d9UUTFubcB'); + const message = await openpgp.message.read(messageBinary); const passwords = ['Test', 'Pinata', 'a']; const decrypted = await openpgp.decrypt({ message, passwords }); expect(decrypted.data).to.equal('Hello world'); }); it('should decrypt broken ECC message from old OpenPGP.js', async function() { - const key = await openpgp.readArmoredKey(ecdh_dec_key); - const message = await openpgp.readArmoredMessage(ecdh_msg_bad); + const { keys: [key] } = await openpgp.key.readArmored(ecdh_dec_key); + const message = await openpgp.message.readArmored(ecdh_msg_bad); await key.decrypt('12345'); const decrypted = await openpgp.decrypt({ message, privateKeys: [key] }); expect(decrypted.data).to.equal('\n'); }); it('should decrypt broken ECC message from old go crypto', async function() { - const key = await openpgp.readArmoredKey(ecdh_dec_key_2); - const message = await openpgp.readArmoredMessage(ecdh_msg_bad_2); + const { keys: [key] } = await openpgp.key.readArmored(ecdh_dec_key_2); + const message = await openpgp.message.readArmored(ecdh_msg_bad_2); await key.decrypt('12345'); const decrypted = await openpgp.decrypt({ message, privateKeys: [key] }); expect(decrypted.data).to.equal('Tesssst


Sent from ProtonMail mobile


'); }); - it('should decrypt Blowfish message', async function() { + it('should decrypt broken Blowfish message from old OpenPGP.js', async function() { + openpgp.crypto.cipher.blowfish.blockSize = 16; + openpgp.crypto.cipher.blowfish.prototype.blockSize = 16; + const use_nativeVal = openpgp.config.use_native; + openpgp.config.use_native = false; + try { + const { data } = await openpgp.decrypt({ + passwords: 'test', + message: await openpgp.message.readArmored(`-----BEGIN PGP MESSAGE----- +Version: OpenPGP.js v4.8.1 +Comment: https://openpgpjs.org + +wx4EBAMI0eHVbTnl2iLg6pIJ4sWw2K7OwfxFP8bmaUvSRAGiSDGJSFNUuB4v +SU69Z1XyXiuTpD3780FnLnR4dF41nhbrTXaDG+X1b3JsZCHTFMGF7Eb+YVhh +YCXOZwd3z5lxcj/M +=oXcN +-----END PGP MESSAGE-----`) + }); + expect(data).to.equal('Hello World!'); + } finally { + openpgp.crypto.cipher.blowfish.blockSize = 8; + openpgp.crypto.cipher.blowfish.prototype.blockSize = 8; + openpgp.config.use_native = use_nativeVal; + } + }); + + it('should decrypt correct Blowfish message from new OpenPGP.js', async function() { const { data } = await openpgp.decrypt({ passwords: 'test', - message: await openpgp.readArmoredMessage(`-----BEGIN PGP MESSAGE----- + message: await openpgp.message.readArmored(`-----BEGIN PGP MESSAGE----- Version: OpenPGP.js v4.9.0 Comment: https://openpgpjs.org @@ -2547,38 +2651,26 @@ amnR6g== }); it('should normalize newlines in encrypted text message', async function() { - const message = openpgp.Message.fromText('"BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\r\nUID:123\r\nDTSTART:20191211T121212Z\r\nDTEND:20191212T121212Z\r\nEND:VEVENT\nEND:VCALENDAR"'); + const message = openpgp.message.fromText('"BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VEVENT\r\nUID:123\r\nDTSTART:20191211T121212Z\r\nDTEND:20191212T121212Z\r\nEND:VEVENT\nEND:VCALENDAR"'); const encrypted = await openpgp.encrypt({ passwords: 'test', message }); const decrypted = await openpgp.decrypt({ passwords: 'test', - message: await openpgp.readArmoredMessage(encrypted), + message: await openpgp.message.readArmored(encrypted.data), format: 'binary' }); - expect(util.decodeUtf8(decrypted.data)).to.equal('"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nBEGIN:VEVENT\r\nUID:123\r\nDTSTART:20191211T121212Z\r\nDTEND:20191212T121212Z\r\nEND:VEVENT\r\nEND:VCALENDAR"'); + expect(openpgp.util.decode_utf8(decrypted.data)).to.equal('"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nBEGIN:VEVENT\r\nUID:123\r\nDTSTART:20191211T121212Z\r\nDTEND:20191212T121212Z\r\nEND:VEVENT\r\nEND:VCALENDAR"'); }); - }); - describe('Sign and verify with each curve', function() { - const curves = ['secp256k1' , 'p256', 'p384', 'p521', 'curve25519', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1']; - curves.forEach(curve => { - it(`sign/verify with ${curve}`, async function() { - const plaintext = 'short message'; - const key = (await openpgp.generateKey({ curve, userIds: { name: 'Alice', email: 'info@alice.com' } })).key; - const signed = await openpgp.sign({ privateKeys:[key], message: openpgp.CleartextMessage.fromText(plaintext) }); - const verified = await openpgp.verify({ publicKeys:[key], message: await openpgp.readArmoredCleartextMessage(signed) }); - expect(verified.signatures[0].valid).to.be.true; - }); - }); }); describe('Errors', function() { it('Error message should contain the original error message', function() { return openpgp.encrypt({ - message: openpgp.Message.fromBinary(new Uint8Array([0x01, 0x01, 0x01])), + message: openpgp.message.fromBinary(new Uint8Array([0x01, 0x01, 0x01])), passwords: null }).then(function() { throw new Error('Error expected.'); @@ -2588,6 +2680,7 @@ amnR6g== }); }); + }); }); diff --git a/test/general/packet.js b/test/general/packet.js index 016e7c83..aacf9048 100644 --- a/test/general/packet.js +++ b/test/general/packet.js @@ -1,6 +1,4 @@ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); -const crypto = require('../../src/crypto'); -const util = require('../../src/util'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); const stub = require('sinon/lib/sinon/stub'); const chai = require('chai'); @@ -8,14 +6,13 @@ chai.use(require('chai-as-promised')); const { expect } = chai; const input = require('./testInputs.js'); -const { PacketList } = require('../../dist/node/openpgp.min'); function stringify(array) { - if (openpgp.stream.isStream(array)) { + if (openpgp.util.isStream(array)) { return openpgp.stream.readToEnd(array).then(stringify); } - if (!util.isUint8Array(array)) { + if (!openpgp.util.isUint8Array(array)) { throw new Error('Data must be in the form of a Uint8Array'); } @@ -26,7 +23,7 @@ function stringify(array) { return result.join(''); } -module.exports = () => describe("Packet", function() { +describe("Packet", function() { const armored_key = '-----BEGIN PGP PRIVATE KEY BLOCK-----\n' + 'Version: GnuPG v2.0.19 (GNU/Linux)\n' + @@ -65,13 +62,13 @@ module.exports = () => describe("Packet", function() { '-----END PGP PRIVATE KEY BLOCK-----'; it('Symmetrically encrypted packet', async function() { - const message = new openpgp.PacketList(); + const message = new openpgp.packet.List(); const testText = input.createSomeMessage(); - const literal = new openpgp.LiteralDataPacket(); + const literal = new openpgp.packet.Literal(); literal.setText(testText); - const enc = new openpgp.SymmetricallyEncryptedDataPacket(); + const enc = new openpgp.packet.SymmetricallyEncrypted(); message.push(enc); enc.packets.push(literal); @@ -80,22 +77,22 @@ module.exports = () => describe("Packet", function() { await enc.encrypt(algo, key); - const msg2 = new openpgp.Message(); - await msg2.packets.read(message.write(), { SymmetricallyEncryptedDataPacket: openpgp.SymmetricallyEncryptedDataPacket }); - msg2.packets[0].ignoreMdcError = true; + const msg2 = new openpgp.message.Message(); + await msg2.packets.read(message.write()); + msg2.packets[0].ignore_mdc_error = true; const dec = await msg2.decrypt(null, null, [{ algorithm: algo, data: key }]); expect(await stringify(dec.packets[0].data)).to.equal(stringify(literal.data)); }); it('Symmetrically encrypted packet - MDC error for modern cipher', async function() { - const message = new openpgp.PacketList(); + const message = new openpgp.packet.List(); const testText = input.createSomeMessage(); - const literal = new openpgp.LiteralDataPacket(); + const literal = new openpgp.packet.Literal(); literal.setText(testText); - const enc = new openpgp.SymmetricallyEncryptedDataPacket(); + const enc = new openpgp.packet.SymmetricallyEncrypted(); message.push(enc); await enc.packets.push(literal); @@ -104,8 +101,8 @@ module.exports = () => describe("Packet", function() { await enc.encrypt(algo, key); - const msg2 = new openpgp.PacketList(); - await msg2.read(message.write(), { SymmetricallyEncryptedDataPacket: openpgp.SymmetricallyEncryptedDataPacket }); + const msg2 = new openpgp.packet.List(); + await msg2.read(message.write()); await expect(msg2[0].decrypt(algo, key)).to.eventually.be.rejectedWith('Decryption failed due to missing MDC.'); }); @@ -114,17 +111,17 @@ module.exports = () => describe("Packet", function() { const algo = 'aes256'; const testText = input.createSomeMessage(); - const literal = new openpgp.LiteralDataPacket(); - const enc = new openpgp.SymEncryptedIntegrityProtectedDataPacket(); - const msg = new openpgp.PacketList(); + const literal = new openpgp.packet.Literal(); + const enc = new openpgp.packet.SymEncryptedIntegrityProtected(); + const msg = new openpgp.packet.List(); msg.push(enc); literal.setText(testText); enc.packets.push(literal); await enc.encrypt(algo, key); - const msg2 = new openpgp.PacketList(); - await msg2.read(msg.write(), openpgp); + const msg2 = new openpgp.packet.List(); + await msg2.read(msg.write()); await msg2[0].decrypt(algo, key); @@ -135,18 +132,18 @@ module.exports = () => describe("Packet", function() { const 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]); const algo = 'aes256'; const testText = input.createSomeMessage(); - const literal = new openpgp.LiteralDataPacket(); - const enc = new openpgp.AEADEncryptedDataPacket(); - const msg = new openpgp.PacketList(); + const literal = new openpgp.packet.Literal(); + const enc = new openpgp.packet.SymEncryptedAEADProtected(); + const msg = new openpgp.packet.List(); msg.push(enc); literal.setText(testText); enc.packets.push(literal); - const msg2 = new openpgp.PacketList(); + const msg2 = new openpgp.packet.List(); return enc.encrypt(algo, key).then(async function() { - await msg2.read(msg.write(), openpgp); + await msg2.read(msg.write()); return msg2[0].decrypt(algo, key); }).then(async function() { expect(await openpgp.stream.readToEnd(msg2[0].packets[0].data)).to.deep.equal(literal.data); @@ -154,30 +151,30 @@ module.exports = () => describe("Packet", function() { }); it('Sym. encrypted AEAD protected packet (AEAD)', async function() { - let aeadProtectVal = openpgp.config.aeadProtect; - openpgp.config.aeadProtect = true; + let aead_protectVal = openpgp.config.aead_protect; + openpgp.config.aead_protect = true; const testText = input.createSomeMessage(); const 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]); const algo = 'aes256'; - const literal = new openpgp.LiteralDataPacket(); - const enc = new openpgp.AEADEncryptedDataPacket(); - const msg = new openpgp.PacketList(); + const literal = new openpgp.packet.Literal(); + const enc = new openpgp.packet.SymEncryptedAEADProtected(); + const msg = new openpgp.packet.List(); msg.push(enc); literal.setText(testText); enc.packets.push(literal); - const msg2 = new openpgp.PacketList(); + const msg2 = new openpgp.packet.List(); try { await enc.encrypt(algo, key); - await msg2.read(msg.write(), openpgp); + await msg2.read(msg.write()); await msg2[0].decrypt(algo, key); expect(await openpgp.stream.readToEnd(msg2[0].packets[0].data)).to.deep.equal(literal.data); } finally { - openpgp.config.aeadProtect = aeadProtectVal; + openpgp.config.aead_protect = aead_protectVal; } }); @@ -202,41 +199,41 @@ module.exports = () => describe("Packet", function() { } it('Sym. encrypted AEAD protected packet is encrypted in parallel (AEAD, GCM)', async function() { - const webCrypto = util.getWebCrypto(); + const webCrypto = openpgp.util.getWebCrypto(); if (!webCrypto) return; const encryptStub = cryptStub(webCrypto, 'encrypt'); const decryptStub = cryptStub(webCrypto, 'decrypt'); - let aeadProtectVal = openpgp.config.aeadProtect; - let aeadChunkSizeByteVal = openpgp.config.aeadChunkSizeByte; - openpgp.config.aeadProtect = true; - openpgp.config.aeadChunkSizeByte = 0; + let aead_protectVal = openpgp.config.aead_protect; + let aead_chunk_size_byteVal = openpgp.config.aead_chunk_size_byte; + openpgp.config.aead_protect = true; + openpgp.config.aead_chunk_size_byte = 0; const testText = input.createSomeMessage(); const 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]); const algo = 'aes256'; - const literal = new openpgp.LiteralDataPacket(); - const enc = new openpgp.AEADEncryptedDataPacket(); - const msg = new openpgp.PacketList(); - enc.aeadAlgorithm = 'experimentalGcm'; + const literal = new openpgp.packet.Literal(); + const enc = new openpgp.packet.SymEncryptedAEADProtected(); + const msg = new openpgp.packet.List(); + enc.aeadAlgorithm = 'experimental_gcm'; msg.push(enc); literal.setText(testText); enc.packets.push(literal); - const msg2 = new openpgp.PacketList(); + const msg2 = new openpgp.packet.List(); try { await enc.encrypt(algo, key); - await msg2.read(msg.write(), openpgp); + await msg2.read(msg.write()); await msg2[0].decrypt(algo, key); expect(await openpgp.stream.readToEnd(msg2[0].packets[0].data)).to.deep.equal(literal.data); expect(encryptStub.callCount > 1).to.be.true; expect(decryptStub.callCount > 1).to.be.true; } finally { - openpgp.config.aeadProtect = aeadProtectVal; - openpgp.config.aeadChunkSizeByte = aeadChunkSizeByteVal; + openpgp.config.aead_protect = aead_protectVal; + openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteVal; encryptStub.restore(); decryptStub.restore(); } @@ -245,10 +242,7 @@ module.exports = () => describe("Packet", function() { it('Sym. encrypted AEAD protected packet test vector (AEAD)', async function() { // From https://gitlab.com/openpgp-wg/rfc4880bis/commit/00b20923e6233fb6ff1666ecd5acfefceb32907d - const nodeCrypto = util.getNodeCrypto(); - if (!nodeCrypto) return; - - let packetBytes = util.hexToUint8Array(` + let packetBytes = openpgp.util.hex_to_Uint8Array(` d4 4a 01 07 01 0e b7 32 37 9f 73 c4 92 8d e2 5f ac fe 65 17 ec 10 5d c1 1a 81 dc 0c b8 a2 f6 f3 d9 00 16 38 4a 56 fc 82 1a e1 1a e8 db cb 49 86 @@ -256,39 +250,39 @@ module.exports = () => describe("Packet", function() { ab 01 3d e1 25 95 86 90 6e ab 24 76 `.replace(/\s+/g, '')); - let aeadProtectVal = openpgp.config.aeadProtect; - let aeadChunkSizeByteVal = openpgp.config.aeadChunkSizeByte; - openpgp.config.aeadProtect = true; - openpgp.config.aeadChunkSizeByte = 14; + let aead_protectVal = openpgp.config.aead_protect; + let aead_chunk_size_byteVal = openpgp.config.aead_chunk_size_byte; + openpgp.config.aead_protect = true; + openpgp.config.aead_chunk_size_byte = 14; - const iv = util.hexToUint8Array('b7 32 37 9f 73 c4 92 8d e2 5f ac fe 65 17 ec 10'.replace(/\s+/g, '')); - const key = util.hexToUint8Array('86 f1 ef b8 69 52 32 9f 24 ac d3 bf d0 e5 34 6d'.replace(/\s+/g, '')); + const iv = openpgp.util.hex_to_Uint8Array('b7 32 37 9f 73 c4 92 8d e2 5f ac fe 65 17 ec 10'.replace(/\s+/g, '')); + const key = openpgp.util.hex_to_Uint8Array('86 f1 ef b8 69 52 32 9f 24 ac d3 bf d0 e5 34 6d'.replace(/\s+/g, '')); const algo = 'aes128'; - const literal = new openpgp.LiteralDataPacket(0); - const enc = new openpgp.AEADEncryptedDataPacket(); - const msg = new openpgp.PacketList(); + const literal = new openpgp.packet.Literal(0); + const enc = new openpgp.packet.SymEncryptedAEADProtected(); + const msg = new openpgp.packet.List(); msg.push(enc); - literal.setBytes(util.strToUint8Array('Hello, world!\n'), openpgp.enums.literal.binary); + literal.setBytes(openpgp.util.str_to_Uint8Array('Hello, world!\n'), openpgp.enums.literal.binary); literal.filename = ''; enc.packets.push(literal); - const msg2 = new openpgp.PacketList(); + const msg2 = new openpgp.packet.List(); - let randomBytesStub = stub(nodeCrypto, 'randomBytes'); - randomBytesStub.returns(iv); + let randomBytesStub = stub(openpgp.crypto.random, 'getRandomBytes'); + randomBytesStub.returns(resolves(iv)); try { await enc.encrypt(algo, key); const data = msg.write(); expect(await openpgp.stream.readToEnd(openpgp.stream.clone(data))).to.deep.equal(packetBytes); - await msg2.read(data, openpgp); + await msg2.read(data); await msg2[0].decrypt(algo, key); expect(await openpgp.stream.readToEnd(msg2[0].packets[0].data)).to.deep.equal(literal.data); } finally { - openpgp.config.aeadProtect = aeadProtectVal; - openpgp.config.aeadChunkSizeByte = aeadChunkSizeByteVal; + openpgp.config.aead_protect = aead_protectVal; + openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteVal; randomBytesStub.restore(); } }); @@ -303,10 +297,10 @@ module.exports = () => describe("Packet", function() { '=VZ0/\n' + '-----END PGP MESSAGE-----'; - const msgbytes = (await openpgp.unarmor(msg)).data; + const msgbytes = (await openpgp.armor.decode(msg)).data; - const parsed = new openpgp.PacketList(); - await parsed.read(msgbytes, openpgp); + const parsed = new openpgp.packet.List(); + await parsed.read(msgbytes); return parsed[0].decrypt('test').then(() => { const key = parsed[0].sessionKey; @@ -321,24 +315,32 @@ module.exports = () => describe("Packet", function() { }); it('Public key encrypted symmetric key packet', function() { - const rsa = openpgp.enums.publicKey.rsaEncryptSign; - const keySize = util.getWebCryptoAll() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys + const rsa = openpgp.crypto.publicKey.rsa; + const keySize = openpgp.util.getWebCryptoAll() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys + + return rsa.generate(keySize, "10001").then(function(mpiGen) { - return crypto.generateParams(rsa, keySize, 65537).then(function({ publicParams, privateParams }) { - const enc = new openpgp.PublicKeyEncryptedSessionKeyPacket(); - const msg = new openpgp.PacketList(); - const msg2 = new openpgp.PacketList(); + let mpi = [mpiGen.n, mpiGen.e, mpiGen.d, mpiGen.p, mpiGen.q, mpiGen.u]; + mpi = mpi.map(function(k) { + return new openpgp.MPI(k); + }); + + const enc = new openpgp.packet.PublicKeyEncryptedSessionKey(); + const msg = new openpgp.packet.List(); + const msg2 = new openpgp.packet.List(); enc.sessionKey = 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]); - enc.publicKeyAlgorithm = 'rsaEncryptSign'; + enc.publicKeyAlgorithm = 'rsa_encrypt_sign'; enc.sessionKeyAlgorithm = 'aes256'; enc.publicKeyId.bytes = '12345678'; - return enc.encrypt({ publicParams, getFingerprintBytes() {} }).then(async () => { + return enc.encrypt({ params: mpi, getFingerprintBytes() {} }).then(async () => { msg.push(enc); - await msg2.read(msg.write(), openpgp); - return msg2[0].decrypt({ algorithm: 'rsaEncryptSign', publicParams, privateParams, getFingerprintBytes() {} }).then(() => { + await msg2.read(msg.write()); + + return msg2[0].decrypt({ algorithm: 'rsa_encrypt_sign', params: mpi, getFingerprintBytes() {} }).then(() => { + expect(stringify(msg2[0].sessionKey)).to.equal(stringify(enc.sessionKey)); expect(msg2[0].sessionKeyAlgorithm).to.equal(enc.sessionKeyAlgorithm); }); @@ -369,15 +371,15 @@ module.exports = () => describe("Packet", function() { '=lKiS\n' + '-----END PGP PRIVATE KEY BLOCK-----'; - let key = new openpgp.PacketList(); - await key.read((await openpgp.unarmor(armored_key)).data, openpgp); + let key = new openpgp.packet.List(); + await key.read((await openpgp.armor.decode(armored_key)).data); key = key[0]; - const enc = new openpgp.PublicKeyEncryptedSessionKeyPacket(); + const enc = new openpgp.packet.PublicKeyEncryptedSessionKey(); const secret = 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]); enc.sessionKey = secret; - enc.publicKeyAlgorithm = 'rsaEncryptSign'; + enc.publicKeyAlgorithm = 'rsa_encrypt_sign'; enc.sessionKeyAlgorithm = 'aes256'; enc.publicKeyId.bytes = '12345678'; @@ -436,12 +438,12 @@ module.exports = () => describe("Packet", function() { '=iSaK\n' + '-----END PGP MESSAGE-----'; - let key = new openpgp.PacketList(); - await key.read((await openpgp.unarmor(armored_key)).data, openpgp); + let key = new openpgp.packet.List(); + await key.read((await openpgp.armor.decode(armored_key)).data); key = key[3]; - const msg = new openpgp.PacketList(); - await msg.read((await openpgp.unarmor(armored_msg)).data, openpgp); + const msg = new openpgp.packet.List(); + await msg.read((await openpgp.armor.decode(armored_msg)).data); return msg[0].decrypt(key).then(async () => { await msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey); @@ -457,10 +459,10 @@ module.exports = () => describe("Packet", function() { const algo = 'aes256'; const testText = input.createSomeMessage(); - const literal = new openpgp.LiteralDataPacket(); - const key_enc = new openpgp.SymEncryptedSessionKeyPacket(); - const enc = new openpgp.SymEncryptedIntegrityProtectedDataPacket(); - const msg = new openpgp.PacketList(); + const literal = new openpgp.packet.Literal(); + const key_enc = new openpgp.packet.SymEncryptedSessionKey(); + const enc = new openpgp.packet.SymEncryptedIntegrityProtected(); + const msg = new openpgp.packet.List(); msg.push(key_enc); msg.push(enc); @@ -474,8 +476,8 @@ module.exports = () => describe("Packet", function() { enc.packets.push(literal); await enc.encrypt(algo, key); - const msg2 = new openpgp.PacketList(); - await msg2.read(msg.write(), openpgp); + const msg2 = new openpgp.packet.List(); + await msg2.read(msg.write()); await msg2[0].decrypt(passphrase); const key2 = msg2[0].sessionKey; @@ -485,18 +487,18 @@ module.exports = () => describe("Packet", function() { }); it('Sym. encrypted session key reading/writing (AEAD)', async function() { - let aeadProtectVal = openpgp.config.aeadProtect; - openpgp.config.aeadProtect = true; + let aead_protectVal = openpgp.config.aead_protect; + openpgp.config.aead_protect = true; try { const passphrase = 'hello'; const algo = 'aes256'; const testText = input.createSomeMessage(); - const literal = new openpgp.LiteralDataPacket(); - const key_enc = new openpgp.SymEncryptedSessionKeyPacket(); - const enc = new openpgp.AEADEncryptedDataPacket(); - const msg = new openpgp.PacketList(); + const literal = new openpgp.packet.Literal(); + const key_enc = new openpgp.packet.SymEncryptedSessionKey(); + const enc = new openpgp.packet.SymEncryptedAEADProtected(); + const msg = new openpgp.packet.List(); msg.push(key_enc); msg.push(enc); @@ -510,8 +512,8 @@ module.exports = () => describe("Packet", function() { enc.packets.push(literal); await enc.encrypt(algo, key); - const msg2 = new openpgp.PacketList(); - await msg2.read(msg.write(), openpgp); + const msg2 = new openpgp.packet.List(); + await msg2.read(msg.write()); await msg2[0].decrypt(passphrase); const key2 = msg2[0].sessionKey; @@ -519,35 +521,32 @@ module.exports = () => describe("Packet", function() { expect(await stringify(msg2[1].packets[0].data)).to.equal(stringify(literal.data)); } finally { - openpgp.config.aeadProtect = aeadProtectVal; + openpgp.config.aead_protect = aead_protectVal; } }); it('Sym. encrypted session key reading/writing test vector (EAX, AEAD)', async function() { // From https://gitlab.com/openpgp-wg/rfc4880bis/blob/00b20923/back.mkd#sample-aead-eax-encryption-and-decryption - const nodeCrypto = util.getNodeCrypto(); - if (!nodeCrypto) return; - - let aeadProtectVal = openpgp.config.aeadProtect; - let aeadChunkSizeByteVal = openpgp.config.aeadChunkSizeByte; - let s2kIterationCountByteVal = openpgp.config.s2kIterationCountByte; - openpgp.config.aeadProtect = true; - openpgp.config.aeadChunkSizeByte = 14; - openpgp.config.s2kIterationCountByte = 0x90; - - let salt = util.hexToUint8Array(`cd5a9f70fbe0bc65`); - let sessionKey = util.hexToUint8Array(`86 f1 ef b8 69 52 32 9f 24 ac d3 bf d0 e5 34 6d`.replace(/\s+/g, '')); - let sessionIV = util.hexToUint8Array(`bc 66 9e 34 e5 00 dc ae dc 5b 32 aa 2d ab 02 35`.replace(/\s+/g, '')); - let dataIV = util.hexToUint8Array(`b7 32 37 9f 73 c4 92 8d e2 5f ac fe 65 17 ec 10`.replace(/\s+/g, '')); - - let randomBytesStub = stub(nodeCrypto, 'randomBytes'); - randomBytesStub.onCall(0).returns(salt); - randomBytesStub.onCall(1).returns(sessionKey); - randomBytesStub.onCall(2).returns(sessionIV); - randomBytesStub.onCall(3).returns(dataIV); - - let packetBytes = util.hexToUint8Array(` + let aead_protectVal = openpgp.config.aead_protect; + let aead_chunk_size_byteVal = openpgp.config.aead_chunk_size_byte; + let s2k_iteration_count_byteVal = openpgp.config.s2k_iteration_count_byte; + openpgp.config.aead_protect = true; + openpgp.config.aead_chunk_size_byte = 14; + openpgp.config.s2k_iteration_count_byte = 0x90; + + let salt = openpgp.util.hex_to_Uint8Array(`cd5a9f70fbe0bc65`); + let sessionKey = openpgp.util.hex_to_Uint8Array(`86 f1 ef b8 69 52 32 9f 24 ac d3 bf d0 e5 34 6d`.replace(/\s+/g, '')); + let sessionIV = openpgp.util.hex_to_Uint8Array(`bc 66 9e 34 e5 00 dc ae dc 5b 32 aa 2d ab 02 35`.replace(/\s+/g, '')); + let dataIV = openpgp.util.hex_to_Uint8Array(`b7 32 37 9f 73 c4 92 8d e2 5f ac fe 65 17 ec 10`.replace(/\s+/g, '')); + + let randomBytesStub = stub(openpgp.crypto.random, 'getRandomBytes'); + randomBytesStub.onCall(0).returns(resolves(salt)); + randomBytesStub.onCall(1).returns(resolves(sessionKey)); + randomBytesStub.onCall(2).returns(resolves(sessionIV)); + randomBytesStub.onCall(3).returns(resolves(dataIV)); + + let packetBytes = openpgp.util.hex_to_Uint8Array(` c3 3e 05 07 01 03 08 cd 5a 9f 70 fb e0 bc 65 90 bc 66 9e 34 e5 00 dc ae dc 5b 32 aa 2d ab 02 35 9d ee 19 d0 7c 34 46 c4 31 2a 34 ae 19 67 a2 fb @@ -564,10 +563,10 @@ module.exports = () => describe("Packet", function() { const passphrase = 'password'; const algo = 'aes128'; - const literal = new openpgp.LiteralDataPacket(0); - const key_enc = new openpgp.SymEncryptedSessionKeyPacket(); - const enc = new openpgp.AEADEncryptedDataPacket(); - const msg = new openpgp.PacketList(); + const literal = new openpgp.packet.Literal(0); + const key_enc = new openpgp.packet.SymEncryptedSessionKey(); + const enc = new openpgp.packet.SymEncryptedAEADProtected(); + const msg = new openpgp.packet.List(); msg.push(key_enc); msg.push(enc); @@ -577,7 +576,7 @@ module.exports = () => describe("Packet", function() { const key = key_enc.sessionKey; - literal.setBytes(util.strToUint8Array('Hello, world!\n'), openpgp.enums.literal.binary); + literal.setBytes(openpgp.util.str_to_Uint8Array('Hello, world!\n'), openpgp.enums.literal.binary); literal.filename = ''; enc.packets.push(literal); await enc.encrypt(algo, key); @@ -585,8 +584,8 @@ module.exports = () => describe("Packet", function() { const data = msg.write(); expect(await openpgp.stream.readToEnd(openpgp.stream.clone(data))).to.deep.equal(packetBytes); - const msg2 = new openpgp.PacketList(); - await msg2.read(data, openpgp); + const msg2 = new openpgp.packet.List(); + await msg2.read(data); await msg2[0].decrypt(passphrase); const key2 = msg2[0].sessionKey; @@ -594,9 +593,9 @@ module.exports = () => describe("Packet", function() { expect(await stringify(msg2[1].packets[0].data)).to.equal(stringify(literal.data)); } finally { - openpgp.config.aeadProtect = aeadProtectVal; - openpgp.config.aeadChunkSizeByte = aeadChunkSizeByteVal; - openpgp.config.s2kIterationCountByte = s2kIterationCountByteVal; + openpgp.config.aead_protect = aead_protectVal; + openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteVal; + openpgp.config.s2k_iteration_count_byte = s2k_iteration_count_byteVal; randomBytesStub.restore(); } }); @@ -604,28 +603,25 @@ module.exports = () => describe("Packet", function() { it('Sym. encrypted session key reading/writing test vector (AEAD, OCB)', async function() { // From https://gitlab.com/openpgp-wg/rfc4880bis/blob/00b20923/back.mkd#sample-aead-ocb-encryption-and-decryption - const nodeCrypto = util.getNodeCrypto(); - if (!nodeCrypto) return; - - let aeadProtectVal = openpgp.config.aeadProtect; - let aeadChunkSizeByteVal = openpgp.config.aeadChunkSizeByte; - let s2kIterationCountByteVal = openpgp.config.s2kIterationCountByte; - openpgp.config.aeadProtect = true; - openpgp.config.aeadChunkSizeByte = 14; - openpgp.config.s2kIterationCountByte = 0x90; - - let salt = util.hexToUint8Array(`9f0b7da3e5ea6477`); - let sessionKey = util.hexToUint8Array(`d1 f0 1b a3 0e 13 0a a7 d2 58 2c 16 e0 50 ae 44`.replace(/\s+/g, '')); - let sessionIV = util.hexToUint8Array(`99 e3 26 e5 40 0a 90 93 6c ef b4 e8 eb a0 8c`.replace(/\s+/g, '')); - let dataIV = util.hexToUint8Array(`5e d2 bc 1e 47 0a be 8f 1d 64 4c 7a 6c 8a 56`.replace(/\s+/g, '')); - - let randomBytesStub = stub(nodeCrypto, 'randomBytes'); - randomBytesStub.onCall(0).returns(salt); - randomBytesStub.onCall(1).returns(sessionKey); - randomBytesStub.onCall(2).returns(sessionIV); - randomBytesStub.onCall(3).returns(dataIV); - - let packetBytes = util.hexToUint8Array(` + let aead_protectVal = openpgp.config.aead_protect; + let aead_chunk_size_byteVal = openpgp.config.aead_chunk_size_byte; + let s2k_iteration_count_byteVal = openpgp.config.s2k_iteration_count_byte; + openpgp.config.aead_protect = true; + openpgp.config.aead_chunk_size_byte = 14; + openpgp.config.s2k_iteration_count_byte = 0x90; + + let salt = openpgp.util.hex_to_Uint8Array(`9f0b7da3e5ea6477`); + let sessionKey = openpgp.util.hex_to_Uint8Array(`d1 f0 1b a3 0e 13 0a a7 d2 58 2c 16 e0 50 ae 44`.replace(/\s+/g, '')); + let sessionIV = openpgp.util.hex_to_Uint8Array(`99 e3 26 e5 40 0a 90 93 6c ef b4 e8 eb a0 8c`.replace(/\s+/g, '')); + let dataIV = openpgp.util.hex_to_Uint8Array(`5e d2 bc 1e 47 0a be 8f 1d 64 4c 7a 6c 8a 56`.replace(/\s+/g, '')); + + let randomBytesStub = stub(openpgp.crypto.random, 'getRandomBytes'); + randomBytesStub.onCall(0).returns(resolves(salt)); + randomBytesStub.onCall(1).returns(resolves(sessionKey)); + randomBytesStub.onCall(2).returns(resolves(sessionIV)); + randomBytesStub.onCall(3).returns(resolves(dataIV)); + + let packetBytes = openpgp.util.hex_to_Uint8Array(` c3 3d 05 07 02 03 08 9f 0b 7d a3 e5 ea 64 77 90 99 e3 26 e5 40 0a 90 93 6c ef b4 e8 eb a0 8c 67 73 71 6d 1f 27 14 54 0a 38 fc ac 52 99 49 da c5 @@ -642,10 +638,10 @@ module.exports = () => describe("Packet", function() { const passphrase = 'password'; const algo = 'aes128'; - const literal = new openpgp.LiteralDataPacket(0); - const key_enc = new openpgp.SymEncryptedSessionKeyPacket(); - const enc = new openpgp.AEADEncryptedDataPacket(); - const msg = new openpgp.PacketList(); + const literal = new openpgp.packet.Literal(0); + const key_enc = new openpgp.packet.SymEncryptedSessionKey(); + const enc = new openpgp.packet.SymEncryptedAEADProtected(); + const msg = new openpgp.packet.List(); enc.aeadAlgorithm = key_enc.aeadAlgorithm = 'ocb'; msg.push(key_enc); @@ -656,7 +652,7 @@ module.exports = () => describe("Packet", function() { const key = key_enc.sessionKey; - literal.setBytes(util.strToUint8Array('Hello, world!\n'), openpgp.enums.literal.binary); + literal.setBytes(openpgp.util.str_to_Uint8Array('Hello, world!\n'), openpgp.enums.literal.binary); literal.filename = ''; enc.packets.push(literal); await enc.encrypt(algo, key); @@ -664,8 +660,8 @@ module.exports = () => describe("Packet", function() { const data = msg.write(); expect(await openpgp.stream.readToEnd(openpgp.stream.clone(data))).to.deep.equal(packetBytes); - const msg2 = new openpgp.PacketList(); - await msg2.read(data, openpgp); + const msg2 = new openpgp.packet.List(); + await msg2.read(data); await msg2[0].decrypt(passphrase); const key2 = msg2[0].sessionKey; @@ -673,9 +669,9 @@ module.exports = () => describe("Packet", function() { expect(await stringify(msg2[1].packets[0].data)).to.equal(stringify(literal.data)); } finally { - openpgp.config.aeadProtect = aeadProtectVal; - openpgp.config.aeadChunkSizeByte = aeadChunkSizeByteVal; - openpgp.config.s2kIterationCountByte = s2kIterationCountByteVal; + openpgp.config.aead_protect = aead_protectVal; + openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteVal; + openpgp.config.s2k_iteration_count_byte = s2k_iteration_count_byteVal; randomBytesStub.restore(); } }); @@ -693,13 +689,13 @@ module.exports = () => describe("Packet", function() { '=pR+C\n' + '-----END PGP MESSAGE-----'; - let key = new openpgp.PacketList(); - await key.read((await openpgp.unarmor(armored_key)).data, openpgp); + let key = new openpgp.packet.List(); + await key.read((await openpgp.armor.decode(armored_key)).data); key = key[3]; await key.decrypt('test'); - const msg = new openpgp.PacketList(); - await msg.read((await openpgp.unarmor(armored_msg)).data, openpgp); + const msg = new openpgp.packet.List(); + await msg.read((await openpgp.armor.decode(armored_msg)).data); return msg[0].decrypt(key).then(async () => { await msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey); @@ -711,18 +707,22 @@ module.exports = () => describe("Packet", function() { }); it('Secret key reading with signature verification.', async function() { - const key = new openpgp.PacketList(); - await key.read((await openpgp.unarmor(armored_key)).data, openpgp); - - expect(key[2].verified).to.be.null; - expect(key[4].verified).to.be.null; - - await key[2].verify( - key[0], openpgp.enums.signature.certGeneric, { userId: key[1], key: key[0] } - ).then(async () => expect(key[2].verified).to.be.true); - await key[4].verify( - key[0], openpgp.enums.signature.keyBinding, { key: key[0], bind: key[3] } - ).then(async () => expect(key[4].verified).to.be.true); + const key = new openpgp.packet.List(); + await key.read((await openpgp.armor.decode(armored_key)).data); + return Promise.all([ + expect(key[2].verify(key[0], + openpgp.enums.signature.cert_generic, + { + userId: key[1], + key: key[0] + })).to.eventually.be.true, + expect(key[4].verify(key[0], + openpgp.enums.signature.key_binding, + { + key: key[0], + bind: key[3] + })).to.eventually.be.true + ]); }); it('Reading a signed, encrypted message.', async function() { @@ -742,12 +742,12 @@ module.exports = () => describe("Packet", function() { '=htrB\n' + '-----END PGP MESSAGE-----'; - const key = new openpgp.PacketList(); - await key.read((await openpgp.unarmor(armored_key)).data, openpgp); + const key = new openpgp.packet.List(); + await key.read((await openpgp.armor.decode(armored_key)).data); await key[3].decrypt('test'); - const msg = new openpgp.PacketList(); - await msg.read((await openpgp.unarmor(armored_msg)).data, openpgp); + const msg = new openpgp.packet.List(); + await msg.read((await openpgp.armor.decode(armored_msg)).data); return msg[0].decrypt(key[3]).then(async () => { await msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey); @@ -756,8 +756,10 @@ module.exports = () => describe("Packet", function() { payload.concat(await openpgp.stream.readToEnd(payload.stream, arr => arr)); await Promise.all([ - payload[2].verify(key[0], openpgp.enums.signature.binary, payload[1]), - openpgp.stream.pipe(payload[1].getBytes(),new openpgp.stream.WritableStream()) + expect(payload[2].verify( + key[0], openpgp.enums.signature.binary, payload[1] + )).to.eventually.be.true, + openpgp.stream.pipe(payload[1].getBytes(), new WritableStream()) ]); }); }); @@ -776,7 +778,7 @@ kePFjAnu9cpynKXu3usf8+FuBw2zLsg1Id1n7ttxoAte416KjBN9lFBt8mcu =wEIR -----END PGP SIGNATURE-----`; - const signature = await openpgp.readArmoredSignature(armored_sig); + const signature = await openpgp.signature.readArmored(armored_sig); expect(signature.packets[0].signersUserId).to.equal('test-wkd@metacode.biz'); }); @@ -815,7 +817,7 @@ V+HOQJQxXJkVRYa3QrFUehiMzTeqqMdgC6ZqJy7+ =et/d -----END PGP PUBLIC KEY BLOCK-----`; - const key = await openpgp.readArmoredKey(pubkey); + const key = (await openpgp.key.readArmored(pubkey)).keys[0]; const { notations, rawNotations } = key.users[0].selfCertifications[0]; @@ -839,95 +841,110 @@ V+HOQJQxXJkVRYa3QrFUehiMzTeqqMdgC6ZqJy7+ expect(rawNotations[1].humanReadable).to.equal(true); }); - it('Writing and encryption of a secret key packet (AEAD)', async function() { - const rsa = openpgp.enums.publicKey.rsaEncryptSign; - const keySize = util.getWebCryptoAll() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys - const { privateParams, publicParams } = await crypto.generateParams(rsa, keySize, 65537); - - const secretKeyPacket = new openpgp.SecretKeyPacket(); - secretKeyPacket.privateParams = privateParams; - secretKeyPacket.publicParams = publicParams; - secretKeyPacket.algorithm = "rsaSign"; - secretKeyPacket.isEncrypted = false; - await secretKeyPacket.encrypt('hello'); - - const raw = new openpgp.PacketList(); - raw.push(secretKeyPacket); - const packetList = new openpgp.PacketList(); - await packetList.read(raw.write(), openpgp); - const secretKeyPacket2 = packetList[0]; - await secretKeyPacket2.decrypt('hello'); - - expect(secretKeyPacket2.privateParams).to.deep.equal(secretKeyPacket.privateParams); - expect(secretKeyPacket2.publicParams).to.deep.equal(secretKeyPacket.publicParams); + it('Writing and encryption of a secret key packet.', function() { + const key = new openpgp.packet.List(); + key.push(new openpgp.packet.SecretKey()); + + const rsa = openpgp.crypto.publicKey.rsa; + const keySize = openpgp.util.getWebCryptoAll() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys + + return rsa.generate(keySize, "10001").then(async function(mpiGen) { + let mpi = [mpiGen.n, mpiGen.e, mpiGen.d, mpiGen.p, mpiGen.q, mpiGen.u]; + mpi = mpi.map(function(k) { + return new openpgp.MPI(k); + }); + + key[0].params = mpi; + key[0].algorithm = "rsa_sign"; + key[0].isEncrypted = false; + await key[0].encrypt('hello'); + + const raw = key.write(); + + const key2 = new openpgp.packet.List(); + await key2.read(raw); + await key2[0].decrypt('hello'); + + expect(key[0].params.toString()).to.equal(key2[0].params.toString()); + }); }); - it('Writing and encryption of a secret key packet (CFB)', async function() { - const aeadProtectVal = openpgp.config.aeadProtect; - openpgp.config.aeadProtect = false; + it('Writing and encryption of a secret key packet. (AEAD)', async function() { + let aead_protectVal = openpgp.config.aead_protect; + openpgp.config.aead_protect = true; + + const key = new openpgp.packet.List(); + key.push(new openpgp.packet.SecretKey()); - const rsa = openpgp.enums.publicKey.rsaEncryptSign; - const keySize = util.getWebCryptoAll() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys + const rsa = openpgp.crypto.publicKey.rsa; + const keySize = openpgp.util.getWebCryptoAll() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys try { - const { privateParams, publicParams } = await crypto.generateParams(rsa, keySize, 65537); - const secretKeyPacket = new openpgp.SecretKeyPacket(); - secretKeyPacket.privateParams = privateParams; - secretKeyPacket.publicParams = publicParams; - secretKeyPacket.algorithm = "rsaSign"; - secretKeyPacket.isEncrypted = false; - await secretKeyPacket.encrypt('hello'); - - const raw = new openpgp.PacketList(); - raw.push(secretKeyPacket); - const packetList = new openpgp.PacketList(); - await packetList.read(raw.write(), openpgp); - const secretKeyPacket2 = packetList[0]; - await secretKeyPacket2.decrypt('hello'); + const mpiGen = await rsa.generate(keySize, "10001"); + let mpi = [mpiGen.n, mpiGen.e, mpiGen.d, mpiGen.p, mpiGen.q, mpiGen.u]; + mpi = mpi.map(function(k) { + return new openpgp.MPI(k); + }); + + key[0].params = mpi; + key[0].algorithm = "rsa_sign"; + key[0].isEncrypted = false; + await key[0].encrypt('hello'); + + const raw = key.write(); + + const key2 = new openpgp.packet.List(); + await key2.read(raw); + await key2[0].decrypt('hello'); + + expect(key[0].params.toString()).to.equal(key2[0].params.toString()); } finally { - openpgp.config.aeadProtect = aeadProtectVal; + openpgp.config.aead_protect = aead_protectVal; } }); - it('Writing and verification of a signature packet', function() { - const key = new openpgp.SecretKeyPacket(); + it('Writing and verification of a signature packet.', function() { + const key = new openpgp.packet.SecretKey(); - const rsa = openpgp.enums.publicKey.rsaEncryptSign; - const keySize = util.getWebCryptoAll() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys + const rsa = openpgp.crypto.publicKey.rsa; + const keySize = openpgp.util.getWebCryptoAll() ? 2048 : 512; // webkit webcrypto accepts minimum 2048 bit keys - return crypto.generateParams(rsa, keySize, 65537).then(function({ privateParams, publicParams }) { - const testText = input.createSomeMessage(); + return rsa.generate(keySize, "10001").then(function(mpiGen) { + let mpi = [mpiGen.n, mpiGen.e, mpiGen.d, mpiGen.p, mpiGen.q, mpiGen.u]; + mpi = mpi.map(function(k) { + return new openpgp.MPI(k); + }); + const testText = input.createSomeMessage(); - key.publicParams = publicParams; - key.privateParams = privateParams; - key.algorithm = "rsaSign"; + key.params = mpi; + key.algorithm = "rsa_sign"; - const signed = new openpgp.PacketList(); - const literal = new openpgp.LiteralDataPacket(); - const signature = new openpgp.SignaturePacket(); + const signed = new openpgp.packet.List(); + const literal = new openpgp.packet.Literal(); + const signature = new openpgp.packet.Signature(); - literal.setText(testText); + literal.setText(testText); - signature.hashAlgorithm = openpgp.enums.hash.sha256; - signature.publicKeyAlgorithm = openpgp.enums.publicKey.rsaSign; - signature.signatureType = openpgp.enums.signature.text; + signature.hashAlgorithm = 'sha256'; + signature.publicKeyAlgorithm = 'rsa_sign'; + signature.signatureType = 'text'; - return signature.sign(key, literal).then(async () => { + return signature.sign(key, literal).then(async () => { - signed.push(literal); - signed.push(signature); + signed.push(literal); + signed.push(signature); - const raw = signed.write(); + const raw = signed.write(); - const signed2 = new openpgp.PacketList(); - await signed2.read(raw, openpgp); - signed2.concat(await openpgp.stream.readToEnd(signed2.stream, arr => arr)); + const signed2 = new openpgp.packet.List(); + await signed2.read(raw); + signed2.concat(await openpgp.stream.readToEnd(signed2.stream, arr => arr)); - await Promise.all([ - signed2[1].verify(key, openpgp.enums.signature.text, signed2[0]), - openpgp.stream.pipe(signed2[0].getBytes(), new openpgp.stream.WritableStream()) - ]); - }); + await Promise.all([ + expect(signed2[1].verify(key, openpgp.enums.signature.text, signed2[0])).to.eventually.be.true, + openpgp.stream.pipe(signed2[0].getBytes(), new WritableStream()) + ]); + }); }); }); }); diff --git a/test/general/signature.js b/test/general/signature.js index 856fd4de..b286c03d 100644 --- a/test/general/signature.js +++ b/test/general/signature.js @@ -1,12 +1,11 @@ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); -const util = require('../../src/util'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); const chai = require('chai'); chai.use(require('chai-as-promised')); const expect = chai.expect; -module.exports = () => describe("Signature", function() { +describe("Signature", function() { const priv_key_arm1 = ['-----BEGIN PGP PRIVATE KEY BLOCK-----', 'Version: GnuPG v1.4.11 (GNU/Linux)', @@ -657,7 +656,7 @@ WPVMYDzj6X7I1A+nWeNiPlp2PoUUUvdCLisY1aU1wyTJa7wBsLARsrhXk5/R1pQt Blk+CJ7ytHy6En8542bB/yC+Z9/zWbVuhg== =jmT1 -----END PGP PUBLIC KEY BLOCK-----`; - + const msg_sig_expired = ['-----BEGIN PGP MESSAGE-----', 'Comment: GPGTools - https://gpgtools.org', @@ -842,32 +841,32 @@ hUhMKMuiM3pRwdIyDOItkUWQmjEEw7/XmhgInkXsCw== `; it('Testing signature checking on CAST5-enciphered message', async function() { - const { rejectMessageHashAlgorithms } = openpgp.config; - Object.assign(openpgp.config, { rejectMessageHashAlgorithms: new Set([openpgp.enums.hash.md5, openpgp.enums.hash.ripemd]) }); + const { reject_message_hash_algorithms } = openpgp.config; + Object.assign(openpgp.config, { reject_message_hash_algorithms: new Set([openpgp.enums.hash.md5, openpgp.enums.hash.ripemd]) }); try { - const priv_key = await openpgp.readArmoredKey(priv_key_arm1); - const pub_key = await openpgp.readArmoredKey(pub_key_arm1); - const msg = await openpgp.readArmoredMessage(msg_arm1); + const priv_key = (await openpgp.key.readArmored(priv_key_arm1)).keys[0]; + const pub_key = (await openpgp.key.readArmored(pub_key_arm1)).keys[0]; + const msg = await openpgp.message.readArmored(msg_arm1); await priv_key.decrypt("abcd"); const decrypted = await openpgp.decrypt({ privateKeys: priv_key, publicKeys:[pub_key], message:msg }); expect(decrypted.data).to.exist; expect(decrypted.signatures[0].valid).to.be.true; expect(decrypted.signatures[0].signature.packets.length).to.equal(1); } finally { - Object.assign(openpgp.config, { rejectMessageHashAlgorithms }); + Object.assign(openpgp.config, { reject_message_hash_algorithms }); } }); it('Supports decrypting with GnuPG stripped-key extension', async function() { - const { rejectMessageHashAlgorithms } = openpgp.config; - Object.assign(openpgp.config, { rejectMessageHashAlgorithms: new Set([openpgp.enums.hash.md5, openpgp.enums.hash.ripemd]) }); + const { reject_message_hash_algorithms } = openpgp.config; + Object.assign(openpgp.config, { reject_message_hash_algorithms: new Set([openpgp.enums.hash.md5, openpgp.enums.hash.ripemd]) }); try { // exercises the GnuPG s2k type 1001 extension: // the secrets on the primary key have been stripped. - const priv_key_gnupg_ext = await openpgp.readArmoredKey(priv_key_arm1_stripped); - const priv_key_gnupg_ext_2 = await openpgp.readArmoredKey(priv_key_arm1_stripped); - const pub_key = await openpgp.readArmoredKey(pub_key_arm1); - const message = await openpgp.readArmoredMessage(msg_arm1); + const priv_key_gnupg_ext = (await openpgp.key.readArmored(priv_key_arm1_stripped)).keys[0]; + const priv_key_gnupg_ext_2 = (await openpgp.key.readArmored(priv_key_arm1_stripped)).keys[0]; + const pub_key = (await openpgp.key.readArmored(pub_key_arm1)).keys[0]; + const message = await openpgp.message.readArmored(msg_arm1); const primaryKey_packet = priv_key_gnupg_ext.primaryKey.write(); expect(priv_key_gnupg_ext.isDecrypted()).to.be.false; await priv_key_gnupg_ext.decrypt("abcd"); @@ -878,27 +877,27 @@ hUhMKMuiM3pRwdIyDOItkUWQmjEEw7/XmhgInkXsCw== expect(msg.signatures).to.have.length(1); expect(msg.signatures[0].valid).to.be.true; expect(msg.signatures[0].signature.packets.length).to.equal(1); - await expect(openpgp.sign({ message: openpgp.Message.fromText('test'), privateKeys: [priv_key_gnupg_ext] })).to.eventually.be.rejectedWith(/Cannot sign with a gnu-dummy key/); - await expect(openpgp.reformatKey({ userIds: { name: 'test' }, privateKey: priv_key_gnupg_ext })).to.eventually.be.rejectedWith(/Cannot reformat a gnu-dummy primary key/); - await expect(openpgp.reformatKey({ userIds: { name: 'test' }, privateKey: priv_key_gnupg_ext_2, passphrase: 'test' })).to.eventually.be.rejectedWith(/Cannot reformat a gnu-dummy primary key/); + await expect(openpgp.sign({ message: openpgp.message.fromText('test'), privateKeys: [priv_key_gnupg_ext] })).to.eventually.be.rejectedWith('Missing private key parameters'); + await expect(openpgp.reformatKey({ userIds: { name: 'test' }, privateKey: priv_key_gnupg_ext })).to.eventually.be.rejectedWith('Missing private key parameters'); + await expect(openpgp.reformatKey({ userIds: { name: 'test' }, privateKey: priv_key_gnupg_ext_2, passphrase: 'test' })).to.eventually.be.rejectedWith('Missing private key parameters'); await priv_key_gnupg_ext.encrypt("abcd"); expect(priv_key_gnupg_ext.isDecrypted()).to.be.false; const primaryKey_packet2 = priv_key_gnupg_ext.primaryKey.write(); expect(primaryKey_packet).to.deep.equal(primaryKey_packet2); } finally { - Object.assign(openpgp.config, { rejectMessageHashAlgorithms }); + Object.assign(openpgp.config, { reject_message_hash_algorithms }); } }); it('Supports signing with GnuPG stripped-key extension', async function() { - const priv_key_gnupg_ext = await openpgp.readArmoredKey(flowcrypt_stripped_key); + const priv_key_gnupg_ext = (await openpgp.key.readArmored(flowcrypt_stripped_key)).keys[0]; await priv_key_gnupg_ext.decrypt('FlowCrypt'); - const sig = await openpgp.sign({ message: openpgp.Message.fromText('test'), privateKeys: [priv_key_gnupg_ext], date: new Date('2018-12-17T03:24:00') }); - expect(sig).to.match(/-----END PGP MESSAGE-----\n$/); + const sig = await openpgp.sign({ message: openpgp.message.fromText('test'), privateKeys: [priv_key_gnupg_ext], date: new Date('2018-12-17T03:24:00') }); + expect(sig.data).to.match(/-----END PGP MESSAGE-----\r\n$/); }); it('Supports non-human-readable notations', async function() { - const { packets: [signature] } = await openpgp.readArmoredMessage(signature_with_non_human_readable_notations); + const { packets: [signature] } = await openpgp.message.readArmored(signature_with_non_human_readable_notations); // There are no human-readable notations so `notations` property does not // expose the `test@key.com` notation. expect(Object.keys(signature.notations).length).to.equal(0); @@ -915,7 +914,7 @@ hUhMKMuiM3pRwdIyDOItkUWQmjEEw7/XmhgInkXsCw== it('Checks for critical bit in non-human-readable notations', async function() { try { openpgp.config.tolerant = false; - await openpgp.readArmoredMessage(`-----BEGIN PGP SIGNATURE----- + await openpgp.message.readArmored(`-----BEGIN PGP SIGNATURE----- wsEfBAABCABJBYJfKDH0K5QAAAAAAB0ABXVua25vd25AdGVzdHMuc2VxdW9pYS1w Z3Aub3JndmFsdWUWIQTRpm4aI7GCyZgPeIz7/MgqAV5zMAAKCRD7/MgqAV5zMLBK @@ -930,7 +929,7 @@ IYs+aFmldMg4p1t1Ab/bRHbBxIlzhtbNE6IyOfc17mgOcjQzVJBc/EaxD7S3KjU2 bwM= =0x2S -----END PGP SIGNATURE-----`); - throw new Error('openpgp.readArmoredMessage should throw but it did not.'); + throw new Error('openpgp.message.readArmored should throw but it did not.'); } catch (e) { expect(e.message).to.equal('Unknown critical notation: unknown@tests.sequoia-pgp.org'); } finally { @@ -939,8 +938,8 @@ bwM= }); it('Verify V4 signature. Hash: SHA1. PK: RSA. Signature Type: 0x00 (binary document)', async function() { - const { rejectMessageHashAlgorithms } = openpgp.config; - Object.assign(openpgp.config, { rejectMessageHashAlgorithms: new Set([openpgp.enums.hash.md5, openpgp.enums.hash.ripemd]) }); + const { reject_message_hash_algorithms } = openpgp.config; + Object.assign(openpgp.config, { reject_message_hash_algorithms: new Set([openpgp.enums.hash.md5, openpgp.enums.hash.ripemd]) }); try { const signedArmor = ['-----BEGIN PGP MESSAGE-----', @@ -954,16 +953,16 @@ bwM= '=VH8F', '-----END PGP MESSAGE-----'].join('\n'); - const sMsg = await openpgp.readArmoredMessage(signedArmor); - const pub_key = await openpgp.readArmoredKey(pub_key_arm2); + const sMsg = await openpgp.message.readArmored(signedArmor); + const pub_key = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; const verified = await sMsg.verify([pub_key]); - openpgp.stream.pipe(sMsg.getLiteralData(), new openpgp.stream.WritableStream()); + openpgp.stream.pipe(sMsg.getLiteralData(), new WritableStream()); expect(verified).to.exist; expect(verified).to.have.length(1); expect(await verified[0].verified).to.be.true; expect((await verified[0].signature).packets.length).to.equal(1); } finally { - Object.assign(openpgp.config, { rejectMessageHashAlgorithms }); + Object.assign(openpgp.config, { reject_message_hash_algorithms }); } }); @@ -985,9 +984,9 @@ bwM= '-----END PGP MESSAGE-----'].join('\n'); const plaintext = 'short message\nnext line\n한국어/조선말'; - const esMsg = await openpgp.readArmoredMessage(msg_armor); - const pubKey = await openpgp.readArmoredKey(pub_key_arm2); - const privKey = await openpgp.readArmoredKey(priv_key_arm2); + const esMsg = await openpgp.message.readArmored(msg_armor); + const pubKey = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; + const privKey = (await openpgp.key.readArmored(priv_key_arm2)).keys[0]; await Promise.all(esMsg.getEncryptionKeyIds().map(keyId => privKey.decrypt('hello world', keyId))); @@ -1020,9 +1019,9 @@ bwM= '-----END PGP MESSAGE-----'].join('\n'); const plaintext = 'short message\nnext line\n한국어/조선말'; - const sMsg = await openpgp.readArmoredMessage(msg_armor); - const pubKey2 = await openpgp.readArmoredKey(pub_key_arm2); - const pubKey3 = await openpgp.readArmoredKey(pub_key_arm3); + const sMsg = await openpgp.message.readArmored(msg_armor); + const pubKey2 = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; + const pubKey3 = (await openpgp.key.readArmored(pub_key_arm3)).keys[0]; const keyids = sMsg.getSigningKeyIds(); @@ -1044,8 +1043,8 @@ bwM= let testFailed = true; try { openpgp.config.tolerant = false; - const sMsg = await openpgp.readArmoredMessage(signature_with_critical_notation); - const pub_key = await openpgp.readArmoredKey(pub_key_arm2); + const sMsg = await openpgp.message.readArmored(signature_with_critical_notation); + const pub_key = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; const verified = await sMsg.verify([pub_key]); await verified[0].verified; testFailed = false; @@ -1060,15 +1059,15 @@ bwM= it('Verify succeeds with known signed message with critical notations', async function() { openpgp.config.tolerant = false; - openpgp.config.knownNotations.push('test@example.com'); + openpgp.config.known_notations.push('test@example.com'); try { - const sMsg = await openpgp.readArmoredMessage(signature_with_critical_notation); - const pub_key = await openpgp.readArmoredKey(pub_key_arm2); + const sMsg = await openpgp.message.readArmored(signature_with_critical_notation); + const pub_key = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; const verified = await sMsg.verify([pub_key]); - openpgp.stream.pipe(sMsg.getLiteralData(), new openpgp.stream.WritableStream()); + openpgp.stream.pipe(sMsg.getLiteralData(), new WritableStream()); expect(await verified[0].verified).to.be.true; } finally { - openpgp.config.knownNotations.pop(); + openpgp.config.known_notations.pop(); openpgp.config.tolerant = true; } }); @@ -1098,9 +1097,9 @@ bwM= '-----END PGP SIGNATURE-----'].join('\n'); const plaintext = 'short message\nnext line\n한국어/조선말'; - const csMsg = await openpgp.readArmoredCleartextMessage(msg_armor); - const pubKey2 = await openpgp.readArmoredKey(pub_key_arm2); - const pubKey3 = await openpgp.readArmoredKey(pub_key_arm3); + const csMsg = await openpgp.cleartext.readArmored(msg_armor); + const pubKey2 = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; + const pubKey3 = (await openpgp.key.readArmored(pub_key_arm3)).keys[0]; const keyids = csMsg.getSigningKeyIds(); @@ -1119,8 +1118,8 @@ bwM= }); it('Verify latin-1 signed message', async function() { - const latin1Binary = util.hexToUint8Array('48e46c6cf62057e86c74'); - const message = openpgp.Message.fromBinary(latin1Binary); + const latin1Binary = openpgp.util.hex_to_Uint8Array('48e46c6cf62057e86c74'); + const message = openpgp.message.fromBinary(latin1Binary); message.appendSignature(`-----BEGIN PGP SIGNATURE----- @@ -1139,7 +1138,7 @@ PAAeuQTUrcJdZeJ86eQ9cCUB216HCwSKOWTQRzL+hBWKXij4WD4= =ZEFm -----END PGP SIGNATURE-----`); - const pubKey = await openpgp.readArmoredKey(pub_latin1_msg); + const pubKey = (await openpgp.key.readArmored(pub_latin1_msg)).keys[0]; return message.verify([pubKey]).then(async verifiedSig => { expect(await openpgp.stream.readToEnd(message.getLiteralData())).to.equal(latin1Binary); @@ -1152,8 +1151,8 @@ PAAeuQTUrcJdZeJ86eQ9cCUB216HCwSKOWTQRzL+hBWKXij4WD4= it('Verify cleartext signed message with trailing spaces from GPG', async function() { - const { rejectMessageHashAlgorithms } = openpgp.config; - Object.assign(openpgp.config, { rejectMessageHashAlgorithms: new Set([openpgp.enums.hash.md5, openpgp.enums.hash.ripemd]) }); + const { reject_message_hash_algorithms } = openpgp.config; + Object.assign(openpgp.config, { reject_message_hash_algorithms: new Set([openpgp.enums.hash.md5, openpgp.enums.hash.ripemd]) }); try { const msg_armor = `-----BEGIN PGP SIGNED MESSAGE----- @@ -1176,8 +1175,8 @@ zmuVOdNuWQqxT9Sqa84= -----END PGP SIGNATURE-----`; const plaintext = 'space: \nspace and tab: \t\nno trailing space\n \ntab:\t\ntab and space:\t '; - const csMsg = await openpgp.readArmoredCleartextMessage(msg_armor); - const pubKey = await openpgp.readArmoredKey(pub_key_arm2); + const csMsg = await openpgp.cleartext.readArmored(msg_armor); + const pubKey = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; const keyids = csMsg.getSigningKeyIds(); @@ -1190,7 +1189,7 @@ zmuVOdNuWQqxT9Sqa84= expect(cleartextSig.signatures[0].valid).to.be.true; expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1); } finally { - Object.assign(openpgp.config, { rejectMessageHashAlgorithms }); + Object.assign(openpgp.config, { reject_message_hash_algorithms }); } }); @@ -1210,8 +1209,8 @@ yYDnCgA= -----END PGP MESSAGE-----`; const plaintext = 'space: \nspace and tab: \t\nno trailing space\n \ntab:\t\ntab and space:\t '; - const sMsg = await openpgp.readArmoredMessage(msg_armor); - const pubKey = await openpgp.readArmoredKey(pub_key_arm2); + const sMsg = await openpgp.message.readArmored(msg_armor); + const pubKey = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; const keyids = sMsg.getSigningKeyIds(); @@ -1219,9 +1218,9 @@ yYDnCgA= return openpgp.verify({ publicKeys: [pubKey], message: sMsg }).then(function(cleartextSig) { expect(cleartextSig).to.exist; - expect(cleartextSig.data).to.equal(plaintext); + expect(openpgp.util.Uint8Array_to_str(openpgp.util.nativeEOL(cleartextSig.data))).to.equal(plaintext); expect(cleartextSig.signatures).to.have.length(1); - expect(cleartextSig.signatures[0].valid).to.equal(!openpgp.config.rejectMessageHashAlgorithms.has(openpgp.enums.hash.sha1)); + expect(cleartextSig.signatures[0].valid).to.equal(!openpgp.config.reject_message_hash_algorithms.has(openpgp.enums.hash.sha1)); expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1); }); }); @@ -1241,14 +1240,14 @@ yYDnCgA= -----END PGP MESSAGE-----`.split(''); const plaintext = 'space: \nspace and tab: \t\nno trailing space\n \ntab:\t\ntab and space:\t '; - const sMsg = await openpgp.readArmoredMessage(new openpgp.stream.ReadableStream({ + const sMsg = await openpgp.message.readArmored(new ReadableStream({ async pull(controller) { await new Promise(setTimeout); controller.enqueue(msg_armor.shift()); if (!msg_armor.length) controller.close(); } })); - const pubKey = await openpgp.readArmoredKey(pub_key_arm2); + const pubKey = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; const keyids = sMsg.getSigningKeyIds(); @@ -1256,9 +1255,9 @@ yYDnCgA= return openpgp.verify({ publicKeys: [pubKey], message: sMsg }).then(async function(cleartextSig) { expect(cleartextSig).to.exist; - expect(await openpgp.stream.readToEnd(cleartextSig.data)).to.equal(plaintext); + expect(openpgp.util.Uint8Array_to_str(openpgp.util.nativeEOL(await openpgp.stream.readToEnd(cleartextSig.data)))).to.equal(plaintext); expect(cleartextSig.signatures).to.have.length(1); - if (!openpgp.config.rejectMessageHashAlgorithms.has(openpgp.enums.hash.sha1)) { + if (!openpgp.config.reject_message_hash_algorithms.has(openpgp.enums.hash.sha1)) { expect(await cleartextSig.signatures[0].verified).to.be.true; } else { await expect(cleartextSig.signatures[0].verified).to.be.rejectedWith('Insecure message hash algorithm: SHA1'); @@ -1280,8 +1279,8 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA -----END PGP MESSAGE-----`; const plaintext = 'space: \nspace and tab: \t\nno trailing space\n \ntab:\t\ntab and space:\t '; - const sMsg = await openpgp.readArmoredMessage(msg_armor); - const pubKey = await openpgp.readArmoredKey(pub_key_arm2); + const sMsg = await openpgp.message.readArmored(msg_armor); + const pubKey = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; const keyids = sMsg.getSigningKeyIds(); @@ -1289,7 +1288,7 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA return openpgp.verify({ publicKeys: [pubKey], message: sMsg }).then(async function(cleartextSig) { expect(cleartextSig).to.exist; - expect(await openpgp.stream.readToEnd(cleartextSig.data)).to.equal(plaintext); + expect(openpgp.util.Uint8Array_to_str(openpgp.util.nativeEOL(await openpgp.stream.readToEnd(cleartextSig.data)))).to.equal(plaintext); expect(cleartextSig.signatures).to.have.length(0); }); }); @@ -1307,14 +1306,14 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA -----END PGP MESSAGE-----`.split(''); const plaintext = 'space: \nspace and tab: \t\nno trailing space\n \ntab:\t\ntab and space:\t '; - const sMsg = await openpgp.readArmoredMessage(new openpgp.stream.ReadableStream({ + const sMsg = await openpgp.message.readArmored(new ReadableStream({ async pull(controller) { await new Promise(setTimeout); controller.enqueue(msg_armor.shift()); if (!msg_armor.length) controller.close(); } })); - const pubKey = await openpgp.readArmoredKey(pub_key_arm2); + const pubKey = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; const keyids = sMsg.getSigningKeyIds(); @@ -1322,7 +1321,7 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA return openpgp.verify({ publicKeys: [pubKey], message: sMsg }).then(async function(cleartextSig) { expect(cleartextSig).to.exist; - expect(await openpgp.stream.readToEnd(cleartextSig.data)).to.equal(plaintext); + expect(openpgp.util.Uint8Array_to_str(openpgp.util.nativeEOL(await openpgp.stream.readToEnd(cleartextSig.data)))).to.equal(plaintext); expect(cleartextSig.signatures).to.have.length(1); await expect(cleartextSig.signatures[0].verified).to.be.rejectedWith('Corresponding signature packet missing'); expect((await cleartextSig.signatures[0].signature).packets.length).to.equal(0); @@ -1332,27 +1331,37 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA tests(); - let rejectMessageHashAlgorithms; + tryTests('With Worker', tests, { + if: typeof window !== 'undefined' && window.Worker, + before: async function() { + await openpgp.initWorker({ path: '../dist/openpgp.worker.js' }); + }, + after: function() { + openpgp.destroyWorker(); + } + }); + + let reject_message_hash_algorithms; tryTests('Accept SHA-1 signatures', tests, { if: true, before: function() { - ({ rejectMessageHashAlgorithms } = openpgp.config); - Object.assign(openpgp.config, { rejectMessageHashAlgorithms: new Set([openpgp.enums.hash.md5, openpgp.enums.hash.ripemd]) }); + ({ reject_message_hash_algorithms } = openpgp.config); + Object.assign(openpgp.config, { reject_message_hash_algorithms: new Set([openpgp.enums.hash.md5, openpgp.enums.hash.ripemd]) }); }, after: function() { - Object.assign(openpgp.config, { rejectMessageHashAlgorithms }); + Object.assign(openpgp.config, { reject_message_hash_algorithms }); } }); it('Sign text with openpgp.sign and verify with openpgp.verify leads to same string cleartext and valid signatures', async function() { const plaintext = 'short message\nnext line \n한국어/조선말'; - const pubKey = await openpgp.readArmoredKey(pub_key_arm2); - const privKey = await openpgp.readArmoredKey(priv_key_arm2); + const pubKey = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; + const privKey = (await openpgp.key.readArmored(priv_key_arm2)).keys[0]; await privKey.decrypt('hello world'); - return openpgp.sign({ privateKeys:[privKey], message: openpgp.CleartextMessage.fromText(plaintext) }).then(async function(signed) { + return openpgp.sign({ privateKeys:[privKey], message: openpgp.cleartext.fromText(plaintext) }).then(async function(signed) { - const csMsg = await openpgp.readArmoredCleartextMessage(signed); + const csMsg = await openpgp.cleartext.readArmored(signed.data); return openpgp.verify({ publicKeys:[pubKey], message:csMsg }); }).then(function(cleartextSig) { @@ -1366,13 +1375,13 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA it('Sign text with openpgp.sign and verify with openpgp.verify leads to same string cleartext and valid signatures -- escape armored message', async function() { const plaintext = pub_key_arm2; - const pubKey = await openpgp.readArmoredKey(pub_key_arm2); - const privKey = await openpgp.readArmoredKey(priv_key_arm2); + const pubKey = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; + const privKey = (await openpgp.key.readArmored(priv_key_arm2)).keys[0]; await privKey.decrypt('hello world'); - return openpgp.sign({ privateKeys:[privKey], message: openpgp.CleartextMessage.fromText(plaintext) }).then(async function(signed) { + return openpgp.sign({ privateKeys:[privKey], message: openpgp.cleartext.fromText(plaintext) }).then(async function(signed) { - const csMsg = await openpgp.readArmoredCleartextMessage(signed); + const csMsg = await openpgp.cleartext.readArmored(signed.data); return openpgp.verify({ publicKeys:[pubKey], message:csMsg }); }).then(function(cleartextSig) { @@ -1386,13 +1395,13 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA it('Sign text with openpgp.sign and verify with openpgp.verify leads to same string cleartext and valid signatures -- trailing spaces', async function() { const plaintext = 'space: \nspace and tab: \t\nno trailing space\n \ntab:\t\ntab and space:\t '; - const pubKey = await openpgp.readArmoredKey(pub_key_arm2); - const privKey = await openpgp.readArmoredKey(priv_key_arm2); + const pubKey = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; + const privKey = (await openpgp.key.readArmored(priv_key_arm2)).keys[0]; await privKey.decrypt('hello world'); - return openpgp.sign({ privateKeys:[privKey], message: openpgp.CleartextMessage.fromText(plaintext) }).then(async function(signed) { + return openpgp.sign({ privateKeys:[privKey], message: openpgp.cleartext.fromText(plaintext) }).then(async function(signed) { - const csMsg = await openpgp.readArmoredCleartextMessage(signed); + const csMsg = await openpgp.cleartext.readArmored(signed.data); return openpgp.verify({ publicKeys:[pubKey], message:csMsg }); }).then(function(cleartextSig) { @@ -1405,15 +1414,15 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA }); it('Sign text with openpgp.sign and verify with openpgp.verify leads to same bytes cleartext and valid signatures - armored', async function() { - const plaintext = util.strToUint8Array('short message\nnext line \n한국어/조선말'); - const pubKey = await openpgp.readArmoredKey(pub_key_arm2); - const privKey = await openpgp.readArmoredKey(priv_key_arm2); + const plaintext = openpgp.util.str_to_Uint8Array('short message\nnext line \n한국어/조선말'); + const pubKey = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; + const privKey = (await openpgp.key.readArmored(priv_key_arm2)).keys[0]; await privKey.decrypt('hello world'); - return openpgp.sign({ privateKeys:[privKey], message: openpgp.Message.fromBinary(plaintext) }).then(async function(signed) { + return openpgp.sign({ privateKeys:[privKey], message: openpgp.message.fromBinary(plaintext) }).then(async function(signed) { - const csMsg = await openpgp.readArmoredMessage(signed); - return openpgp.verify({ publicKeys:[pubKey], message:csMsg, format: 'binary' }); + const csMsg = await openpgp.message.readArmored(signed.data); + return openpgp.verify({ publicKeys:[pubKey], message:csMsg }); }).then(async function(cleartextSig) { expect(cleartextSig).to.exist; @@ -1425,15 +1434,15 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA }); it('Sign text with openpgp.sign and verify with openpgp.verify leads to same bytes cleartext and valid signatures - not armored', async function() { - const plaintext = util.strToUint8Array('short message\nnext line \n한국어/조선말'); - const pubKey = await openpgp.readArmoredKey(pub_key_arm2); - const privKey = await openpgp.readArmoredKey(priv_key_arm2); + const plaintext = openpgp.util.str_to_Uint8Array('short message\nnext line \n한국어/조선말'); + const pubKey = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; + const privKey = (await openpgp.key.readArmored(priv_key_arm2)).keys[0]; await privKey.decrypt('hello world'); - return openpgp.sign({ privateKeys:[privKey], message: openpgp.Message.fromBinary(plaintext), armor:false }).then(async function(signed) { + return openpgp.sign({ privateKeys:[privKey], message: openpgp.message.fromBinary(plaintext), armor:false }).then(function(signed) { - const csMsg = await openpgp.readMessage(signed); - return openpgp.verify({ publicKeys:[pubKey], message:csMsg, format: 'binary' }); + const csMsg = signed.message; + return openpgp.verify({ publicKeys:[pubKey], message:csMsg }); }).then(function(cleartextSig) { expect(cleartextSig).to.exist; @@ -1446,12 +1455,12 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA it('Should verify cleartext message correctly when using a detached cleartext signature and binary literal data', async function () { const plaintext = 'short message\nnext line \n한국어/조선말'; - const pubKey = await openpgp.readArmoredKey(pub_key_arm2); - const privKey = await openpgp.readArmoredKey(priv_key_arm2); + const pubKey = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; + const privKey = (await openpgp.key.readArmored(priv_key_arm2)).keys[0]; await privKey.decrypt('hello world'); - return openpgp.sign({ privateKeys:[privKey], message: openpgp.Message.fromText(plaintext), detached: true}).then(async function(signed) { - const signature = await openpgp.readArmoredSignature(signed); - return openpgp.verify({ publicKeys:[pubKey], message: openpgp.Message.fromBinary(util.encodeUtf8(plaintext)), signature: signature }); + return openpgp.sign({ privateKeys:[privKey], message: openpgp.message.fromText(plaintext), detached: true}).then(async function(signed) { + const signature = await openpgp.signature.readArmored(signed.signature); + return openpgp.verify({ publicKeys:[pubKey], message: openpgp.message.fromBinary(openpgp.util.encode_utf8(plaintext)), signature: signature }); }).then(function(cleartextSig) { expect(cleartextSig).to.exist; expect(cleartextSig.signatures).to.have.length(1); @@ -1462,13 +1471,13 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA it('Should verify cleartext message correctly when using a detached binary signature and text literal data', async function () { const plaintext = 'short message\nnext line \n한국어/조선말'; - const plaintextArray = util.encodeUtf8(plaintext); - const pubKey = await openpgp.readArmoredKey(pub_key_arm2); - const privKey = await openpgp.readArmoredKey(priv_key_arm2); + const plaintextArray = openpgp.util.encode_utf8(plaintext); + const pubKey = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; + const privKey = (await openpgp.key.readArmored(priv_key_arm2)).keys[0]; await privKey.decrypt('hello world'); - return openpgp.sign({ privateKeys:[privKey], message:openpgp.Message.fromBinary(plaintextArray), detached: true}).then(async function(signed) { - const signature = await openpgp.readArmoredSignature(signed); - return openpgp.verify({ publicKeys:[pubKey], message: openpgp.Message.fromText(plaintext), signature: signature }); + return openpgp.sign({ privateKeys:[privKey], message:openpgp.message.fromBinary(plaintextArray), detached: true}).then(async function(signed) { + const signature = await openpgp.signature.readArmored(signed.signature); + return openpgp.verify({ publicKeys:[pubKey], message: openpgp.message.fromText(plaintext), signature: signature }); }).then(function(cleartextSig) { expect(cleartextSig).to.exist; expect(cleartextSig.signatures).to.have.length(1); @@ -1479,14 +1488,14 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA it('Should verify encrypted cleartext message correctly when encrypting binary literal data with a canonical text signature', async function () { const plaintext = 'short message\nnext line \n한국어/조선말'; - const pubKey = await openpgp.readArmoredKey(pub_key_arm2); - const privKey = await openpgp.readArmoredKey(priv_key_arm2); + const pubKey = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; + const privKey = (await openpgp.key.readArmored(priv_key_arm2)).keys[0]; await Promise.all([privKey.primaryKey.decrypt('hello world'), privKey.subKeys[0].keyPacket.decrypt('hello world')]); - return openpgp.sign({ privateKeys:[privKey], message: openpgp.Message.fromText(plaintext), detached: true}).then(async function(signed) { - const signature = await openpgp.readArmoredSignature(signed); - return openpgp.encrypt({ message: openpgp.Message.fromBinary(util.encodeUtf8(plaintext)), publicKeys: [pubKey], signature }) - }).then(async data => { - const csMsg = await openpgp.readArmoredMessage(data); + return openpgp.sign({ privateKeys:[privKey], message: openpgp.message.fromText(plaintext), detached: true}).then(async function(signed) { + const signature = await openpgp.signature.readArmored(signed.signature); + return openpgp.encrypt({ message: openpgp.message.fromBinary(openpgp.util.encode_utf8(plaintext)), publicKeys: [pubKey], signature }) + }).then(async ({ data }) => { + const csMsg = await openpgp.message.readArmored(data); return openpgp.decrypt({ message: csMsg, privateKeys: [ privKey ], publicKeys: [ pubKey ] }); }).then(function(cleartextSig) { expect(cleartextSig).to.exist; @@ -1497,8 +1506,8 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA }); it('Verify test with expired verification public key', async function() { - const pubKey = await openpgp.readArmoredKey(pub_expired); - const message = await openpgp.readArmoredMessage(msg_sig_expired); + const pubKey = (await openpgp.key.readArmored(pub_expired)).keys[0]; + const message = await openpgp.message.readArmored(msg_sig_expired); return openpgp.verify({ publicKeys:[pubKey], message:message }).then(function(verified) { expect(verified).to.exist; expect(verified.signatures).to.have.length(1); @@ -1508,8 +1517,8 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA }); it('Verify test with expired verification public key and disable expiration checks using null date', async function() { - const pubKey = await openpgp.readArmoredKey(pub_expired); - const message = await openpgp.readArmoredMessage(msg_sig_expired); + const pubKey = (await openpgp.key.readArmored(pub_expired)).keys[0]; + const message = await openpgp.message.readArmored(msg_sig_expired); return openpgp.verify({ publicKeys:[pubKey], message:message, date: null }).then(function(verified) { expect(verified).to.exist; expect(verified.signatures).to.have.length(1); @@ -1520,26 +1529,22 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA // TODO add test with multiple revocation signatures it('Verify primary key revocation signatures', async function() { - const pubKey = await openpgp.readArmoredKey(pub_revoked); - const revSig = pubKey.revocationSignatures[0]; - revSig.verified = null; - await pubKey.revocationSignatures[0].verify( - pubKey.primaryKey, openpgp.enums.signature.keyRevocation, { key: pubKey.primaryKey } - ).then(() => expect(revSig.verified).to.be.true); + const pubKey = (await openpgp.key.readArmored(pub_revoked)).keys[0]; + await expect(pubKey.revocationSignatures[0].verify( + pubKey.primaryKey, openpgp.enums.signature.key_revocation, {key: pubKey.primaryKey} + )).to.eventually.be.true; }); // TODO add test with multiple revocation signatures it('Verify subkey revocation signatures', async function() { - const pubKey = await openpgp.readArmoredKey(pub_revoked); - const revSig = pubKey.subKeys[0].revocationSignatures[0]; - revSig.verified = null; - await revSig.verify( - pubKey.primaryKey, openpgp.enums.signature.subkeyRevocation, { key: pubKey.primaryKey, bind: pubKey.subKeys[0].keyPacket } - ).then(() => expect(revSig.verified).to.be.true); + const pubKey = (await openpgp.key.readArmored(pub_revoked)).keys[0]; + await expect(pubKey.subKeys[0].revocationSignatures[0].verify( + pubKey.primaryKey, openpgp.enums.signature.subkey_revocation, {key: pubKey.primaryKey, bind: pubKey.subKeys[0].keyPacket} + )).to.eventually.be.true; }); it('Verify key expiration date', async function() { - const pubKey = await openpgp.readArmoredKey(pub_revoked); + const pubKey = (await openpgp.key.readArmored(pub_revoked)).keys[0]; expect(pubKey).to.exist; expect(pubKey.users[0].selfCertifications[0].keyNeverExpires).to.be.false; @@ -1547,15 +1552,15 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA }); it('Write unhashed subpackets', async function() { - let pubKey = await openpgp.readArmoredKey(pub_key_arm2); + let pubKey = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; expect(pubKey.users[0].selfCertifications).to.exist; - pubKey = await openpgp.readArmoredKey(pubKey.armor()); + pubKey = (await openpgp.key.readArmored(pubKey.armor())).keys[0]; expect(pubKey.users[0].selfCertifications).to.exist; }); it('Write V4 signatures', async function() { - const pubKey = await openpgp.readArmoredKey(pub_key_arm2); - const pubKey2 = await openpgp.readArmoredKey(pubKey.armor()); + const pubKey = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; + const pubKey2 = (await openpgp.key.readArmored(pubKey.armor())).keys[0]; expect(pubKey2).to.exist; expect(pubKey.users[0].selfCertifications).to.eql(pubKey2.users[0].selfCertifications); }); @@ -1597,37 +1602,42 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA ''].join('\r\n'); const publicKeyArmored = '-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nVersion: OpenPGP.js v.1.20131116\r\nComment: Whiteout Mail - https://whiteout.io\r\n\r\nxsBNBFKODs4BB/9iOF4THsjQMY+WEpT7ShgKxj4bHzRRaQkqczS4nZvP0U3g\r\nqeqCnbpagyeKXA+bhWFQW4GmXtgAoeD5PXs6AZYrw3tWNxLKu2Oe6Tp9K/XI\r\nxTMQ2wl4qZKDXHvuPsJ7cmgaWqpPyXtxA4zHHS3WrkI/6VzHAcI/y6x4szSB\r\nKgSuhI3hjh3s7TybUC1U6AfoQGx/S7e3WwlCOrK8GTClirN/2mCPRC5wuIft\r\nnkoMfA6jK8d2OPrJ63shy5cgwHOjQg/xuk46dNS7tkvGmbaa+X0PgqSKB+Hf\r\nYPPNS/ylg911DH9qa8BqYU2QpNh9jUKXSF+HbaOM+plWkCSAL7czV+R3ABEB\r\nAAHNLVdoaXRlb3V0IFVzZXIgPHNhZmV3aXRobWUudGVzdHVzZXJAZ21haWwu\r\nY29tPsLAXAQQAQgAEAUCUo4O2gkQ1/uT/N+/wjwAAN2cB/9gFRmAfvEQ2qz+\r\nWubmT2EsSSnjPMxzG4uyykFoa+TaZCWo2Xa2tQghmU103kEkQb1OEjRjpgwJ\r\nYX9Kghnl8DByM686L5AXnRyHP78qRJCLXSXl0AGicboUDp5sovaa4rswQceH\r\nvcdWgZ/mgHTRoiQeJddy9k+H6MPFiyFaVcFwegVsmpc+dCcC8yT+qh8ZIbyG\r\nRJU60PmKKN7LUusP+8DbSv39zCGJCBlVVKyA4MzdF5uM+sqTdXbKzOrT5DGd\r\nCZaox4s+w16Sq1rHzZKFWfQPfKLDB9pyA0ufCVRA3AF6BUi7G3ZqhZiHNhMP\r\nNvE45V/hS1PbZcfPVoUjE2qc1Ix1\r\n=7Wpe\r\n-----END PGP PUBLIC KEY BLOCK-----'; - const publicKey = await openpgp.readArmoredKey(publicKeyArmored); + const publicKeys = (await openpgp.key.readArmored(publicKeyArmored)).keys; // Text - const msg = openpgp.Message.fromText(content); + const msg = openpgp.message.fromText(content); await msg.appendSignature(detachedSig); - return msg.verify([publicKey]).then(async result => { - openpgp.stream.pipe(msg.getLiteralData(), new openpgp.stream.WritableStream()); + return msg.verify(publicKeys).then(async result => { + openpgp.stream.pipe(msg.getLiteralData(), new WritableStream()); expect(await result[0].verified).to.be.true; }); }); it('Detached signature signing and verification', async function() { - const msg = openpgp.Message.fromText('hello'); - const pubKey2 = await openpgp.readArmoredKey(pub_key_arm2); - const privKey2 = await openpgp.readArmoredKey(priv_key_arm2); + const msg = openpgp.message.fromText('hello'); + const pubKey2 = (await openpgp.key.readArmored(pub_key_arm2)).keys[0]; + const privKey2 = (await openpgp.key.readArmored(priv_key_arm2)).keys[0]; await privKey2.decrypt('hello world'); - const opt = { rsaBits: 512, userIds: { name:'test', email:'a@b.com' }, passphrase: null }; - if (util.getWebCryptoAll()) { opt.rsaBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys - const { key: generatedKey } = await openpgp.generateKey(opt); - const detachedSig = await msg.signDetached([generatedKey, privKey2]); - const result = await msg.verifyDetached(detachedSig, [generatedKey.toPublic(), pubKey2]); - expect(await result[0].verified).to.be.true; - expect(await result[1].verified).to.be.true; + const opt = {numBits: 512, userIds: { name:'test', email:'a@b.com' }, passphrase: null}; + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys + return openpgp.generateKey(opt).then(function(gen) { + const generatedKey = gen.key; + return msg.signDetached([generatedKey, privKey2]).then(detachedSig => { + return msg.verifyDetached(detachedSig, [generatedKey.toPublic(), pubKey2]).then(async result => { + expect(await result[0].verified).to.be.true; + expect(await result[1].verified).to.be.true; + }); + }); + }); }); it('Sign message with key without password', function() { - const opt = { userIds: { name:'test', email:'a@b.com' }, passphrase: null }; + const opt = {numBits: 512, userIds: { name:'test', email:'a@b.com' }, passphrase: null}; + if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys return openpgp.generateKey(opt).then(function(gen) { const key = gen.key; - let message = openpgp.Message.fromText('hello world'); + let message = openpgp.message.fromText('hello world'); message = message.sign([key]); expect(message).to.exist; }); @@ -1661,8 +1671,8 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA '-----END PGP PUBLIC KEY BLOCK-----' ].join('\n'); - const signedKey = await openpgp.readArmoredKey(signedArmor); - const signerKey = await openpgp.readArmoredKey(priv_key_arm1); + const signedKey = (await openpgp.key.readArmored(signedArmor)).keys[0]; + const signerKey = (await openpgp.key.readArmored(priv_key_arm1)).keys[0]; return signedKey.verifyPrimaryUser([signerKey]).then(signatures => { expect(signatures[0].valid).to.be.null; expect(signatures[0].keyid.toHex()).to.equal(signedKey.getKeyId().toHex()); @@ -1696,141 +1706,10 @@ iTuGu4fEU1UligAXSrZmCdE= =VK6I -----END PGP PUBLIC KEY BLOCK-----`; - const key = await openpgp.readArmoredKey(armoredKeyWithPhoto); + const key = (await openpgp.key.readArmored(armoredKeyWithPhoto)).keys[0]; for (const user of key.users) { await user.verify(key.primaryKey); } }); - it('should verify a shorter RSA signature', async function () { - const encrypted = `-----BEGIN PGP MESSAGE----- - -wYwD4IT3RGwgLJcBBACmH+a2c2yieZJ3wFchKeTVqzWkoltiidWgHHNE5v5x -8aZGNzZFBd02v80VS23P9oxeJOpqKX2IZyuD36SniNoi+eXdT3zraqIe9x5p -0RY9OrTP9pl58iogFBi1ARls41j7ui8KKDt2/iyQDCWHW1LoOVstiEb5/Xi3 -EWI+34EbNNTBMgEJAQAwEXImkOPmhYhE7bB3FnXe9rb7Fo3GZYA4/8B9YVf7 -GGZRLGwbICGu8E0MolmzLYW9hRThEfusAsNPGSgB+Yaqp0drsk01N4JJj3FT -RKEUvd5EcL3u+Z5EoUUW6GpUL5p8Hvy2thqQfeem7XUbDBY6V3wqydOjbN9u -c4CWB5Zu3GjDGDOvXFsy6cgdQvd/B9xbugKvUbAIsecTPlLtjZwfQklIu63T -DA/3Pz/+zTAknBCsuIM0m7U/ZP3N6AGQIp4To7RJk0I6AxthHF5LbU11MjDZ -iB7+vmhqlrPyIS11g25UNijottJm13f84glVwBdWTJCiEqjh3KbcnTQCckCY -V39DDLtbZG/XIx1ktqp765O9D/9xp2IA4zTyZzH4TuDbYs1j+JRdMsAq254k -1m+wtW5gxJGcD5nh2T2T+ABL0n3jW0G504kR0LNBAQOZhVSKnSLn+F0GkjmI -iGw8+BOy8p2pX/WCLOf776ppSL77TpzhpG6wSE2oQxDrudazmRgVkZpyGzFE -fDjspLTJHOhZ5zlLuoiKS9qEARGp39ysQnElR4dsx7tyVZz0uJvIrVzrQBlB -ekoD0DH0bhfqiwDrqeTJT2ORk8I/Q3jWnhQ3MnRN+q9d0yf1LWApMCwA7xU2 -C4KUFRC/wuF2TR9NvA== -=v3WS ------END PGP MESSAGE-----`; - const armoredKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- - -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 -oaBUyhCKt8tz6Q== -=52k1 ------END PGP PRIVATE KEY BLOCK-----`; - const key = await openpgp.readArmoredKey(armoredKey); - const decrypted = await openpgp.decrypt({ - message: await openpgp.readArmoredMessage(encrypted), - publicKeys: key, - privateKeys: key - }); - expect(decrypted.signatures[0].valid).to.be.true; - }); - - it('should verify a shorter EdDSA signature', async function() { - const key = await openpgp.readArmoredKey(`-----BEGIN PGP PRIVATE KEY BLOCK----- - -xVgEX8+jfBYJKwYBBAHaRw8BAQdA9GbdDjprR0sWf0R5a5IpulUauc0FsmzJ -mOYCfoowt8EAAP9UwaqC0LWWQ5RlX7mps3728vFa/If1KBVwAjk7Uqhi2BKL -zQ90ZXN0MiA8YkBhLmNvbT7CjAQQFgoAHQUCX8+jfAQLCQcIAxUICgQWAgEA -AhkBAhsDAh4BACEJEG464aV2od77FiEEIcg441MtKnyJnPDRbjrhpXah3vuR -gQD+Il6Gw2oIok4/ANyDDLBYZtKqRrMv4NcfF9DHYuAFcP4BAPhFOffyP3qU -AEZb7QPrWdLfhn8/FeSFZxJvnmupQ9sDx10EX8+jfBIKKwYBBAGXVQEFAQEH -QOSzo9cX1U2esGFClprOt0QWXNJ97228R5tKFxo6/0NoAwEIBwAA/0n4sq2i -N6/jE+6rVO4o/7LW0xahxpV1tTA6qv1Op9TwFIDCeAQYFggACQUCX8+jfAIb -DAAhCRBuOuGldqHe+xYhBCHIOONTLSp8iZzw0W464aV2od773XcA/jlmX8/c -1/zIotEkyMZB4mI+GAg3FQ6bIACFBH1sz0MzAP9Snri0P4FRZ8D5THRCJoUm -GBgpBmrf6IVv484jBswGDA== -=8rBO ------END PGP PRIVATE KEY BLOCK-----`); - const encrypted = `-----BEGIN PGP MESSAGE----- - -wV4DWlRRjuYiLSsSAQdAWwDKQLN4ZUS5fqiwFtAMrRfZZe9J4SgClhG6avEe -AEowkSZwWRT+8Hy8aBIb4oPehYUFXXZ7BtlJCyd7LOTUtqyc00OE0721PC3M -v0+zird60sACATlDmTwweR5GFtEAjHVheIL5rbkOBRD+oSqB8z+IovNg83Pz -FVwsFZnCLtECoYgpF2MJdopuC/bPHcrvf4ndwmD11uXtms4Rq4y25QyqApbn -Hj/hljufk0OkavUXxrNKjGQtxLHMpa3Nsi0MHWY8JguxOKFKpAIMP32CD1e+ -j+GItrR+QbbN13ODlcR3hf66cwjLLsJCx5VcBaRspKF05O3ix/u9KVjJqtbi -Ie6jnY0zP2ldtS4JmhKBa43qmOHCxHc= -=7B58 ------END PGP MESSAGE-----`; - const decrypted = await openpgp.decrypt({ message: await openpgp.readArmoredMessage(encrypted), privateKeys: key, publicKeys: key.toPublic() }); - expect(decrypted.signatures[0].valid).to.be.true; - }); - - it('should verify a shorter ECDSA signature', async function() { - const key = await openpgp.readArmoredKey(`-----BEGIN PGP PRIVATE KEY BLOCK----- - -xYAFX9JrLRMAAABMCCqGSM49AwEHAgMErtQdX4vh7ng/ut+k1mooYNh3Ywqt -wr0tSS8hxZMvQRIFQ53Weq0e97ioZKXGimprEL571yvAN7I19wtQtqi61AAA -AAAAJAEAjWdW+qlMFaKwXCls3O/X8I1rbZ0OdFgeE3TnRP3YETAP5s0KYSA8 -YUBhLml0PsKSBRATCAAhBQJf0mstBAsJBwgDFQgKBBYCAQACGQECGwMCHgcD -IgECACMiIQUee6Tb+GlhTk/ozKrt7RhInCyR6w3OJb/tYAN1+qbIoYUqAP9S -XmJCmSMrq6KfAD1aWSTBhtmujh+6y/pYTaf6VJVBYQEAt18zK0tw5EihHASY -FXbfdFHBzrMmPJ4UV6UiBvH6k2zHhAVf0mstEgAAAFAIKoZIzj0DAQcCAwQx -qnVPmWex365Nx8X8BGuMNI2TITXzTh9+AuPftZjPm09dhxdT9xmrCstPu/U1 -cpacIp0LIq13ngLgeZWcGFcnAwEIBwAAAAAAJAEAsTvBsKk/XoCz2mi8sz5q -EYaN9YdDOU2jF+HOaSNaJAsPF8J6BRgTCAAJBQJf0mstAhsMACMiIQUee6Tb -+GlhTk/ozKrt7RhInCyR6w3OJb/tYAN1+qbIoVutAP9GHPLn7D9Uahm81lhK -AcvDfr9a0Cp4WAVzKDKLUzrRMgEAozi0VyjiBo1U2LcwTPJkA4PEQqQRVW1D -KZTMSAH7JEo= -=tqWy ------END PGP PRIVATE KEY BLOCK-----`); - const signed = `-----BEGIN PGP SIGNED MESSAGE----- -Hash: SHA256 - -short message ------BEGIN PGP SIGNATURE----- - -wnYFARMIAAYFAl/Say0AIyIhBR57pNv4aWFOT+jMqu3tGEicLJHrDc4lv+1g -A3X6psihFkcA+Nuog2qpAq20Zc2lzVjDZzQosb8MLvKMg3UFCX12Oc0BAJwd -JImeZLY02MctIpGZULbqgcUGK0P/yqrPL8Pe4lQM -=Pacb ------END PGP SIGNATURE-----`; - const message = await openpgp.readArmoredCleartextMessage(signed); - const verified = await openpgp.verify({ publicKeys: key, message }); - expect(verified.signatures[0].valid).to.be.true; - }); }); diff --git a/test/general/streaming.js b/test/general/streaming.js index 4b30319c..05ed2ed8 100644 --- a/test/general/streaming.js +++ b/test/general/streaming.js @@ -1,6 +1,4 @@ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); -const random = require('../../src/crypto/random'); -const util = require('../../src/util'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); const stub = require('sinon/lib/sinon/stub'); const chai = require('chai'); @@ -9,10 +7,7 @@ const input = require('./testInputs.js'); const { expect } = chai; -const { stream } = openpgp; - -const useNativeStream = (() => { try { new global.ReadableStream(); return true; } catch (e) { return false; } })(); -const ReadableStream = useNativeStream ? global.ReadableStream : openpgp.stream.ReadableStream; +const { stream, util } = openpgp; const pub_key = ['-----BEGIN PGP PUBLIC KEY BLOCK-----', @@ -172,17 +167,17 @@ function tests() { dataArrived(); // Do not wait until data arrived. const data = new ReadableStream({ async start(controller) { - controller.enqueue(util.strToUint8Array('hello ')); - controller.enqueue(util.strToUint8Array('world')); + controller.enqueue(util.str_to_Uint8Array('hello ')); + controller.enqueue(util.str_to_Uint8Array('world')); controller.close(); } }); const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromBinary(data), + message: openpgp.message.fromBinary(data), passwords: ['test'], }); - const msgAsciiArmored = await openpgp.stream.readToEnd(encrypted); - const message = await openpgp.readArmoredMessage(msgAsciiArmored); + const msgAsciiArmored = await openpgp.stream.readToEnd(encrypted.data); + const message = await openpgp.message.readArmored(msgAsciiArmored); const decrypted = await openpgp.decrypt({ passwords: ['test'], message @@ -192,15 +187,15 @@ function tests() { it('Encrypt larger message', async function() { const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromBinary(data), + message: openpgp.message.fromBinary(data), passwords: ['test'], }); - const reader = openpgp.stream.getReader(encrypted); - expect(await reader.peekBytes(1024)).to.match(/^-----BEGIN PGP MESSAGE-----\n/); + const reader = openpgp.stream.getReader(encrypted.data); + expect(await reader.peekBytes(1024)).to.match(/^-----BEGIN PGP MESSAGE-----\r\n/); dataArrived(); reader.releaseLock(); - const msgAsciiArmored = await openpgp.stream.readToEnd(encrypted); - const message = await openpgp.readArmoredMessage(msgAsciiArmored); + const msgAsciiArmored = await openpgp.stream.readToEnd(encrypted.data); + const message = await openpgp.message.readArmored(msgAsciiArmored); const decrypted = await openpgp.decrypt({ passwords: ['test'], message, @@ -211,196 +206,183 @@ function tests() { it('Input stream should be canceled when canceling encrypted stream', async function() { const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromBinary(data), + message: openpgp.message.fromBinary(data), passwords: ['test'], }); - const reader = openpgp.stream.getReader(encrypted); - expect(await reader.readBytes(1024)).to.match(/^-----BEGIN PGP MESSAGE-----\n/); + const reader = openpgp.stream.getReader(encrypted.data); + expect(await reader.readBytes(1024)).to.match(/^-----BEGIN PGP MESSAGE-----\r\n/); dataArrived(); reader.releaseLock(); - await openpgp.stream.cancel(encrypted); + await openpgp.stream.cancel(encrypted.data); expect(canceled).to.be.true; }); it('Sign: Input stream should be canceled when canceling encrypted stream', async function() { const signed = await openpgp.sign({ - message: openpgp.Message.fromBinary(data), + message: openpgp.message.fromBinary(data), privateKeys: privKey }); - const reader = openpgp.stream.getReader(signed); - expect(await reader.readBytes(1024)).to.match(/^-----BEGIN PGP MESSAGE-----\n/); + const reader = openpgp.stream.getReader(signed.data); + expect(await reader.readBytes(1024)).to.match(/^-----BEGIN PGP MESSAGE-----\r\n/); dataArrived(); reader.releaseLock(); - await openpgp.stream.cancel(signed); + await openpgp.stream.cancel(signed.data); expect(canceled).to.be.true; }); it('Encrypt and decrypt larger message roundtrip', async function() { - let aeadProtectValue = openpgp.config.aeadProtect; - openpgp.config.aeadProtect = false; const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromBinary(data), + message: openpgp.message.fromBinary(data), passwords: ['test'], - armor: false }); - expect(openpgp.stream.isStream(encrypted)).to.equal(expectedType); - const message = await openpgp.readMessage(encrypted); + const msgAsciiArmored = encrypted.data; + const message = await openpgp.message.readArmored(msgAsciiArmored); setTimeout(dataArrived, 3000); // Do not wait until data arrived, but wait a bit to check that it doesn't arrive early. const decrypted = await openpgp.decrypt({ passwords: ['test'], message, format: 'binary' }); - expect(openpgp.stream.isStream(decrypted.data)).to.equal(expectedType); + expect(util.isStream(decrypted.data)).to.equal(expectedType); const reader = openpgp.stream.getReader(decrypted.data); expect(await reader.peekBytes(1024)).to.deep.equal(plaintext[0]); if (i <= 10) throw new Error('Data arrived early.'); expect(await reader.readToEnd()).to.deep.equal(util.concatUint8Array(plaintext)); - openpgp.config.aeadProtect = aeadProtectValue; }); - it('Encrypt and decrypt larger message roundtrip (allowUnauthenticatedStream=true)', async function() { - let aeadProtectValue = openpgp.config.aeadProtect; - let allowUnauthenticatedStreamValue = openpgp.config.allowUnauthenticatedStream; - openpgp.config.aeadProtect = false; - openpgp.config.allowUnauthenticatedStream = true; + it('Encrypt and decrypt larger message roundtrip (allow_unauthenticated_stream=true)', async function() { + let allow_unauthenticated_streamValue = openpgp.config.allow_unauthenticated_stream; + openpgp.config.allow_unauthenticated_stream = true; try { const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromBinary(data), + message: openpgp.message.fromBinary(data), passwords: ['test'], - armor: false }); - expect(openpgp.stream.isStream(encrypted)).to.equal(expectedType); - const message = await openpgp.readMessage(encrypted); + const msgAsciiArmored = encrypted.data; + const message = await openpgp.message.readArmored(msgAsciiArmored); const decrypted = await openpgp.decrypt({ passwords: ['test'], message, format: 'binary' }); - expect(openpgp.stream.isStream(decrypted.data)).to.equal(expectedType); - expect(openpgp.stream.isStream(decrypted.signatures)).to.be.false; + expect(util.isStream(decrypted.data)).to.equal(expectedType); + expect(util.isStream(decrypted.signatures)).to.be.false; const reader = openpgp.stream.getReader(decrypted.data); expect(await reader.peekBytes(1024)).to.deep.equal(plaintext[0]); dataArrived(); expect(await reader.readToEnd()).to.deep.equal(util.concatUint8Array(plaintext)); expect(decrypted.signatures).to.exist.and.have.length(0); } finally { - openpgp.config.aeadProtect = aeadProtectValue; - openpgp.config.allowUnauthenticatedStream = allowUnauthenticatedStreamValue; + openpgp.config.allow_unauthenticated_stream = allow_unauthenticated_streamValue; } }); - it('Encrypt and decrypt larger message roundtrip using public keys (allowUnauthenticatedStream=true)', async function() { - let allowUnauthenticatedStreamValue = openpgp.config.allowUnauthenticatedStream; - openpgp.config.allowUnauthenticatedStream = true; + it('Encrypt and decrypt larger message roundtrip using public keys (allow_unauthenticated_stream=true)', async function() { + let allow_unauthenticated_streamValue = openpgp.config.allow_unauthenticated_stream; + openpgp.config.allow_unauthenticated_stream = true; try { const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromBinary(data), + message: openpgp.message.fromBinary(data), publicKeys: pubKey, - privateKeys: privKey, - armor: false + privateKeys: privKey }); - expect(openpgp.stream.isStream(encrypted)).to.equal(expectedType); - const message = await openpgp.readMessage(encrypted); + const msgAsciiArmored = encrypted.data; + const message = await openpgp.message.readArmored(msgAsciiArmored); const decrypted = await openpgp.decrypt({ publicKeys: pubKey, privateKeys: privKey, message, format: 'binary' }); - expect(openpgp.stream.isStream(decrypted.data)).to.equal(expectedType); + expect(util.isStream(decrypted.data)).to.equal(expectedType); const reader = openpgp.stream.getReader(decrypted.data); expect(await reader.peekBytes(1024)).to.deep.equal(plaintext[0]); dataArrived(); expect(await reader.readToEnd()).to.deep.equal(util.concatUint8Array(plaintext)); } finally { - openpgp.config.allowUnauthenticatedStream = allowUnauthenticatedStreamValue; + openpgp.config.allow_unauthenticated_stream = allow_unauthenticated_streamValue; } }); - it('Encrypt and decrypt larger message roundtrip using curve x25519 (allowUnauthenticatedStream=true)', async function() { - let allowUnauthenticatedStreamValue = openpgp.config.allowUnauthenticatedStream; - openpgp.config.allowUnauthenticatedStream = true; - const priv = await openpgp.readArmoredKey(xPriv); - const pub = await openpgp.readArmoredKey(xPub); + it('Encrypt and decrypt larger message roundtrip using curve x25519 (allow_unauthenticated_stream=true)', async function() { + let allow_unauthenticated_streamValue = openpgp.config.allow_unauthenticated_stream; + openpgp.config.allow_unauthenticated_stream = true; + const priv = (await openpgp.key.readArmored(xPriv)).keys[0]; + const pub = (await openpgp.key.readArmored(xPub)).keys[0]; await priv.decrypt(xPass); try { const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromBinary(data), + message: openpgp.message.fromBinary(data), publicKeys: pub, - privateKeys: priv, - armor: false + privateKeys: priv }); - expect(openpgp.stream.isStream(encrypted)).to.equal(expectedType); - const message = await openpgp.readMessage(encrypted); + const msgAsciiArmored = encrypted.data; + const message = await openpgp.message.readArmored(msgAsciiArmored); const decrypted = await openpgp.decrypt({ publicKeys: pub, privateKeys: priv, message, format: 'binary' }); - expect(openpgp.stream.isStream(decrypted.data)).to.equal(expectedType); + expect(util.isStream(decrypted.data)).to.equal(expectedType); const reader = openpgp.stream.getReader(decrypted.data); expect(await reader.peekBytes(1024)).to.deep.equal(plaintext[0]); dataArrived(); expect(await reader.readToEnd()).to.deep.equal(util.concatUint8Array(plaintext)); } finally { - openpgp.config.allowUnauthenticatedStream = allowUnauthenticatedStreamValue; + openpgp.config.allow_unauthenticated_stream = allow_unauthenticated_streamValue; } }); - it('Encrypt and decrypt larger message roundtrip using curve brainpool (allowUnauthenticatedStream=true)', async function() { - let allowUnauthenticatedStreamValue = openpgp.config.allowUnauthenticatedStream; - openpgp.config.allowUnauthenticatedStream = true; - const priv = await openpgp.readArmoredKey(brainpoolPriv); - const pub = await openpgp.readArmoredKey(brainpoolPub); + it('Encrypt and decrypt larger message roundtrip using curve brainpool (allow_unauthenticated_stream=true)', async function() { + let allow_unauthenticated_streamValue = openpgp.config.allow_unauthenticated_stream; + openpgp.config.allow_unauthenticated_stream = true; + const priv = (await openpgp.key.readArmored(brainpoolPriv)).keys[0]; + const pub = (await openpgp.key.readArmored(brainpoolPub)).keys[0]; await priv.decrypt(brainpoolPass); try { const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromBinary(data), + message: openpgp.message.fromBinary(data), publicKeys: pub, - privateKeys: priv, - armor: false + privateKeys: priv }); - expect(openpgp.stream.isStream(encrypted)).to.equal(expectedType); - const message = await openpgp.readMessage(encrypted); + const msgAsciiArmored = encrypted.data; + const message = await openpgp.message.readArmored(msgAsciiArmored); const decrypted = await openpgp.decrypt({ publicKeys: pub, privateKeys: priv, message, format: 'binary' }); - expect(openpgp.stream.isStream(decrypted.data)).to.equal(expectedType); + expect(util.isStream(decrypted.data)).to.equal(expectedType); const reader = openpgp.stream.getReader(decrypted.data); expect(await reader.peekBytes(1024)).to.deep.equal(plaintext[0]); dataArrived(); expect(await reader.readToEnd()).to.deep.equal(util.concatUint8Array(plaintext)); } finally { - openpgp.config.allowUnauthenticatedStream = allowUnauthenticatedStreamValue; + openpgp.config.allow_unauthenticated_stream = allow_unauthenticated_streamValue; } }); - it('Detect MDC modifications (allowUnauthenticatedStream=true)', async function() { - let aeadProtectValue = openpgp.config.aeadProtect; - openpgp.config.aeadProtect = false; - let allowUnauthenticatedStreamValue = openpgp.config.allowUnauthenticatedStream; - openpgp.config.allowUnauthenticatedStream = true; + it('Detect MDC modifications (allow_unauthenticated_stream=true)', async function() { + let allow_unauthenticated_streamValue = openpgp.config.allow_unauthenticated_stream; + openpgp.config.allow_unauthenticated_stream = true; try { const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromBinary(data), - passwords: ['test'] + message: openpgp.message.fromBinary(data), + passwords: ['test'], }); - expect(openpgp.stream.isStream(encrypted)).to.equal(expectedType); - const message = await openpgp.readArmoredMessage(openpgp.stream.transform(encrypted, value => { + const msgAsciiArmored = encrypted.data; + const message = await openpgp.message.readArmored(openpgp.stream.transform(msgAsciiArmored, value => { value += ''; if (value === '=' || value.length === 6) return; // Remove checksum - const newlineIndex = value.indexOf('\n', 500); + const newlineIndex = value.indexOf('\r\n', 500); if (value.length > 1000) return value.slice(0, newlineIndex - 1) + (value[newlineIndex - 1] === 'a' ? 'b' : 'a') + value.slice(newlineIndex); return value; })); @@ -410,32 +392,31 @@ function tests() { streaming: expectedType, format: 'binary' }); - expect(openpgp.stream.isStream(decrypted.data)).to.equal(expectedType); + expect(util.isStream(decrypted.data)).to.equal(expectedType); const reader = openpgp.stream.getReader(decrypted.data); expect(await reader.peekBytes(1024)).not.to.deep.equal(plaintext[0]); dataArrived(); await expect(reader.readToEnd()).to.be.rejectedWith('Modification detected.'); expect(decrypted.signatures).to.exist.and.have.length(0); } finally { - openpgp.config.aeadProtect = aeadProtectValue; - openpgp.config.allowUnauthenticatedStream = allowUnauthenticatedStreamValue; + openpgp.config.allow_unauthenticated_stream = allow_unauthenticated_streamValue; } }); - it('Detect armor checksum error (allowUnauthenticatedStream=true)', async function() { - let allowUnauthenticatedStreamValue = openpgp.config.allowUnauthenticatedStream; - openpgp.config.allowUnauthenticatedStream = true; + it('Detect armor checksum error (allow_unauthenticated_stream=true)', async function() { + let allow_unauthenticated_streamValue = openpgp.config.allow_unauthenticated_stream; + openpgp.config.allow_unauthenticated_stream = true; try { const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromBinary(data), + message: openpgp.message.fromBinary(data), publicKeys: pubKey, privateKeys: privKey }); - expect(openpgp.stream.isStream(encrypted)).to.equal(expectedType); - const message = await openpgp.readArmoredMessage(openpgp.stream.transform(encrypted, value => { + const msgAsciiArmored = encrypted.data; + const message = await openpgp.message.readArmored(openpgp.stream.transform(msgAsciiArmored, value => { value += ''; - const newlineIndex = value.indexOf('\n', 500); + const newlineIndex = value.indexOf('\r\n', 500); if (value.length > 1000) return value.slice(0, newlineIndex - 1) + (value[newlineIndex - 1] === 'a' ? 'b' : 'a') + value.slice(newlineIndex); return value; })); @@ -446,31 +427,31 @@ function tests() { streaming: expectedType, format: 'binary' }); - expect(openpgp.stream.isStream(decrypted.data)).to.equal(expectedType); + expect(util.isStream(decrypted.data)).to.equal(expectedType); const reader = openpgp.stream.getReader(decrypted.data); expect(await reader.peekBytes(1024)).not.to.deep.equal(plaintext[0]); dataArrived(); await expect(reader.readToEnd()).to.be.rejectedWith('Ascii armor integrity check on message failed'); expect(decrypted.signatures).to.exist.and.have.length(1); } finally { - openpgp.config.allowUnauthenticatedStream = allowUnauthenticatedStreamValue; + openpgp.config.allow_unauthenticated_stream = allow_unauthenticated_streamValue; } }); - it('Detect armor checksum error when not passing public keys (allowUnauthenticatedStream=true)', async function() { - let allowUnauthenticatedStreamValue = openpgp.config.allowUnauthenticatedStream; - openpgp.config.allowUnauthenticatedStream = true; + it('Detect armor checksum error when not passing public keys (allow_unauthenticated_stream=true)', async function() { + let allow_unauthenticated_streamValue = openpgp.config.allow_unauthenticated_stream; + openpgp.config.allow_unauthenticated_stream = true; try { const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromBinary(data), + message: openpgp.message.fromBinary(data), publicKeys: pubKey, privateKeys: privKey }); - expect(openpgp.stream.isStream(encrypted)).to.equal(expectedType); - const message = await openpgp.readArmoredMessage(openpgp.stream.transform(encrypted, value => { + const msgAsciiArmored = encrypted.data; + const message = await openpgp.message.readArmored(openpgp.stream.transform(msgAsciiArmored, value => { value += ''; - const newlineIndex = value.indexOf('\n', 500); + const newlineIndex = value.indexOf('\r\n', 500); if (value.length > 1000) return value.slice(0, newlineIndex - 1) + (value[newlineIndex - 1] === 'a' ? 'b' : 'a') + value.slice(newlineIndex); return value; })); @@ -480,7 +461,7 @@ function tests() { streaming: expectedType, format: 'binary' }); - expect(openpgp.stream.isStream(decrypted.data)).to.equal(expectedType); + expect(util.isStream(decrypted.data)).to.equal(expectedType); const reader = openpgp.stream.getReader(decrypted.data); expect(await reader.peekBytes(1024)).not.to.deep.equal(plaintext[0]); dataArrived(); @@ -488,30 +469,29 @@ function tests() { expect(decrypted.signatures).to.exist.and.have.length(1); expect(await decrypted.signatures[0].verified).to.be.null; } finally { - openpgp.config.allowUnauthenticatedStream = allowUnauthenticatedStreamValue; + openpgp.config.allow_unauthenticated_stream = allow_unauthenticated_streamValue; } }); it('Sign/verify: Detect armor checksum error', async function() { const signed = await openpgp.sign({ - message: openpgp.Message.fromBinary(data), + message: openpgp.message.fromBinary(data), privateKeys: privKey }); - expect(openpgp.stream.isStream(signed)).to.equal(expectedType); - const message = await openpgp.readArmoredMessage(openpgp.stream.transform(signed, value => { + const msgAsciiArmored = signed.data; + const message = await openpgp.message.readArmored(openpgp.stream.transform(msgAsciiArmored, value => { value += ''; - const newlineIndex = value.indexOf('\n', 500); + const newlineIndex = value.indexOf('\r\n', 500); if (value.length > 1000) return value.slice(0, newlineIndex - 1) + (value[newlineIndex - 1] === 'a' ? 'b' : 'a') + value.slice(newlineIndex); return value; })); const verified = await openpgp.verify({ publicKeys: pubKey, message, - streaming: expectedType, - format: 'binary' + streaming: expectedType }); - expect(openpgp.stream.isStream(verified.data)).to.equal(expectedType); + expect(util.isStream(verified.data)).to.equal(expectedType); const reader = openpgp.stream.getReader(verified.data); expect(await reader.peekBytes(1024)).not.to.deep.equal(plaintext[0]); dataArrived(); @@ -519,6 +499,78 @@ function tests() { expect(verified.signatures).to.exist.and.have.length(1); }); + it('Encrypt and decrypt larger message roundtrip (AEAD)', async function() { + let aead_protectValue = openpgp.config.aead_protect; + let aead_chunk_size_byteValue = openpgp.config.aead_chunk_size_byte; + openpgp.config.aead_protect = true; + openpgp.config.aead_chunk_size_byte = 4; + try { + const encrypted = await openpgp.encrypt({ + message: openpgp.message.fromBinary(data), + passwords: ['test'], + }); + + const msgAsciiArmored = encrypted.data; + const message = await openpgp.message.readArmored(msgAsciiArmored); + const decrypted = await openpgp.decrypt({ + passwords: ['test'], + message, + format: 'binary' + }); + expect(util.isStream(decrypted.data)).to.equal(expectedType); + const reader = openpgp.stream.getReader(decrypted.data); + expect(await reader.peekBytes(1024)).to.deep.equal(plaintext[0]); + dataArrived(); + expect(await reader.readToEnd()).to.deep.equal(util.concatUint8Array(plaintext)); + } finally { + openpgp.config.aead_protect = aead_protectValue; + openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteValue; + } + }); + + it('Encrypt and decrypt larger text message roundtrip (AEAD)', async function() { + let aead_protectValue = openpgp.config.aead_protect; + let aead_chunk_size_byteValue = openpgp.config.aead_chunk_size_byte; + openpgp.config.aead_protect = true; + openpgp.config.aead_chunk_size_byte = 0; + try { + let plaintext = []; + let i = 0; + const data = new ReadableStream({ + async pull(controller) { + await new Promise(resolve => setTimeout(resolve, 10)); + if (i++ < 10) { + let randomData = input.createSomeMessage(); + controller.enqueue(randomData); + plaintext.push(randomData); + } else { + controller.close(); + } + } + }); + const encrypted = await openpgp.encrypt({ + message: openpgp.message.fromText(data), + streaming: expectedType, + passwords: ['test'], + }); + + const msgAsciiArmored = encrypted.data; + const message = await openpgp.message.readArmored(msgAsciiArmored); + const decrypted = await openpgp.decrypt({ + passwords: ['test'], + message + }); + expect(util.isStream(decrypted.data)).to.equal(expectedType); + const reader = openpgp.stream.getReader(decrypted.data); + expect((await reader.peekBytes(plaintext[0].length * 4)).toString('utf8').substr(0, plaintext[0].length)).to.equal(plaintext[0]); + dataArrived(); + expect((await reader.readToEnd()).toString('utf8')).to.equal(util.concat(plaintext)); + } finally { + openpgp.config.aead_protect = aead_protectValue; + openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteValue; + } + }); + it('stream.transformPair()', async function() { dataArrived(); // Do not wait until data arrived. const transformed = stream.transformPair(stream.slice(data, 0, 5000), async (readable, writable) => { @@ -534,30 +586,59 @@ function tests() { } await writer.write(value); } - } catch (e) { + } catch(e) { await writer.abort(e); } }); await new Promise(resolve => setTimeout(resolve)); await stream.cancel(transformed); - await new Promise(resolve => setTimeout(resolve)); expect(canceled).to.be.true; }); + it('Input stream should be canceled when canceling decrypted stream (AEAD)', async function() { + let aead_protectValue = openpgp.config.aead_protect; + let aead_chunk_size_byteValue = openpgp.config.aead_chunk_size_byte; + openpgp.config.aead_protect = true; + openpgp.config.aead_chunk_size_byte = 4; + try { + const encrypted = await openpgp.encrypt({ + message: openpgp.message.fromBinary(data), + passwords: ['test'], + }); + + const msgAsciiArmored = encrypted.data; + const message = await openpgp.message.readArmored(msgAsciiArmored); + const decrypted = await openpgp.decrypt({ + passwords: ['test'], + message, + format: 'binary' + }); + expect(util.isStream(decrypted.data)).to.equal(expectedType); + const reader = openpgp.stream.getReader(decrypted.data); + expect(await reader.readBytes(1024)).to.deep.equal(plaintext[0]); + dataArrived(); + reader.releaseLock(); + await openpgp.stream.cancel(decrypted.data, new Error('canceled by test')); + expect(canceled).to.be.true; + } finally { + openpgp.config.aead_protect = aead_protectValue; + openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteValue; + } + }); + it('Sign/verify: Input stream should be canceled when canceling verified stream', async function() { const signed = await openpgp.sign({ - message: openpgp.Message.fromBinary(data), + message: openpgp.message.fromBinary(data), privateKeys: privKey }); - expect(openpgp.stream.isStream(signed)).to.equal(expectedType); - const message = await openpgp.readArmoredMessage(signed); + const msgAsciiArmored = signed.data; + const message = await openpgp.message.readArmored(msgAsciiArmored); const verified = await openpgp.verify({ publicKeys: pubKey, - message, - format: 'binary' + message }); - expect(openpgp.stream.isStream(verified.data)).to.equal(expectedType); + expect(util.isStream(verified.data)).to.equal(expectedType); const reader = openpgp.stream.getReader(verified.data); expect(await reader.readBytes(1024)).to.deep.equal(plaintext[0]); dataArrived(); @@ -570,13 +651,11 @@ function tests() { it("Don't pull entire input stream when we're not pulling encrypted stream", async function() { const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromBinary(data), - passwords: ['test'] + message: openpgp.message.fromBinary(data), + passwords: ['test'], }); - expect(openpgp.stream.isStream(encrypted)).to.equal(expectedType); - - const reader = openpgp.stream.getReader(encrypted); - expect(await reader.readBytes(1024)).to.match(/^-----BEGIN PGP MESSAGE-----\n/); + const reader = openpgp.stream.getReader(encrypted.data); + expect(await reader.readBytes(1024)).to.match(/^-----BEGIN PGP MESSAGE-----\r\n/); dataArrived(); await new Promise(resolve => setTimeout(resolve, 3000)); expect(i).to.be.lessThan(expectedType === 'web' ? 50 : 100); @@ -584,31 +663,60 @@ function tests() { it("Sign: Don't pull entire input stream when we're not pulling signed stream", async function() { const signed = await openpgp.sign({ - message: openpgp.Message.fromBinary(data), + message: openpgp.message.fromBinary(data), privateKeys: privKey }); - expect(openpgp.stream.isStream(signed)).to.equal(expectedType); - - const reader = openpgp.stream.getReader(signed); - expect(await reader.readBytes(1024)).to.match(/^-----BEGIN PGP MESSAGE-----\n/); + const reader = openpgp.stream.getReader(signed.data); + expect(await reader.readBytes(1024)).to.match(/^-----BEGIN PGP MESSAGE-----\r\n/); dataArrived(); await new Promise(resolve => setTimeout(resolve, 3000)); expect(i).to.be.lessThan(expectedType === 'web' ? 50 : 100); }); + it("Don't pull entire input stream when we're not pulling decrypted stream (AEAD)", async function() { + let aead_protectValue = openpgp.config.aead_protect; + let aead_chunk_size_byteValue = openpgp.config.aead_chunk_size_byte; + openpgp.config.aead_protect = true; + openpgp.config.aead_chunk_size_byte = 4; + let coresStub = stub(openpgp.util, 'getHardwareConcurrency'); + coresStub.returns(1); + try { + const encrypted = await openpgp.encrypt({ + message: openpgp.message.fromBinary(data), + passwords: ['test'], + }); + const msgAsciiArmored = encrypted.data; + const message = await openpgp.message.readArmored(msgAsciiArmored); + const decrypted = await openpgp.decrypt({ + passwords: ['test'], + message, + format: 'binary' + }); + expect(util.isStream(decrypted.data)).to.equal(expectedType); + const reader = openpgp.stream.getReader(decrypted.data); + expect(await reader.readBytes(1024)).to.deep.equal(plaintext[0]); + dataArrived(); + await new Promise(resolve => setTimeout(resolve, 3000)); + expect(i).to.be.lessThan(expectedType === 'web' ? 50 : 100); + } finally { + openpgp.config.aead_protect = aead_protectValue; + openpgp.config.aead_chunk_size_byte = aead_chunk_size_byteValue; + coresStub.restore(); + } + }); + it("Sign/verify: Don't pull entire input stream when we're not pulling verified stream", async function() { const signed = await openpgp.sign({ - message: openpgp.Message.fromBinary(data), + message: openpgp.message.fromBinary(data), privateKeys: privKey }); - expect(openpgp.stream.isStream(signed)).to.equal(expectedType); - const message = await openpgp.readArmoredMessage(signed); + const msgAsciiArmored = signed.data; + const message = await openpgp.message.readArmored(msgAsciiArmored); const verified = await openpgp.verify({ publicKeys: pubKey, - message, - format: 'binary' + message }); - expect(openpgp.stream.isStream(verified.data)).to.equal(expectedType); + expect(util.isStream(verified.data)).to.equal(expectedType); const reader = openpgp.stream.getReader(verified.data); expect(await reader.readBytes(1024)).to.deep.equal(plaintext[0]); dataArrived(); @@ -620,26 +728,25 @@ function tests() { dataArrived(); // Do not wait until data arrived. const data = new ReadableStream({ async start(controller) { - controller.enqueue(util.strToUint8Array('hello ')); - controller.enqueue(util.strToUint8Array('world')); + controller.enqueue(util.str_to_Uint8Array('hello ')); + controller.enqueue(util.str_to_Uint8Array('world')); controller.close(); } }); const signed = await openpgp.sign({ - message: openpgp.Message.fromBinary(data), + message: openpgp.message.fromBinary(data), privateKeys: privKey, detached: true, streaming: expectedType }); - expect(openpgp.stream.isStream(signed)).to.equal(expectedType); - const sigArmored = await openpgp.stream.readToEnd(signed); - const signature = await openpgp.readArmoredMessage(sigArmored); + const sigArmored = await openpgp.stream.readToEnd(signed.signature); + const signature = await openpgp.message.readArmored(sigArmored); const verified = await openpgp.verify({ signature, publicKeys: pubKey, - message: openpgp.Message.fromText('hello world') + message: openpgp.message.fromText('hello world') }); - expect(verified.data).to.equal('hello world'); + expect(openpgp.util.decode_utf8(verified.data)).to.equal('hello world'); expect(verified.signatures).to.exist.and.have.length(1); expect(verified.signatures[0].valid).to.be.true; }); @@ -648,26 +755,25 @@ function tests() { dataArrived(); // Do not wait until data arrived. const data = new ReadableStream({ async start(controller) { - controller.enqueue(util.strToUint8Array('hello ')); - controller.enqueue(util.strToUint8Array('world')); + controller.enqueue(util.str_to_Uint8Array('hello ')); + controller.enqueue(util.str_to_Uint8Array('world')); controller.close(); } }); const signed = await openpgp.sign({ - message: openpgp.Message.fromBinary(data), + message: openpgp.message.fromBinary(data), privateKeys: privKey, detached: true, streaming: false, - armor: false }); - expect(openpgp.stream.isStream(signed)).to.be.false; - const signature = await openpgp.readMessage(signed); + const sigArmored = await openpgp.stream.readToEnd(signed.signature); + const signature = await openpgp.message.readArmored(sigArmored); const verified = await openpgp.verify({ signature, publicKeys: pubKey, - message: openpgp.Message.fromText('hello world') + message: openpgp.message.fromText('hello world') }); - expect(verified.data).to.equal('hello world'); + expect(openpgp.util.decode_utf8(verified.data)).to.equal('hello world'); expect(verified.signatures).to.exist.and.have.length(1); expect(verified.signatures[0].valid).to.be.true; }); @@ -676,29 +782,28 @@ function tests() { dataArrived(); // Do not wait until data arrived. const data = new ReadableStream({ async start(controller) { - controller.enqueue(util.strToUint8Array('hello ')); - controller.enqueue(util.strToUint8Array('world')); + controller.enqueue(util.str_to_Uint8Array('hello ')); + controller.enqueue(util.str_to_Uint8Array('world')); controller.close(); } }); - const priv = await openpgp.readArmoredKey(brainpoolPriv); - const pub = await openpgp.readArmoredKey(brainpoolPub); + const priv = (await openpgp.key.readArmored(brainpoolPriv)).keys[0]; + const pub = (await openpgp.key.readArmored(brainpoolPub)).keys[0]; await priv.decrypt(brainpoolPass); const signed = await openpgp.sign({ - message: openpgp.Message.fromBinary(data), + message: openpgp.message.fromBinary(data), privateKeys: priv, detached: true, streaming: expectedType }); - expect(openpgp.stream.isStream(signed)).to.equal(expectedType); - const sigArmored = await openpgp.stream.readToEnd(signed); - const signature = await openpgp.readArmoredMessage(sigArmored); + const sigArmored = await openpgp.stream.readToEnd(signed.signature); + const signature = await openpgp.message.readArmored(sigArmored); const verified = await openpgp.verify({ signature, publicKeys: pub, - message: openpgp.Message.fromText('hello world') + message: openpgp.message.fromText('hello world') }); - expect(verified.data).to.equal('hello world'); + expect(openpgp.util.decode_utf8(verified.data)).to.equal('hello world'); expect(verified.signatures).to.exist.and.have.length(1); expect(verified.signatures[0].valid).to.be.true; }); @@ -707,42 +812,40 @@ function tests() { dataArrived(); // Do not wait until data arrived. const data = new ReadableStream({ async start(controller) { - controller.enqueue(util.strToUint8Array('hello ')); - controller.enqueue(util.strToUint8Array('world')); + controller.enqueue(util.str_to_Uint8Array('hello ')); + controller.enqueue(util.str_to_Uint8Array('world')); controller.close(); } }); - const priv = await openpgp.readArmoredKey(xPriv); - const pub = await openpgp.readArmoredKey(xPub); + const priv = (await openpgp.key.readArmored(xPriv)).keys[0]; + const pub = (await openpgp.key.readArmored(xPub)).keys[0]; await priv.decrypt(xPass); const signed = await openpgp.sign({ - message: openpgp.Message.fromBinary(data), + message: openpgp.message.fromBinary(data), privateKeys: priv, detached: true, streaming: expectedType }); - expect(openpgp.stream.isStream(signed)).to.equal(expectedType); - const sigArmored = await openpgp.stream.readToEnd(signed); - const signature = await openpgp.readArmoredMessage(sigArmored); + const sigArmored = await openpgp.stream.readToEnd(signed.signature); + const signature = await openpgp.message.readArmored(sigArmored); const verified = await openpgp.verify({ signature, publicKeys: pub, - message: openpgp.Message.fromText('hello world') + message: openpgp.message.fromText('hello world') }); - expect(verified.data).to.equal('hello world'); + expect(openpgp.util.decode_utf8(verified.data)).to.equal('hello world'); expect(verified.signatures).to.exist.and.have.length(1); expect(verified.signatures[0].valid).to.be.true; }); it("Detached sign is expected to pull entire input stream when we're not pulling signed stream", async function() { const signed = await openpgp.sign({ - message: openpgp.Message.fromBinary(data), + message: openpgp.message.fromBinary(data), privateKeys: privKey, detached: true }); - expect(openpgp.stream.isStream(signed)).to.equal(expectedType); - const reader = openpgp.stream.getReader(signed); - expect((await reader.readBytes(30)).toString('utf8')).to.equal('-----BEGIN PGP SIGNATURE-----\n'); + const reader = openpgp.stream.getReader(signed.signature); + expect((await reader.readBytes(31)).toString('utf8')).to.equal('-----BEGIN PGP SIGNATURE-----\r\n'); dataArrived(); await new Promise(resolve => setTimeout(resolve, 3000)); expect(i).to.be.greaterThan(100); @@ -750,157 +853,50 @@ function tests() { it('Detached sign: Input stream should be canceled when canceling signed stream', async function() { const signed = await openpgp.sign({ - message: openpgp.Message.fromBinary(data), + message: openpgp.message.fromBinary(data), privateKeys: privKey, detached: true }); - expect(openpgp.stream.isStream(signed)).to.equal(expectedType); - const reader = openpgp.stream.getReader(signed); - expect((await reader.readBytes(30)).toString('utf8')).to.equal('-----BEGIN PGP SIGNATURE-----\n'); + const reader = openpgp.stream.getReader(signed.signature); + expect((await reader.readBytes(31)).toString('utf8')).to.equal('-----BEGIN PGP SIGNATURE-----\r\n'); dataArrived(); reader.releaseLock(); - await openpgp.stream.cancel(signed, new Error('canceled by test')); + await openpgp.stream.cancel(signed.signature, new Error('canceled by test')); expect(canceled).to.be.true; }); - describe('AEAD', function() { - let aeadProtectValue; - let aeadChunkSizeByteValue; - beforeEach(function() { - aeadProtectValue = openpgp.config.aeadProtect; - aeadChunkSizeByteValue = openpgp.config.aeadChunkSizeByte; - openpgp.config.aeadProtect = true; - openpgp.config.aeadChunkSizeByte = 4; - }); - afterEach(function() { - openpgp.config.aeadProtect = aeadProtectValue; - openpgp.config.aeadChunkSizeByte = aeadChunkSizeByteValue; - }); - + if (openpgp.util.detectNode()) { + const fs = util.nodeRequire('fs'); - it('Encrypt and decrypt larger message roundtrip (AEAD)', async function() { + it('Node: Encrypt and decrypt binary message roundtrip', async function() { + dataArrived(); // Do not wait until data arrived. + let plaintext = fs.readFileSync(__filename); + const data = fs.createReadStream(__filename); const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromBinary(data), + message: openpgp.message.fromBinary(data), passwords: ['test'], - armor: false }); - expect(openpgp.stream.isStream(encrypted)).to.equal(expectedType); - const message = await openpgp.readMessage(encrypted); + const msgAsciiArmored = encrypted.data; + const message = await openpgp.message.readArmored(msgAsciiArmored); const decrypted = await openpgp.decrypt({ passwords: ['test'], message, format: 'binary' }); - expect(openpgp.stream.isStream(decrypted.data)).to.equal(expectedType); - const reader = openpgp.stream.getReader(decrypted.data); - expect(await reader.peekBytes(1024)).to.deep.equal(plaintext[0]); - dataArrived(); - expect(await reader.readToEnd()).to.deep.equal(util.concatUint8Array(plaintext)); - }); - - it('Encrypt and decrypt larger text message roundtrip (AEAD)', async function() { - openpgp.config.aeadChunkSizeByte = 0; - - let plaintext = []; - let i = 0; - const data = new ReadableStream({ - async pull(controller) { - await new Promise(resolve => setTimeout(resolve, 10)); - if (i++ < 10) { - let randomData = input.createSomeMessage(); - controller.enqueue(randomData); - plaintext.push(randomData); - } else { - controller.close(); - } - } - }); - const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromText(data), - streaming: expectedType, - passwords: ['test'] - }); - expect(openpgp.stream.isStream(encrypted)).to.equal(expectedType); - - const message = await openpgp.readArmoredMessage(encrypted); - const decrypted = await openpgp.decrypt({ - passwords: ['test'], - message - }); - expect(openpgp.stream.isStream(decrypted.data)).to.equal(expectedType); - const reader = openpgp.stream.getReader(decrypted.data); - expect((await reader.peekBytes(plaintext[0].length * 4)).toString('utf8').substr(0, plaintext[0].length)).to.equal(plaintext[0]); - dataArrived(); - expect((await reader.readToEnd()).toString('utf8')).to.equal(util.concat(plaintext)); - - }); - - it("Don't pull entire input stream when we're not pulling decrypted stream (AEAD)", async function() { - let coresStub; - if (util.detectNode()) { - coresStub = stub(require('os'), 'cpus'); - coresStub.returns(new Array(2)); - // Object.defineProperty(require('os'), 'cpus', { value: () => [,], configurable: true }); - } else { - Object.defineProperty(navigator, 'hardwareConcurrency', { value: 1, configurable: true }); - } - try { - const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromBinary(data), - passwords: ['test'] - }); - expect(openpgp.stream.isStream(encrypted)).to.equal(expectedType); - const message = await openpgp.readArmoredMessage(encrypted); - const decrypted = await openpgp.decrypt({ - passwords: ['test'], - message, - format: 'binary' - }); - expect(openpgp.stream.isStream(decrypted.data)).to.equal(expectedType); - const reader = openpgp.stream.getReader(decrypted.data); - expect(await reader.readBytes(1024)).to.deep.equal(plaintext[0]); - dataArrived(); - await new Promise(resolve => setTimeout(resolve, 3000)); - expect(i).to.be.lessThan(expectedType === 'web' ? 50 : 100); - } finally { - if (util.detectNode()) { - coresStub.restore(); - } else { - delete navigator.hardwareConcurrency; - } - } + expect(util.isStream(decrypted.data)).to.equal('node'); + expect(await openpgp.stream.readToEnd(decrypted.data)).to.deep.equal(plaintext); }); - it('Input stream should be canceled when canceling decrypted stream (AEAD)', async function() { - const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromBinary(data), - passwords: ['test'], - }); - - const message = await openpgp.readArmoredMessage(encrypted); - const decrypted = await openpgp.decrypt({ - passwords: ['test'], - message, - format: 'binary' - }); - expect(openpgp.stream.isStream(decrypted.data)).to.equal(expectedType); - const reader = openpgp.stream.getReader(decrypted.data); - expect(await reader.readBytes(1024)).to.deep.equal(plaintext[0]); - dataArrived(); - reader.releaseLock(); - await openpgp.stream.cancel(decrypted.data, new Error('canceled by test')); - expect(canceled).to.be.true; - }); - }); + } } -module.exports = () => describe('Streaming', function() { +describe('Streaming', function() { let currentTest = 0; before(async function() { - pubKey = await openpgp.readArmoredKey(pub_key); - privKey = await openpgp.readArmoredKey(priv_key); + pubKey = (await openpgp.key.readArmored(pub_key)).keys[0]; + privKey = (await openpgp.key.readArmored(priv_key)).keys[0]; await privKey.decrypt(passphrase); }); @@ -918,7 +914,7 @@ module.exports = () => describe('Streaming', function() { await new Promise(setTimeout); if (test === currentTest && i++ < 100) { if (i === 4) await dataArrivedPromise; - let randomBytes = await random.getRandomBytes(1024); + let randomBytes = await openpgp.crypto.random.getRandomBytes(1024); controller.enqueue(randomBytes); plaintext.push(randomBytes); } else { @@ -934,59 +930,15 @@ module.exports = () => describe('Streaming', function() { tryTests('WhatWG Streams', tests, { if: true, beforeEach: function() { - expectedType = useNativeStream ? 'web' : 'ponyfill'; + expectedType = 'web'; } }); tryTests('Node Streams', tests, { - if: util.detectNode(), + if: openpgp.util.detectNode(), beforeEach: function() { data = openpgp.stream.webToNode(data); expectedType = 'node'; } }); - - if (util.detectNode()) { - const fs = require('fs'); - - it('Node: Encrypt and decrypt text message roundtrip', async function() { - dataArrived(); // Do not wait until data arrived. - const plaintext = fs.readFileSync(__filename.replace('streaming.js', 'openpgp.js'), 'utf8'); - const data = fs.createReadStream(__filename.replace('streaming.js', 'openpgp.js'), { encoding: 'utf8' }); - const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromText(data), - passwords: ['test'] - }); - expect(openpgp.stream.isStream(encrypted)).to.equal('node'); - - const message = await openpgp.readArmoredMessage(encrypted); - const decrypted = await openpgp.decrypt({ - passwords: ['test'], - message - }); - expect(openpgp.stream.isStream(decrypted.data)).to.equal('node'); - expect(await openpgp.stream.readToEnd(decrypted.data)).to.equal(plaintext); - }); - - it('Node: Encrypt and decrypt binary message roundtrip', async function() { - dataArrived(); // Do not wait until data arrived. - const plaintext = fs.readFileSync(__filename.replace('streaming.js', 'openpgp.js')); - const data = fs.createReadStream(__filename.replace('streaming.js', 'openpgp.js')); - const encrypted = await openpgp.encrypt({ - message: openpgp.Message.fromBinary(data), - passwords: ['test'], - armor: false - }); - expect(openpgp.stream.isStream(encrypted)).to.equal('node'); - - const message = await openpgp.readMessage(encrypted); - const decrypted = await openpgp.decrypt({ - passwords: ['test'], - message, - format: 'binary' - }); - expect(openpgp.stream.isStream(decrypted.data)).to.equal('node'); - expect(await openpgp.stream.readToEnd(decrypted.data)).to.deep.equal(plaintext); - }); - } -}); +}); \ No newline at end of file diff --git a/test/general/testInputs.js b/test/general/testInputs.js index 6bcb9e1d..8a12cee5 100644 --- a/test/general/testInputs.js +++ b/test/general/testInputs.js @@ -1,17 +1,18 @@ + /** * Generates a 64 character long javascript string out of the whole utf-8 range. */ function createSomeMessage(){ - const arr = []; - for (let i = 0; i < 30; i++) { - arr.push(Math.floor(Math.random() * 10174) + 1); - } - for (let i = 0; i < 10; i++) { - arr.push(0x1F600 + Math.floor(Math.random() * (0x1F64F - 0x1F600)) + 1); - } - return '  \t' + String.fromCodePoint(...arr).replace(/\r/g, '\n') + '  \t\n한국어/조선말'; + let arr = []; + for (let i = 0; i < 30; i++) { + arr.push(Math.floor(Math.random() * 10174) + 1); + } + for (let i = 0; i < 10; i++) { + arr.push(0x1F600 + Math.floor(Math.random() * (0x1F64F - 0x1F600)) + 1); + } + return '  \t' + String.fromCodePoint(...arr).replace(/\r/g, '\n') + '  \t\n한국어/조선말'; } module.exports = { - createSomeMessage: createSomeMessage + createSomeMessage: createSomeMessage }; diff --git a/test/general/util.js b/test/general/util.js index 7bc4ae6e..f8628331 100644 --- a/test/general/util.js +++ b/test/general/util.js @@ -1,159 +1,181 @@ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); -const util = require('../../src/util'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); const chai = require('chai'); const { expect } = chai; -module.exports = () => describe('Util unit tests', function() { +describe('Util unit tests', function() { describe('isString', function() { it('should return true for type "string"', function() { const data = 'foo'; - expect(util.isString(data)).to.be.true; + expect(openpgp.util.isString(data)).to.be.true; }); it('should return true for type String', function() { const data = String('foo'); - expect(util.isString(data)).to.be.true; + expect(openpgp.util.isString(data)).to.be.true; }); it('should return true for inherited type of String', function() { function MyString() {} MyString.prototype = Object.create(String.prototype); const data = new MyString(); - expect(util.isString(data)).to.be.true; + expect(openpgp.util.isString(data)).to.be.true; }); it('should return true for empty string', function() { const data = ''; - expect(util.isString(data)).to.be.true; + expect(openpgp.util.isString(data)).to.be.true; }); it('should return false for undefined', function() { let data; - expect(util.isString(data)).to.be.false; + expect(openpgp.util.isString(data)).to.be.false; }); it('should return false for Object', function() { const data = {}; - expect(util.isString(data)).to.be.false; + expect(openpgp.util.isString(data)).to.be.false; }); }); describe('isArray', function() { it('should return true for []', function() { const data = []; - expect(util.isArray(data)).to.be.true; + expect(openpgp.util.isArray(data)).to.be.true; }); it('should return true for type Array', function() { const data = Array(); - expect(util.isArray(data)).to.be.true; + expect(openpgp.util.isArray(data)).to.be.true; }); it('should return true for inherited type of Array', function() { function MyArray() {} MyArray.prototype = Object.create(Array.prototype); const data = new MyArray(); - expect(util.isArray(data)).to.be.true; + expect(openpgp.util.isArray(data)).to.be.true; }); it('should return false for undefined', function() { let data; - expect(util.isArray(data)).to.be.false; + expect(openpgp.util.isArray(data)).to.be.false; }); it('should return false for Object', function() { const data = {}; - expect(util.isArray(data)).to.be.false; + expect(openpgp.util.isArray(data)).to.be.false; }); }); describe('isUint8Array', function() { it('should return true for type Uint8Array', function() { const data = new Uint8Array(); - expect(util.isUint8Array(data)).to.be.true; + expect(openpgp.util.isUint8Array(data)).to.be.true; }); it('should return true for inherited type of Uint8Array', function() { function MyUint8Array() {} MyUint8Array.prototype = new Uint8Array(); const data = new MyUint8Array(); - expect(util.isUint8Array(data)).to.be.true; + expect(openpgp.util.isUint8Array(data)).to.be.true; }); it('should return false for undefined', function() { let data; - expect(util.isUint8Array(data)).to.be.false; + expect(openpgp.util.isUint8Array(data)).to.be.false; }); it('should return false for Object', function() { const data = {}; - expect(util.isUint8Array(data)).to.be.false; - }); - }); - - describe('leftPad', function() { - it('should not change the input if the length is correct', function() { - const bytes = new Uint8Array([2, 1]); - const padded = util.leftPad(bytes, 2); - expect(padded).to.deep.equal(bytes); - }); - it('should add leading zeros to input array', function() { - const bytes = new Uint8Array([1, 2]); - const padded = util.leftPad(bytes, 5); - expect(padded).to.deep.equal(new Uint8Array([0, 0, 0, 1, 2])); - }); - }); - - describe('uint8ArrayToMpi', function() { - it('should strip leading zeros', function() { - const bytes = new Uint8Array([0, 0, 1, 2]); - const mpi = util.uint8ArrayToMpi(bytes); - expect(mpi).to.deep.equal(new Uint8Array([0, 9, 1, 2])); - }); - it('should throw on array of all zeros', function() { - const bytes = new Uint8Array([0, 0]); - expect(() => util.uint8ArrayToMpi(bytes)).to.throw('Zero MPI'); + expect(openpgp.util.isUint8Array(data)).to.be.false; }); }); describe('isEmailAddress', function() { it('should return true for valid email address', function() { const data = 'test@example.com'; - expect(util.isEmailAddress(data)).to.be.true; + expect(openpgp.util.isEmailAddress(data)).to.be.true; }); it('should return true for valid email address', function() { const data = 'test@xn--wgv.xn--q9jyb4c'; - expect(util.isEmailAddress(data)).to.be.true; + expect(openpgp.util.isEmailAddress(data)).to.be.true; }); it('should return false for invalid email address', function() { const data = 'Test User '; - expect(util.isEmailAddress(data)).to.be.false; + expect(openpgp.util.isEmailAddress(data)).to.be.false; }); it('should return false for invalid email address', function() { const data = 'test@examplecom'; - expect(util.isEmailAddress(data)).to.be.false; + expect(openpgp.util.isEmailAddress(data)).to.be.false; }); it('should return false for invalid email address', function() { const data = 'testexamplecom'; - expect(util.isEmailAddress(data)).to.be.false; + expect(openpgp.util.isEmailAddress(data)).to.be.false; }); it('should return false for empty string', function() { const data = ''; - expect(util.isEmailAddress(data)).to.be.false; + expect(openpgp.util.isEmailAddress(data)).to.be.false; }); it('should return false for undefined', function() { let data; - expect(util.isEmailAddress(data)).to.be.false; + expect(openpgp.util.isEmailAddress(data)).to.be.false; }); it('should return false for Object', function() { const data = {}; - expect(util.isEmailAddress(data)).to.be.false; + expect(openpgp.util.isEmailAddress(data)).to.be.false; + }); + }); + + describe('parseUserID', function() { + it('should parse email address', function() { + const email = "TestName Test "; + const result = openpgp.util.parseUserId(email); + expect(result.name).to.equal('TestName Test'); + expect(result.email).to.equal('test@example.com'); + }); + it('should parse email address with @ in display name and comment', function() { + const email = "Test@Name Test (a comment) "; + const result = openpgp.util.parseUserId(email); + expect(result.name).to.equal('Test@Name Test'); + expect(result.email).to.equal('test@example.com'); + expect(result.comment).to.equal('a comment'); }); }); + describe('getTransferables', function() { + const buf1 = new Uint8Array(1); + const buf2 = new Uint8Array(1); + const obj = { + data1: buf1, + data2: buf1, + data3: { + data4: buf2 + } + }; + + it('should return undefined when zero_copy is false', function() { + openpgp.config.zero_copy = false; + expect(openpgp.util.getTransferables(obj, false)).to.be.undefined; + }); + it('should return undefined for no input', function() { + expect(openpgp.util.getTransferables(undefined, true)).to.be.undefined; + }); + it('should return undefined for an empty oject', function() { + expect(openpgp.util.getTransferables({}, true)).to.be.undefined; + }); + if (typeof navigator !== 'undefined') { + it('should return two buffers', function() { + expect(openpgp.util.getTransferables(obj, true)).to.deep.equal( + navigator.userAgent.indexOf('Safari') !== -1 && (navigator.userAgent.indexOf('Version/11.1') !== -1 || (navigator.userAgent.match(/Chrome\/(\d+)/) || [])[1] < 56) ? + undefined : + [buf1.buffer, buf2.buffer] + ); + }); + } + }); + describe("Misc.", function() { it('util.readNumber should not overflow until full range of uint32', function () { const ints = [Math.pow(2, 20), Math.pow(2, 25), Math.pow(2, 30), Math.pow(2, 32) - 1]; for(let i = 0; i < ints.length; i++) { - expect(util.readNumber(util.writeNumber(ints[i], 4))).to.equal(ints[i]); + expect(openpgp.util.readNumber(openpgp.util.writeNumber(ints[i], 4))).to.equal(ints[i]); } }); }); describe("Zbase32", function() { it('util.encodeZBase32 encodes correctly', function() { - const encoded = util.encodeZBase32(util.strToUint8Array('test-wkd')); + const encoded = openpgp.util.encodeZBase32(openpgp.util.str_to_Uint8Array('test-wkd')); expect(encoded).to.equal('qt1zg7bpq7ise'); }) }) diff --git a/test/general/wkd.js b/test/general/wkd.js index 77da38c9..e4fa4d9a 100644 --- a/test/general/wkd.js +++ b/test/general/wkd.js @@ -1,10 +1,10 @@ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); const chai = require('chai'); const { expect } = chai; -module.exports = () => describe.skip('WKD unit tests', function() { +describe.skip('WKD unit tests', function() { this.timeout(60000); let wkd; @@ -20,26 +20,27 @@ module.exports = () => describe.skip('WKD unit tests', function() { return wkd.lookup({ email: 'test-wkd@metacode.biz', rawBytes: true - }).then(function(keys) { - expect(keys).to.exist; - expect(keys).to.be.an.instanceof(Uint8Array); + }).then(function(key) { + expect(key).to.exist; + expect(key).to.be.an.instanceof(Uint8Array); }); }); it('by email address should work', function() { return wkd.lookup({ email: 'test-wkd@metacode.biz' - }).then(function(keys) { - expect(keys).to.exist; - expect(keys).to.have.length(1); + }).then(function(key) { + expect(key).to.exist; + expect(key).to.have.property('keys'); + expect(key.keys).to.have.length(1); }); }); it('by email address should not find a key', function() { return wkd.lookup({ email: 'test-wkd-does-not-exist@metacode.biz' - }).then(function(keys) { - expect(keys).to.be.undefined; + }).then(function(key) { + expect(key).to.be.undefined; }); }); }); diff --git a/test/general/x25519.js b/test/general/x25519.js index 90ca9b8d..0aabf317 100644 --- a/test/general/x25519.js +++ b/test/general/x25519.js @@ -1,10 +1,6 @@ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); -const elliptic = require('../../src/crypto/public_key/elliptic'); -const signature = require('../../src/crypto/signature'); -const OID = require('../../src/type/oid'); -const util = require('../../src/util'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); -const nacl = require('tweetnacl'); +const elliptic = openpgp.crypto.publicKey.elliptic; const chai = require('chai'); chai.use(require('chai-as-promised')); @@ -12,7 +8,7 @@ chai.use(require('chai-as-promised')); const { expect } = chai; const input = require('./testInputs'); -module.exports = () => (openpgp.config.ci ? describe.skip : describe)('X25519 Cryptography', function () { +(openpgp.config.ci ? describe.skip : describe)('X25519 Cryptography', function () { const data = { light: { id: '1ecdf026c0245830', @@ -126,23 +122,27 @@ module.exports = () => (openpgp.config.ci ? describe.skip : describe)('X25519 Cr if (data[name].pub_key) { return data[name].pub_key; } - const pub = await openpgp.readArmoredKey(data[name].pub); + const pub = await openpgp.key.readArmored(data[name].pub); expect(pub).to.exist; - expect(pub.getKeyId().toHex()).to.equal(data[name].id); - data[name].pub_key = pub; - return pub; + expect(pub.err).to.not.exist; + expect(pub.keys).to.have.length(1); + expect(pub.keys[0].getKeyId().toHex()).to.equal(data[name].id); + data[name].pub_key = pub.keys[0]; + return data[name].pub_key; } async function load_priv_key(name) { if (data[name].priv_key) { return data[name].priv_key; } - const pk = await openpgp.readArmoredKey(data[name].priv); + const pk = await openpgp.key.readArmored(data[name].priv); expect(pk).to.exist; - expect(pk.getKeyId().toHex()).to.equal(data[name].id); - await pk.decrypt(data[name].pass); - data[name].priv_key = pk; - return pk; + expect(pk.err).to.not.exist; + expect(pk.keys).to.have.length(1); + expect(pk.keys[0].getKeyId().toHex()).to.equal(data[name].id); + expect(await pk.keys[0].decrypt(data[name].pass)).to.be.true; + data[name].priv_key = pk.keys[0]; + return data[name].priv_key; } it('Load public key', async function () { @@ -161,7 +161,7 @@ module.exports = () => (openpgp.config.ci ? describe.skip : describe)('X25519 Cr it('Verify clear signed message', async function () { const name = 'light'; const pub = await load_pub_key(name); - const msg = await openpgp.readArmoredCleartextMessage(data[name].message_signed); + const msg = await openpgp.cleartext.readArmored(data[name].message_signed); return openpgp.verify({ publicKeys: [pub], message: msg }).then(function(result) { expect(result).to.exist; expect(result.data).to.equal(data[name].message); @@ -174,10 +174,10 @@ module.exports = () => (openpgp.config.ci ? describe.skip : describe)('X25519 Cr const name = 'light'; const randomData = input.createSomeMessage(); const priv = await load_priv_key(name); - const signed = await openpgp.sign({ privateKeys: [priv], message: openpgp.CleartextMessage.fromText(randomData)}); + const signed = await openpgp.sign({ privateKeys: [priv], message: openpgp.cleartext.fromText(randomData)}); const pub = await load_pub_key(name); - const msg = await openpgp.readArmoredCleartextMessage(signed); - const result = await openpgp.verify({ publicKeys: [pub], message: msg }); + const msg = await openpgp.cleartext.readArmored(signed.data); + const result = await openpgp.verify({ publicKeys: [pub], message: msg}); expect(result).to.exist; expect(result.data).to.equal(randomData.replace(/[ \t]+$/mg, '')); @@ -188,7 +188,7 @@ module.exports = () => (openpgp.config.ci ? describe.skip : describe)('X25519 Cr it('Decrypt and verify message', async function () { const light = await load_pub_key('light'); const night = await load_priv_key('night'); - const msg = await openpgp.readArmoredMessage(data.night.message_encrypted); + const msg = await openpgp.message.readArmored(data.night.message_encrypted); const result = await openpgp.decrypt({ privateKeys: night, publicKeys: [light], message: msg }); expect(result).to.exist; @@ -201,9 +201,9 @@ module.exports = () => (openpgp.config.ci ? describe.skip : describe)('X25519 Cr const nightPublic = await load_pub_key('night'); const lightPrivate = await load_priv_key('light'); const randomData = input.createSomeMessage(); - const encrypted = await openpgp.encrypt({ publicKeys: [nightPublic], privateKeys: [lightPrivate], message: openpgp.Message.fromText(randomData) }); + const encrypted = await openpgp.encrypt({ publicKeys: [nightPublic], privateKeys: [lightPrivate], message: openpgp.message.fromText(randomData) }); - const message = await openpgp.readArmoredMessage(encrypted); + const message = await openpgp.message.readArmored(encrypted.data); const lightPublic = await load_pub_key('light'); const nightPrivate = await load_priv_key('night'); const result = await openpgp.decrypt({ privateKeys: nightPrivate, publicKeys: [lightPublic], message: message }); @@ -216,26 +216,29 @@ module.exports = () => (openpgp.config.ci ? describe.skip : describe)('X25519 Cr describe('Ed25519 Test Vectors from RFC8032', function () { // https://tools.ietf.org/html/rfc8032#section-7.1 + const signature = openpgp.crypto.signature; + const util = openpgp.util; function testVector(vector) { const curve = new elliptic.Curve('ed25519'); - const { publicKey } = nacl.sign.keyPair.fromSeed(util.hexToUint8Array(vector.SECRET_KEY)); - expect(publicKey).to.deep.equal(util.hexToUint8Array(vector.PUBLIC_KEY)); - const data = util.strToUint8Array(vector.MESSAGE); - const privateParams = { - seed: util.hexToUint8Array(vector.SECRET_KEY) - }; - const publicParams = { - oid: new OID(curve.oid), - Q: util.hexToUint8Array('40' + vector.PUBLIC_KEY) - }; - const R = util.hexToUint8Array(vector.SIGNATURE.R); - const S = util.hexToUint8Array(vector.SIGNATURE.S); + const { publicKey } = openpgp.crypto.publicKey.nacl.sign.keyPair.fromSeed(openpgp.util.hex_to_Uint8Array(vector.SECRET_KEY)); + expect(publicKey).to.deep.equal(openpgp.util.hex_to_Uint8Array(vector.PUBLIC_KEY)); + const data = util.str_to_Uint8Array(vector.MESSAGE); + const keyIntegers = [ + openpgp.OID.fromClone(curve), + new openpgp.MPI(util.hex_to_str('40'+vector.PUBLIC_KEY)), + new openpgp.MPI(util.hex_to_str(vector.SECRET_KEY)) + ]; + const msg_MPIs = [ + new openpgp.MPI(util.Uint8Array_to_str(util.hex_to_Uint8Array(vector.SIGNATURE.R).reverse())), + new openpgp.MPI(util.Uint8Array_to_str(util.hex_to_Uint8Array(vector.SIGNATURE.S).reverse())) + ]; return Promise.all([ - signature.sign(22, undefined, publicParams, privateParams, undefined, data).then(({ r, s }) => { - expect(R).to.deep.eq(r); - expect(S).to.deep.eq(s); + signature.sign(22, undefined, keyIntegers, undefined, data).then(signed => { + const len = ((signed[0] << 8| signed[1]) + 7) / 8; + expect(util.hex_to_Uint8Array(vector.SIGNATURE.R)).to.deep.eq(signed.slice(2, 2 + len)); + expect(util.hex_to_Uint8Array(vector.SIGNATURE.S)).to.deep.eq(signed.slice(4 + len)); }), - signature.verify(22, undefined, { r: R, s: S }, publicParams, undefined, data).then(result => { + signature.verify(22, undefined, msg_MPIs, keyIntegers, undefined, data).then(result => { expect(result).to.be.true; }) ]); @@ -243,45 +246,64 @@ module.exports = () => (openpgp.config.ci ? describe.skip : describe)('X25519 Cr it('Signature of empty string', function () { return testVector({ - SECRET_KEY: '9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60', - PUBLIC_KEY: 'd75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a', + SECRET_KEY: + ['9d61b19deffd5a60ba844af492ec2cc4', + '4449c5697b326919703bac031cae7f60'].join(''), + PUBLIC_KEY: + ['d75a980182b10ab7d54bfed3c964073a', + '0ee172f3daa62325af021a68f707511a'].join(''), MESSAGE: '', - SIGNATURE: { - R: 'e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155', - S: '5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b' - } + SIGNATURE: + { R: ['e5564300c360ac729086e2cc806e828a', + '84877f1eb8e5d974d873e06522490155'].join(''), + S: ['5fb8821590a33bacc61e39701cf9b46b', + 'd25bf5f0595bbe24655141438e7a100b'].join('') } }); }); it('Signature of single byte', function () { return testVector({ - SECRET_KEY: '4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb', - PUBLIC_KEY: '3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c', - MESSAGE: util.hexToStr('72'), - SIGNATURE: { - R: '92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da', - S: '085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00' - } + SECRET_KEY: + ['4ccd089b28ff96da9db6c346ec114e0f', + '5b8a319f35aba624da8cf6ed4fb8a6fb'].join(''), + PUBLIC_KEY: + ['3d4017c3e843895a92b70aa74d1b7ebc', + '9c982ccf2ec4968cc0cd55f12af4660c'].join(''), + MESSAGE: util.hex_to_str('72'), + SIGNATURE: + { R: ['92a009a9f0d4cab8720e820b5f642540', + 'a2b27b5416503f8fb3762223ebdb69da'].join(''), + S: ['085ac1e43e15996e458f3613d0f11d8c', + '387b2eaeb4302aeeb00d291612bb0c00'].join('') } }); }); it('Signature of two bytes', function () { return testVector({ - SECRET_KEY: 'c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7', - PUBLIC_KEY: 'fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025', - MESSAGE: util.hexToStr('af82'), - SIGNATURE: { - R: '6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac', - S: '18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a' - } + SECRET_KEY: + ['c5aa8df43f9f837bedb7442f31dcb7b1', + '66d38535076f094b85ce3a2e0b4458f7'].join(''), + PUBLIC_KEY: + ['fc51cd8e6218a1a38da47ed00230f058', + '0816ed13ba3303ac5deb911548908025'].join(''), + MESSAGE: util.hex_to_str('af82'), + SIGNATURE: + { R: ['6291d657deec24024827e69c3abe01a3', + '0ce548a284743a445e3680d7db5ac3ac'].join(''), + S: ['18ff9b538d16f290ae67f760984dc659', + '4a7c15e9716ed28dc027beceea1ec40a'].join('') } }); }); it('Signature of 1023 bytes', function () { return testVector({ - SECRET_KEY: 'f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5', - PUBLIC_KEY: '278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e', - MESSAGE: util.hexToStr([ + SECRET_KEY: + ['f5e5767cf153319517630f226876b86c', + '8160cc583bc013744c6bf255f5cc0ee5'].join(''), + PUBLIC_KEY: + ['278117fc144c72340f67d0f2316e8386', + 'ceffbf2b2428c9c51fef7c597f1d426e'].join(''), + MESSAGE: util.hex_to_str([ '08b8b2b733424243760fe426a4b54908', '632110a66c2f6591eabd3345e3e4eb98', 'fa6e264bf09efe12ee50f8f54e9f77b1', @@ -347,32 +369,66 @@ module.exports = () => (openpgp.config.ci ? describe.skip : describe)('X25519 Cr '0618983f8741c5ef68d3a101e8a3b8ca', 'c60c905c15fc910840b94c00a0b9d0' ].join('')), - SIGNATURE: { - R: '0aab4c900501b3e24d7cdf4663326a3a87df5e4843b2cbdb67cbf6e460fec350', - S: 'aa5371b1508f9f4528ecea23c436d94b5e8fcd4f681e30a6ac00a9704a188a03' - } + SIGNATURE: + { R: ['0aab4c900501b3e24d7cdf4663326a3a', + '87df5e4843b2cbdb67cbf6e460fec350'].join(''), + S: ['aa5371b1508f9f4528ecea23c436d94b', + '5e8fcd4f681e30a6ac00a9704a188a03'].join('') } }); }); it('Signature of SHA(abc)', function () { return testVector({ - SECRET_KEY: '833fe62409237b9d62ec77587520911e9a759cec1d19755b7da901b96dca3d42', - PUBLIC_KEY: 'ec172b93ad5e563bf4932c70e1245034c35467ef2efd4d64ebf819683467e2bf', - MESSAGE: util.hexToStr([ + SECRET_KEY: + ['833fe62409237b9d62ec77587520911e', + '9a759cec1d19755b7da901b96dca3d42'].join(''), + PUBLIC_KEY: + ['ec172b93ad5e563bf4932c70e1245034', + 'c35467ef2efd4d64ebf819683467e2bf'].join(''), + MESSAGE: util.hex_to_str([ 'ddaf35a193617abacc417349ae204131', '12e6fa4e89a97ea20a9eeee64b55d39a', '2192992a274fc1a836ba3c23a3feebbd', '454d4423643ce80e2a9ac94fa54ca49f' ].join('')), - SIGNATURE: { - R: 'dc2a4459e7369633a52b1bf277839a00201009a3efbf3ecb69bea2186c26b589', - S: '09351fc9ac90b3ecfdfbc7c66431e0303dca179c138ac17ad9bef1177331a704' - } + SIGNATURE: + { R: ['dc2a4459e7369633a52b1bf277839a00', + '201009a3efbf3ecb69bea2186c26b589'].join(''), + S: ['09351fc9ac90b3ecfdfbc7c66431e030', + '3dca179c138ac17ad9bef1177331a704'].join('') } }); }); }); - describe('X25519 Omnibus Tests', omnibus); +/* TODO how does GPG2 accept this? + it('Should handle little-endian parameters in EdDSA', function () { + const pubKey = [ + '-----BEGIN PGP PUBLIC KEY BLOCK-----', + 'Version: OpenPGP.js v3.0.0', + 'Comment: https://openpgpjs.org', + '', + 'xjMEWnRgnxYJKwYBBAHaRw8BAQdAZ8gxxCdUxIv4tBwhfUMW2uoEb1KvOfP8', + 'D+0ObBtsLnfNDkhpIDxoaUBoZWwubG8+wnYEEBYKACkFAlp0YJ8GCwkHCAMC', + 'CRDAYsFlymHCFQQVCAoCAxYCAQIZAQIbAwIeAQAAswsA/3qNZnwBn/ef4twv', + 'uvmFicYK//DDX1jIkpDiQ+/okLUEAPdAr3J/Z2WA7OD0d36trHNB06WLXJUu', + 'aCVm1TwoJHcNzjgEWnRgnxIKKwYBBAGXVQEFAQEHQPBVH+skap0NHMBw2HMe', + 'xWYUQ67I9Did3KoJuuEJ/ctQAwEIB8JhBBgWCAATBQJadGCfCRDAYsFlymHC', + 'FQIbDAAAhNQBAKmy4gPorjbwTwy5usylHttP28XnTdaGkZ1E7Rc3G9luAQCs', + 'Gbm1oe83ZB+0aSp5m34YkpHQNb80y8PGFy7nIexiAA==', + '=xeG/', + '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); + const hi = (await openpgp.key.readArmored(pubKey)).keys[0]; + const results = hi.getPrimaryUser(); + expect(results).to.exist; + expect(results.user).to.exist; + const user = results.user; + expect(user.selfCertifications[0].verify( + hi.primaryKey, {userId: user.userId, key: hi.primaryKey} + )).to.eventually.be.true; + await user.verifyCertificate( + hi.primaryKey, user.selfCertifications[0], [hi] + ); + }); */ }); // TODO export, then reimport key and validate @@ -401,16 +457,12 @@ function omnibus() { // Self Certificate is valid const user = hi.users[0]; - const certificate = user.selfCertifications[0]; - certificate.verified = null; - await certificate.verify( - primaryKey, openpgp.enums.signature.certGeneric, { userId: user.userId, key: primaryKey } - ).then(async () => expect(certificate.verified).to.be.true); - - certificate.verified = null; + await expect(user.selfCertifications[0].verify( + primaryKey, openpgp.enums.signature.cert_generic, { userId: user.userId, key: primaryKey } + )).to.eventually.be.true; await user.verifyCertificate( - primaryKey, certificate, [hi.toPublic()] - ).then(async () => expect(certificate.verified).to.be.true); + primaryKey, user.selfCertifications[0], [hi.toPublic()] + ); const options = { userIds: { name: "Bye", email: "bye@good.bye" }, @@ -425,31 +477,25 @@ function omnibus() { // Self Certificate is valid const user = bye.users[0]; - const certificate = user.selfCertifications[0]; - certificate.verified = null; - await certificate.verify( - bye.primaryKey, openpgp.enums.signature.certGeneric, { userId: user.userId, key: bye.primaryKey } - ).then(async () => expect(certificate.verified).to.be.true); - certificate.verified = null; + await expect(user.selfCertifications[0].verify( + bye.primaryKey, openpgp.enums.signature.cert_generic, { userId: user.userId, key: bye.primaryKey } + )).to.eventually.be.true; await user.verifyCertificate( bye.primaryKey, user.selfCertifications[0], [bye.toPublic()] - ).then(async () => expect(certificate.verified).to.be.true); + ); return Promise.all([ // Hi trusts Bye! bye.toPublic().signPrimaryUser([hi]).then(trustedBye => { - const hiCertificate = trustedBye.users[0].otherCertifications[0]; - expect(hiCertificate.verified).to.be.true; - hiCertificate.verified = null; - return hiCertificate.verify( - primaryKey, openpgp.enums.signature.certGeneric, { userId: user.userId, key: bye.toPublic().primaryKey } - ).then(async () => expect(hiCertificate.verified).to.be.true); + expect(trustedBye.users[0].otherCertifications[0].verify( + primaryKey, openpgp.enums.signature.cert_generic, { userId: user.userId, key: bye.toPublic().primaryKey } + )).to.eventually.be.true; }), // Signing message openpgp.sign( - { message: openpgp.CleartextMessage.fromText('Hi, this is me, Hi!'), privateKeys: hi } + { message: openpgp.cleartext.fromText('Hi, this is me, Hi!'), privateKeys: hi } ).then(async signed => { - const msg = await openpgp.readArmoredCleartextMessage(signed); + const msg = await openpgp.cleartext.readArmored(signed.data); // Verifying signed message return Promise.all([ openpgp.verify( @@ -458,9 +504,9 @@ function omnibus() { // Verifying detached signature openpgp.verify( { - message: openpgp.Message.fromText('Hi, this is me, Hi!'), + message: openpgp.message.fromText('Hi, this is me, Hi!'), publicKeys: hi.toPublic(), - signature: await openpgp.readArmoredSignature(signed) + signature: await openpgp.signature.readArmored(signed.data) } ).then(output => expect(output.signatures[0].valid).to.be.true) ]); @@ -468,12 +514,12 @@ function omnibus() { // Encrypting and signing openpgp.encrypt( { - message: openpgp.Message.fromText('Hi, Hi wrote this but only Bye can read it!'), + message: openpgp.message.fromText('Hi, Hi wrote this but only Bye can read it!'), publicKeys: [bye.toPublic()], privateKeys: [hi] } ).then(async encrypted => { - const msg = await openpgp.readArmoredMessage(encrypted); + const msg = await openpgp.message.readArmored(encrypted.data); // Decrypting and verifying return openpgp.decrypt( { @@ -491,3 +537,20 @@ function omnibus() { }); }); } + +tryTests('X25519 Omnibus Tests', omnibus, { + if: !openpgp.config.ci +}); + +tryTests('X25519 Omnibus Tests - Worker', omnibus, { + if: typeof window !== 'undefined' && window.Worker, + before: async function() { + await openpgp.initWorker({ path: '../dist/openpgp.worker.js' }); + }, + beforeEach: function() { + openpgp.config.use_native = true; + }, + after: function() { + openpgp.destroyWorker(); + } +}); diff --git a/test/security/index.js b/test/security/index.js index a83ecc21..d515963f 100644 --- a/test/security/index.js +++ b/test/security/index.js @@ -1,6 +1,6 @@ -module.exports = () => describe('Security', function () { - require('./message_signature_bypass')(); - require('./unsigned_subpackets')(); - require('./subkey_trust')(); - require('./preferred_algo_mismatch')(); +describe('Security', function () { + require('./message_signature_bypass'); + require('./unsigned_subpackets'); + require('./subkey_trust'); + require('./preferred_algo_mismatch'); }); diff --git a/test/security/message_signature_bypass.js b/test/security/message_signature_bypass.js index 85034757..8cc8c345 100644 --- a/test/security/message_signature_bypass.js +++ b/test/security/message_signature_bypass.js @@ -1,7 +1,6 @@ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); -const util = require('../../src/util'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); -const { readArmoredKey, readArmoredCleartextMessage, SignaturePacket } = openpgp; +const { key, cleartext, util, packet: { Signature } } = openpgp; const chai = require('chai'); chai.use(require('chai-as-promised')); @@ -68,22 +67,29 @@ fhGyl7nA7UCwgsqf7ZPBhRg= =nbjQ -----END PGP SIGNATURE-----`; async function getOtherPubKey() { - return await readArmoredKey(OTHERPUBKEY); + return (await key.readArmored(OTHERPUBKEY)).keys[0]; } /** * The "standalone" signature signed by the victim. */ -const STANDALONE_PKT = util.hexToUint8Array(`04020108001005025bab730a091055208b2d18ace3ce000059da0800b823eceba1bae016afa584bc67ef931dde167fe683bea58761dac31abaaf223afa4cd41fe609b06f7809f2e01ae8792e08591419e591d652d7580af3b7cdfa27e63dd4838fc7ec2aa485757d6c1c6c33bf305cb8fb7eaa1b47ac00825b08a20606a320e988733294957e03012064b61c74a3d41bfebddd4fdd739ab9e220ae48d32a9edf8ff5aec1e13807fc76cd84b9bba914926a14e6f5aacb0c584fa306b4d11280ff107e6aeee9f68c419c7084dc5504990aa7e31d3e042fa745fdb9ae8207fbc15fc440b5df148252e9c65cccaf3a5d6d6919a5c12912ef41761afde4561ca70696bba37452b32584684fa2d50e4f138e101f13dab6125aa5680bd9658c`); +const STANDALONE_PKT = util.b64_to_Uint8Array(` +BAIBCAAQBQJbq3MKCRBVIIstGKzjzgAAWdoIALgj7OuhuuAWr6WEvGfvkx3e +Fn/mg76lh2Hawxq6ryI6+kzUH+YJsG94CfLgGuh5LghZFBnlkdZS11gK87fN ++ifmPdSDj8fsKqSFdX1sHGwzvzBcuPt+qhtHrACCWwiiBgajIOmIczKUlX4D +ASBkthx0o9Qb/r3dT91zmrniIK5I0yqe34/1rsHhOAf8ds2EubupFJJqFOb1 +qssMWE+jBrTREoD/EH5q7un2jEGccITcVQSZCqfjHT4EL6dF/bmuggf7wV/E +QLXfFIJS6cZczK86XW1pGaXBKRLvQXYa/eRWHKcGlrujdFKzJYRoT6LVDk8T +jhAfE9q2ElqlaAvZZYw=`); async function fakeSignature() { // read the template and modify the text to // invalidate the signature. - let fake = await readArmoredCleartextMessage( + let fake = await cleartext.readArmored( ORIGINAL.replace( 'You owe me', 'I owe you')); // read the standalone signature packet - const tmp = new SignaturePacket(); + const tmp = new Signature(); await tmp.read(STANDALONE_PKT); // replace the "text" signature with the @@ -92,7 +98,7 @@ async function fakeSignature() { const faked_armored = await fake.armor(); // re-read the message to eliminate any // behaviour due to cached values. - fake = await readArmoredCleartextMessage(faked_armored); + fake = await cleartext.readArmored(faked_armored); // faked message now verifies correctly const res = await openpgp.verify({ message: fake, @@ -103,4 +109,4 @@ async function fakeSignature() { expect(signatures).to.have.length(0); } -module.exports = () => it('Does not accept non-binary/text signatures', fakeSignature); +it('Does not accept non-binary/text signatures', fakeSignature); diff --git a/test/security/preferred_algo_mismatch.js b/test/security/preferred_algo_mismatch.js index 6bb7942d..9df2c1af 100644 --- a/test/security/preferred_algo_mismatch.js +++ b/test/security/preferred_algo_mismatch.js @@ -1,6 +1,6 @@ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); -const { key, cleartext, enums, PacketList, SignaturePacket } = openpgp; +const { key, cleartext, enums, packet: { List, Signature } } = openpgp; const chai = require('chai'); chai.use(require('chai-as-promised')); @@ -42,8 +42,8 @@ EnxUPL95HuMKoVkf4w== =oopr -----END PGP PRIVATE KEY BLOCK-----`; -module.exports = () => it('Does not accept message encrypted with algo not mentioned in preferred algorithms', async function() { - const message = await openpgp.readArmoredMessage(messageArmor); - const privKey = await openpgp.readArmoredKey(privateKeyArmor); +it('Does not accept message encrypted with algo not mentioned in preferred algorithms', async function() { + const message = await openpgp.message.readArmored(messageArmor); + const privKey = (await openpgp.key.readArmored(privateKeyArmor)).keys[0]; await expect(openpgp.decrypt({ message, privateKeys: [privKey] })).to.be.rejectedWith('A non-preferred symmetric algorithm was used.'); }); diff --git a/test/security/subkey_trust.js b/test/security/subkey_trust.js index ec97865f..601ee6b1 100644 --- a/test/security/subkey_trust.js +++ b/test/security/subkey_trust.js @@ -1,7 +1,6 @@ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); -const { readArmoredKey, Key, readArmoredCleartextMessage, CleartextMessage, enums, PacketList, SignaturePacket } = openpgp; -const key = require('../../src/key'); +const { key, cleartext, enums, packet: { List, Signature } } = openpgp; const chai = require('chai'); chai.use(require('chai-as-promised')); @@ -10,9 +9,8 @@ const expect = chai.expect; async function generateTestData() { const victimPrivKey = await key.generate({ - userIds: [{ name: 'Victim', email: 'victim@example.com' }], - type: 'rsa', - rsaBits: 1024, + userIds: ['Victim '], + rsaBits: openpgp.util.getWebCryptoAll() ? 2048 : 1024, subkeys: [{ sign: true }] @@ -20,15 +18,14 @@ async function generateTestData() { victimPrivKey.revocationSignatures = []; const attackerPrivKey = await key.generate({ - userIds: [{ name: 'Attacker', email: 'attacker@example.com' }], - type: 'rsa', - rsaBits: 1024, + userIds: ['Attacker '], + rsaBits: openpgp.util.getWebCryptoAll() ? 2048 : 1024, subkeys: [], sign: false }); attackerPrivKey.revocationSignatures = []; const signed = await openpgp.sign({ - message: await CleartextMessage.fromText('I am batman'), + message: await cleartext.fromText('I am batman'), privateKeys: victimPrivKey, streaming: false, armor: true @@ -51,13 +48,13 @@ async function testSubkeyTrust() { key: attackerPrivKey.toPublic().keyPacket, bind: pktPubVictim[3] // victim subkey }; - const fakeBindingSignature = new SignaturePacket(); - fakeBindingSignature.signatureType = enums.signature.subkeyBinding; + const fakeBindingSignature = new Signature(); + fakeBindingSignature.signatureType = enums.signature.subkey_binding; fakeBindingSignature.publicKeyAlgorithm = attackerPrivKey.keyPacket.algorithm; fakeBindingSignature.hashAlgorithm = enums.hash.sha256; - fakeBindingSignature.keyFlags = [enums.keyFlags.signData]; + fakeBindingSignature.keyFlags = [enums.keyFlags.sign_data]; await fakeBindingSignature.sign(attackerPrivKey.keyPacket, dataToSign); - const newList = new PacketList(); + const newList = new List(); newList.concat([ pktPrivAttacker[0], // attacker private key pktPrivAttacker[1], // attacker user @@ -65,10 +62,10 @@ async function testSubkeyTrust() { pktPubVictim[3], // victim subkey fakeBindingSignature // faked key binding ]); - let fakeKey = new Key(newList); - fakeKey = await readArmoredKey(await fakeKey.toPublic().armor()); + let fakeKey = new key.Key(newList); + fakeKey = (await key.readArmored(await fakeKey.toPublic().armor())).keys[0]; const verifyAttackerIsBatman = await openpgp.verify({ - message: (await readArmoredCleartextMessage(signed)), + message: (await cleartext.readArmored(signed.data)), publicKeys: fakeKey, streaming: false }); @@ -76,4 +73,4 @@ async function testSubkeyTrust() { expect(verifyAttackerIsBatman.signatures[0].valid).to.be.null; } -module.exports = () => it('Does not trust subkeys without Primary Key Binding Signature', testSubkeyTrust); +it('Does not trust subkeys without Primary Key Binding Signature', testSubkeyTrust); diff --git a/test/security/unsigned_subpackets.js b/test/security/unsigned_subpackets.js index 67cfac9e..9ea55056 100644 --- a/test/security/unsigned_subpackets.js +++ b/test/security/unsigned_subpackets.js @@ -1,6 +1,6 @@ -const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); -const { readArmoredKey, Key, message, enums, PacketList, SignaturePacket } = openpgp; +const { key, message, enums, packet: { List, Signature } } = openpgp; const chai = require('chai'); chai.use(require('chai-as-promised')); @@ -49,7 +49,7 @@ Dc2vwS83Aja9iWrIEg== -----END PGP PRIVATE KEY BLOCK-----`; async function getInvalidKey() { - return await readArmoredKey(INVALID_KEY); + return (await key.readArmored(INVALID_KEY)).keys[0]; } async function makeKeyValid() { /** @@ -70,25 +70,26 @@ async function makeKeyValid() { // deconstruct invalid key const [pubkey, puser, pusersig] = invalidkey.toPacketlist().map(i => i); // create a fake signature - const fake = new SignaturePacket(); + const fake = new Signature(); Object.assign(fake, pusersig); // extend expiration times fake.keyExpirationTime = 0x7FFFFFFF; fake.signatureExpirationTime = 0x7FFFFFFF; // add key capability - fake.keyFlags[0] |= enums.keyFlags.encryptCommunication; + fake.keyFlags[0] |= enums.keyFlags.encrypt_communication; // create modified subpacket data pusersig.read_sub_packets(fake.write_hashed_sub_packets(), false); // reconstruct the modified key - const newlist = new PacketList(); + const newlist = new List(); newlist.concat([pubkey, puser, pusersig]); - let modifiedkey = new Key(newlist); + let modifiedkey = new key.Key(newlist); // re-read the message to eliminate any // behaviour due to cached values. - modifiedkey = await readArmoredKey(await modifiedkey.armor()); + modifiedkey = (await key.readArmored( + await modifiedkey.armor())).keys[0]; expect(await encryptFails(invalidkey)).to.be.true; expect(await encryptFails(modifiedkey)).to.be.true; } -module.exports = () => it('Does not accept unsigned subpackets', makeKeyValid); +it('Does not accept unsigned subpackets', makeKeyValid); diff --git a/test/typescript/definitions.ts b/test/typescript/definitions.ts deleted file mode 100644 index 86a0c180..00000000 --- a/test/typescript/definitions.ts +++ /dev/null @@ -1,117 +0,0 @@ -/** - * npm run-script test-type-definitions - * - * If types are off, either this will fail to build with TypeScript, or it will fail to run. - * - if it fails to build, edit the file to match type definitions - * - if it fails to run, edit this file to match the actual library API, then edit the definitions file (openpgp.d.ts) accordingly. - */ - -import { generateKey, readArmoredKey, readArmoredKeys, Key, readMessage, readArmoredMessage, Message, CleartextMessage, encrypt, decrypt, sign, verify } from '../..'; -import { expect } from 'chai'; - -(async () => { - - // Generate keys - const { publicKeyArmored, key } = await generateKey({ userIds: [{ email: "user@corp.co" }] }); - expect(key).to.be.instanceOf(Key); - const privateKeys = [key]; - const publicKeys = [key.toPublic()]; - - // Parse keys - expect(await readArmoredKey(publicKeyArmored)).to.be.instanceOf(Key); - expect(await readArmoredKeys(publicKeyArmored)).to.have.lengthOf(1); - - // Encrypt text message (armored) - const text = 'hello'; - const textMessage = Message.fromText('hello'); - const encryptedArmor: string = await encrypt({ publicKeys, message: textMessage }); - expect(encryptedArmor).to.include('-----BEGIN PGP MESSAGE-----'); - - // Encrypt binary message (unarmored) - const binary = new Uint8Array(2); - binary[0] = 1; - binary[1] = 2; - const binaryMessage = Message.fromBinary(binary); - const encryptedBinary: Uint8Array = await encrypt({ publicKeys, message: binaryMessage, armor: false }); - expect(encryptedBinary).to.be.instanceOf(Uint8Array); - - // Decrypt text message (armored) - const encryptedTextMessage = await readArmoredMessage(encryptedArmor); - const decryptedText = await decrypt({ privateKeys, message: encryptedTextMessage }); - const decryptedTextData: string = decryptedText.data; - expect(decryptedTextData).to.equal(text); - - // Decrypt binary message (unarmored) - const encryptedBinaryMessage = await readMessage(encryptedBinary); - const decryptedBinary = await decrypt({ privateKeys, message: encryptedBinaryMessage, format: 'binary' }); - const decryptedBinaryData: Uint8Array = decryptedBinary.data; - expect(decryptedBinaryData).to.deep.equal(binary); - - // Encrypt message (inspect packets) - const encryptedMessage = await readMessage(encryptedBinary); - expect(encryptedMessage).to.be.instanceOf(Message); - - // Sign cleartext message (armored) - const cleartextMessage = CleartextMessage.fromText('hello'); - const clearSignedArmor = await sign({ privateKeys, message: cleartextMessage }); - expect(clearSignedArmor).to.include('-----BEGIN PGP SIGNED MESSAGE-----'); - - // Sign text message (armored) - const textSignedArmor: string = await sign({ privateKeys, message: textMessage }); - expect(textSignedArmor).to.include('-----BEGIN PGP MESSAGE-----'); - - // Sign text message (unarmored) - const textSignedBinary: Uint8Array = await sign({ privateKeys, message: binaryMessage, armor: false }); - expect(textSignedBinary).to.be.instanceOf(Uint8Array); - - // Verify signed text message (armored) - const signedMessage = await readArmoredMessage(textSignedArmor); - const verifiedText = await verify({ publicKeys, message: signedMessage }); - const verifiedTextData: string = verifiedText.data; - expect(verifiedTextData).to.equal(text); - - // Verify signed binary message (unarmored) - const message = await readMessage(textSignedBinary); - const verifiedBinary = await verify({ publicKeys, message, format: 'binary' }); - const verifiedBinaryData: Uint8Array = verifiedBinary.data; - expect(verifiedBinaryData).to.deep.equal(binary); - - // // Detached - sign cleartext message (armored) - // import { Message, sign } from 'openpgp'; - // const message = Message.fromText(util.removeTrailingSpaces(text)); - // const signed = await sign({ privateKeys, message, detached: true }); - // console.log(signed); // String - - // // Detached - sign binary message (unarmored) - // const message = Message.fromText(text); - // const signed = await sign({ privateKeys, message, detached: true, armor: false }); - // console.log(signed); // Uint8Array - - // // Encrypt session keys (armored) - // const encrypted = await encryptSessionKey({ publicKeys, data, algorithm }); - // console.log(encrypted); // String - - // // Encrypt session keys (unarmored) - // const encrypted = await encryptSessionKey({ publicKeys, data, algorithm, armor: false }); - // console.log(encrypted); // Uint8Array - - // // Streaming - encrypt text message on Node.js (armored) - // const data = fs.createReadStream(filename, { encoding: 'utf8' }); - // const message = Message.fromText(data); - // const encrypted = await encrypt({ publicKeys, message }); - // encrypted.on('data', chunk => { - // console.log(chunk); // String - // }); - - // // Streaming - encrypt binary message on Node.js (unarmored) - // const data = fs.createReadStream(filename); - // const message = Message.fromBinary(data); - // const encrypted = await encrypt({ publicKeys, message, armor: false }); - // encrypted.pipe(targetStream); - - console.log('TypeScript definitions are correct'); -})().catch(e => { - console.error('TypeScript definitions tests failed by throwing the following error'); - console.error(e); - process.exit(1); -}); diff --git a/test/unittests.html b/test/unittests.html index c1182783..292bd165 100644 --- a/test/unittests.html +++ b/test/unittests.html @@ -3,48 +3,24 @@ OpenPGPJS Unit Tests - + - + +
- + diff --git a/test/unittests.js b/test/unittests.js index 1296da9c..c639037c 100644 --- a/test/unittests.js +++ b/test/unittests.js @@ -1,4 +1,13 @@ -(typeof window !== 'undefined' ? window : global).globalThis = (typeof window !== 'undefined' ? window : global); +// Old browser polyfills +if (typeof Symbol === 'undefined') { + require('core-js/fn/symbol'); +} +if (typeof Promise === 'undefined') { + require('core-js/fn/promise'); +} +if (typeof TransformStream === 'undefined') { + require('@mattiasbuelens/web-streams-polyfill'); +} (typeof window !== 'undefined' ? window : global).resolves = function(val) { return new Promise(function(res) { res(val); }); @@ -27,21 +36,29 @@ describe('Unit Tests', function () { if (typeof window !== 'undefined') { - openpgp.config.s2kIterationCountByte = 0; + openpgp.config.s2k_iteration_count_byte = 0; + openpgp.config.indutny_elliptic_path = '../dist/elliptic.min.js'; + + afterEach(function () { + if (window.scrollY >= document.body.scrollHeight - window.innerHeight - 100 + || openpgp.config.ci) { + window.scrollTo(0, document.body.scrollHeight); + } + }); window.location.search.substr(1).split('&').forEach(param => { const [key, value] = param.split('='); if (key && key !== 'grep') { openpgp.config[key] = decodeURIComponent(value); try { - openpgp.config[key] = window.eval(openpgp.config[key]); - } catch (e) {} + openpgp.config[key] = eval(openpgp.config[key]); + } catch(e) {} } }); } - require('./crypto')(); - require('./general')(); - require('./worker')(); - require('./security')(); + require('./crypto'); + require('./general'); + require('./worker'); + require('./security'); }); diff --git a/test/worker/application_worker.js b/test/worker/application_worker.js index 574e7c93..2b1bc425 100644 --- a/test/worker/application_worker.js +++ b/test/worker/application_worker.js @@ -4,19 +4,15 @@ const chai = require('chai'); const { expect } = chai; -/* eslint-disable no-invalid-this */ -module.exports = () => tryTests('Application Worker', tests, { +tryTests('Application Worker', tests, { if: typeof window !== 'undefined' && window.Worker && window.MessageChannel }); function tests() { it('Should support loading OpenPGP.js from inside a Web Worker', async function() { - if (/Edge/.test(navigator.userAgent)) { - this.skip(); // Old Edge doesn't support crypto.getRandomValues inside a Worker. - } try { - globalThis.eval('(async function() {})'); + eval('(async function() {})'); } catch (e) { console.error(e); this.skip(); diff --git a/test/worker/async_proxy.js b/test/worker/async_proxy.js new file mode 100644 index 00000000..bedc9739 --- /dev/null +++ b/test/worker/async_proxy.js @@ -0,0 +1,61 @@ +/* globals tryTests: true */ + +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); + +const chai = require('chai'); + +const { expect } = chai; + +const pub_key = + ['-----BEGIN PGP PUBLIC KEY BLOCK-----', + 'Version: GnuPG v2.0.19 (GNU/Linux)', + '', + 'mI0EUmEvTgEEANyWtQQMOybQ9JltDqmaX0WnNPJeLILIM36sw6zL0nfTQ5zXSS3+', + 'fIF6P29lJFxpblWk02PSID5zX/DYU9/zjM2xPO8Oa4xo0cVTOTLj++Ri5mtr//f5', + 'GLsIXxFrBJhD/ghFsL3Op0GXOeLJ9A5bsOn8th7x6JucNKuaRB6bQbSPABEBAAG0', + 'JFRlc3QgTWNUZXN0aW5ndG9uIDx0ZXN0QGV4YW1wbGUuY29tPoi5BBMBAgAjBQJS', + 'YS9OAhsvBwsJCAcDAgEGFQgCCQoLBBYCAwECHgECF4AACgkQSmNhOk1uQJQwDAP6', + 'AgrTyqkRlJVqz2pb46TfbDM2TDF7o9CBnBzIGoxBhlRwpqALz7z2kxBDmwpQa+ki', + 'Bq3jZN/UosY9y8bhwMAlnrDY9jP1gdCo+H0sD48CdXybblNwaYpwqC8VSpDdTndf', + '9j2wE/weihGp/DAdy/2kyBCaiOY1sjhUfJ1GogF49rC4jQRSYS9OAQQA6R/PtBFa', + 'JaT4jq10yqASk4sqwVMsc6HcifM5lSdxzExFP74naUMMyEsKHP53QxTF0Grqusag', + 'Qg/ZtgT0CN1HUM152y7ACOdp1giKjpMzOTQClqCoclyvWOFB+L/SwGEIJf7LSCEr', + 'woBuJifJc8xAVr0XX0JthoW+uP91eTQ3XpsAEQEAAYkBPQQYAQIACQUCUmEvTgIb', + 'LgCoCRBKY2E6TW5AlJ0gBBkBAgAGBQJSYS9OAAoJEOCE90RsICyXuqIEANmmiRCA', + 'SF7YK7PvFkieJNwzeK0V3F2lGX+uu6Y3Q/Zxdtwc4xR+me/CSBmsURyXTO29OWhP', + 'GLszPH9zSJU9BdDi6v0yNprmFPX/1Ng0Abn/sCkwetvjxC1YIvTLFwtUL/7v6NS2', + 'bZpsUxRTg9+cSrMWWSNjiY9qUKajm1tuzPDZXAUEAMNmAN3xXN/Kjyvj2OK2ck0X', + 'W748sl/tc3qiKPMJ+0AkMF7Pjhmh9nxqE9+QCEl7qinFqqBLjuzgUhBU4QlwX1GD', + 'AtNTq6ihLMD5v1d82ZC7tNatdlDMGWnIdvEMCv2GZcuIqDQ9rXWs49e7tq1NncLY', + 'hz3tYjKhoFTKEIq3y3Pp', + '=h/aX', + '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); + +const plaintext = 'short message\nnext line\n한국어/조선말'; +let pubKey; + +tryTests('Async Proxy', tests, { + if: typeof window !== 'undefined' && window.Worker && window.MessageChannel, + before: async function() { + await openpgp.initWorker({ path:'../dist/openpgp.worker.js' }); + pubKey = (await openpgp.key.readArmored(pub_key)).keys[0]; + }, + after: async function() { + await openpgp.destroyWorker(); + } +}); + +function tests() { + + describe('Random number pipeline', function() { + it('Random number buffer automatically reseeded', async function() { + const worker = new Worker('../dist/openpgp.worker.js'); + const wProxy = new openpgp.AsyncProxy({ path:'../dist/openpgp.worker.js', workers: [worker] }); + const loaded = await wProxy.loaded(); + if (loaded) { + return wProxy.delegate('encrypt', { publicKeys:[pubKey], message:openpgp.message.fromText(plaintext) }); + } + }); + }); + +} diff --git a/test/worker/index.js b/test/worker/index.js index 51683a8e..903babbc 100644 --- a/test/worker/index.js +++ b/test/worker/index.js @@ -1,4 +1,5 @@ -module.exports = () => describe('Web Worker', function () { - require('./application_worker.js')(); +describe('Web Worker', function () { + require('./async_proxy.js'); + require('./application_worker.js'); }); diff --git a/test/worker/worker_example.js b/test/worker/worker_example.js index 5b992304..b935ece5 100644 --- a/test/worker/worker_example.js +++ b/test/worker/worker_example.js @@ -42,25 +42,25 @@ onmessage = async function({ data: { action, message }, ports: [port] }) { let result; switch (action) { case 'encrypt': { - const publicKey = await openpgp.readArmoredKey(publicKeyArmored); - const privateKey = await openpgp.readArmoredKey(privateKeyArmored); - await privateKey.decrypt('test'); - const data = await openpgp.encrypt({ - message: openpgp.Message.fromText(message), - publicKeys: publicKey, - privateKeys: privateKey + const { keys: publicKeys } = await openpgp.key.readArmored(publicKeyArmored); + const { keys: privateKeys } = await openpgp.key.readArmored(privateKeyArmored); + await privateKeys[0].decrypt('test'); + const { data } = await openpgp.encrypt({ + message: openpgp.message.fromText(message), + publicKeys, + privateKeys }); result = data; break; } case 'decrypt': { - const publicKey = await openpgp.readArmoredKey(publicKeyArmored); - const privateKey = await openpgp.readArmoredKey(privateKeyArmored); - await privateKey.decrypt('test'); + const { keys: publicKeys } = await openpgp.key.readArmored(publicKeyArmored); + const { keys: privateKeys } = await openpgp.key.readArmored(privateKeyArmored); + await privateKeys[0].decrypt('test'); const { data, signatures } = await openpgp.decrypt({ - message: await openpgp.readArmoredMessage(message), - publicKeys: publicKey, - privateKeys: privateKey + message: await openpgp.message.readArmored(message), + publicKeys, + privateKeys }); if (!signatures[0].valid) { throw new Error("Couldn't veriy signature"); diff --git a/travis.sh b/travis.sh index bac508ed..9e4017f8 100755 --- a/travis.sh +++ b/travis.sh @@ -4,27 +4,19 @@ set -e if [ $OPENPGPJSTEST = "coverage" ]; then echo "Running OpenPGP.js unit tests on node.js with code coverage." - npm run coverage + grunt coverage codeclimate-test-reporter < coverage/lcov.info -elif [ $OPENPGPJSTEST = "lint" ]; then - echo "Running OpenPGP.js eslint." - npm run lint - -elif [ $OPENPGPJSTEST = "test-type-definitions" ]; then - echo "Testing OpenPGP.js type definitions." - npm run test-type-definitions - elif [ $OPENPGPJSTEST = "unit" ]; then echo "Running OpenPGP.js unit tests on node.js." - npm test ${LIGHTWEIGHT+ -- --grep lightweight} + grunt build test --lightweight=$LIGHTWEIGHT elif [ $OPENPGPJSTEST = "browserstack" ]; then echo "Running OpenPGP.js browser unit tests on Browserstack." - npm run build-test + grunt build browserify:unittests copy:browsertest --compat=$COMPAT echo -n "Using config: " - echo "{\"browsers\": [$BROWSER], \"test_framework\": \"mocha\", \"test_path\": [\"test/unittests.html?ci=true${LIGHTWEIGHT+&lightweight=true&grep=lightweight}\"], \"timeout\": 1800, \"exit_with_fail\": true, \"project\": \"openpgpjs/${TRAVIS_EVENT_TYPE:-push}${LIGHTWEIGHT:+/lightweight}\"}" > browserstack.json + echo "{\"browsers\": [$BROWSER], \"test_framework\": \"mocha\", \"test_path\": [\"test/unittests.html?ci=true\"], \"timeout\": 1800, \"exit_with_fail\": true, \"project\": \"openpgpjs/${TRAVIS_EVENT_TYPE:-push}${COMPAT:+/compat}${LIGHTWEIGHT:+/lightweight}\"}" > browserstack.json cat browserstack.json result=0