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/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', + '', + 'mQENBFqm7EoBCAC9MNVwQqdpz9bQK9fpKGLSPk20ckRvvTwq7oDUM1IMZGb4bd/A', + '3KDi9SoBU4oEFRbCAk3rxj8iGL9pxvsX3szyCEXuWfzilAQ/1amRe2woMst+YkTt', + 'x9rzkRiQta4T1fNlqQsJyIIpHrKAFGPp0UjBTr6Vs+Ti6JkF5su4ea+yEiuBHtY4', + 'NjFb1xuV7OyAsZToh0B5fah4/WZ5Joyt9h8gp4WGSvdhLbdsoo8Tjveh2G+Uy2qC', + 'mM8h5v9qGBBRyGM9QmAlhn9XtvEODGbSPYVijEUu8QmbUwHqONAN4fCKAM+jHPuA', + 'rFG+si6QNEk3SOhXX3nvu9ThXg/gKZmmX5ABABEBAAGJATYEIAEKACAWIQQuQVvj', + 'r/N2jaYeuSDeEOPy1UVxbwUCWqbs0wIdAQAKCRDeEOPy1UVxb8CyCACyEagyyvmg', + 'kmS8pEI+iJQU/LsfnNPHwYrDOm0NodGw8HYkil2kfWJim60vFPC3jozzFmvlfy5z', + 'VAge9sVUl3sk7HxnYdPmK767h5Skp6dQSBeeh5WfH4ZK+hhJt9vJTstzaAhVNkX1', + '5OPBfkpy9pbYblQj56g0ECF4UhUxGFVZfycy+i6jvTpk+ABHWDKdqoKj9pTOzwDV', + 'JVa6Y0UT76PMIDjkeDKUYTU6MHexN1oyC07IYh+HsZtlsPTs/zo1JsrO+D6aEkEg', + 'yoLStyg0uemr6LRQ5YuhpG7OMeGRgJstCSo22JHJEtpSUR688aHKN35KNmxjkJDi', + 'fL7cRKHLlqqKtBlTdW5ueSA8c3VubnlAc3Vubnkuc3Vubnk+iQFUBBMBCgA+FiEE', + 'LkFb46/zdo2mHrkg3hDj8tVFcW8FAlqm7EoCGwMFCQeGH4AFCwkIBwMFFQoJCAsF', + 'FgIDAQACHgECF4AACgkQ3hDj8tVFcW83pgf6Auezf0G4MR8/jfHTshYRO8uGdTVR', + 'PjSmczyk4UAk3xy2dZuVc4CathVs/ID3QhycurL33fiZntx+p3JKUrypnp2Y+ZXW', + 'q4xjL05yirDFq4WGgksovmP4q1NfNB3YIsNulHMJ/qCOHl6d+oIDIKF/udwr0+qf', + 'rhd1rMFqO5lAF5/kSBbRdCCLpvMIWKxvDkbZrsqvWcchP5nuymhVHn9cCVGdxsZ8', + 'a/1iODFsBTDF4LISX2Tk1AW5thT96erbvq9XOluDFNjZY9dc6/JWmyWBvLTNguGV', + 'rx0bydeGaddfZc+3XkpImKrpckz5gwYvkgu6bm7GroERjEeYzQDLsg2L07kBDQRa', + 'puxKAQgApxDXRk9YUQ2Ba9QVe8WW/NSmyYQEvtSuvG86nZn5aMiZkEuDpVcmePOS', + '1u6Pz0RB9k1WzAi6Az2l/fS7xSbzjDPV+VXV704t9r0M3Zr5RMzIRjbGoxaZp7Tv', + 'Da3QGN4VIZN6o4oAJM7G2FeZMstnCDxrT3wyKXaEdOn5Uc6hxl2Bhx2gTCpsTFn8', + 'AaBnSY6+kge6rCkeufndXQUhTVy8dYsaSqGwpQHVtk1X4nDoZlCC929F9d3I2/WV', + 'OGlfHqbpbO+8tprvQS0JSzsa9w7xaGJcYUA2tOWV8ZZgC8/1MHMsj5HhHKmmWPsS', + 's6k6RLg3nP+CV9zkKn4sI+l/erOEaQARAQABiQE8BBgBCgAmFiEELkFb46/zdo2m', + 'Hrkg3hDj8tVFcW8FAlqm7EoCGwwFCQeGH4AACgkQ3hDj8tVFcW/lmwgAs3o/b24U', + 't2jioTzjZNFMrqjc99PpURJ9BhKPqa9RF7HrpM4x2mJEFw0fUZGQpQmL5NP0ZazE', + 'N47YHwZH1O5i4t5HtFmjtmMkJUFPlwy0MrClW+OVu6Wl7rtYuXIBqFouUx1YBZtQ', + 'isAmwBeB6DS8Oc39raZpoHh9lGPN1Kmp6iLX3xq+6IqUEV567vSAAR6N2m18GH79', + '365Dq88eZKS/NtlzFnEzoThYlIVt/dknuQsUSdZHtuBbNXaJ816byVZQQWdqnXd5', + 'BIDZSFjrJY/gm2kgQX2Pn9hGqDdGhxiALjxhA0+OJQNw4v11y0zVGdofh0IHjkcZ', + '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)', - '', - 'lQH+BFJhL04BBADclrUEDDsm0PSZbQ6pml9FpzTyXiyCyDN+rMOsy9J300Oc10kt', - '/nyBej9vZSRcaW5VpNNj0iA+c1/w2FPf84zNsTzvDmuMaNHFUzky4/vkYuZra//3', - '+Ri7CF8RawSYQ/4IRbC9zqdBlzniyfQOW7Dp/LYe8eibnDSrmkQem0G0jwARAQAB', - '/gMDAu7L//czBpE40p1ZqO8K3k7UejemjsQqc7kOqnlDYd1Z6/3NEA/UM30Siipr', - 'KjdIFY5+hp0hcs6EiiNq0PDfm/W2j+7HfrZ5kpeQVxDek4irezYZrl7JS2xezaLv', - 'k0Fv/6fxasnFtjOM6Qbstu67s5Gpl9y06ZxbP3VpT62+Xeibn/swWrfiJjuGEEhM', - 'bgnsMpHtzAz/L8y6KSzViG/05hBaqrvk3/GeEA6nE+o0+0a6r0LYLTemmq6FbaA1', - 'PHo+x7k7oFcBFUUeSzgx78GckuPwqr2mNfeF+IuSRnrlpZl3kcbHASPAOfEkyMXS', - 'sWGE7grCAjbyQyM3OEXTSyqnehvGS/1RdB6kDDxGwgE/QFbwNyEh6K4eaaAThW2j', - 'IEEI0WEnRkPi9fXyxhFsCLSI1XhqTaq7iDNqJTxE+AX2b9ZuZXAxI3Tc/7++vEyL', - '3p18N/MB2kt1Wb1azmXWL2EKlT1BZ5yDaJuBQ8BhphM3tCRUZXN0IE1jVGVzdGlu', - 'Z3RvbiA8dGVzdEBleGFtcGxlLmNvbT6IuQQTAQIAIwUCUmEvTgIbLwcLCQgHAwIB', - 'BhUIAgkKCwQWAgMBAh4BAheAAAoJEEpjYTpNbkCUMAwD+gIK08qpEZSVas9qW+Ok', - '32wzNkwxe6PQgZwcyBqMQYZUcKagC8+89pMQQ5sKUGvpIgat42Tf1KLGPcvG4cDA', - 'JZ6w2PYz9YHQqPh9LA+PAnV8m25TcGmKcKgvFUqQ3U53X/Y9sBP8HooRqfwwHcv9', - 'pMgQmojmNbI4VHydRqIBePawnQH+BFJhL04BBADpH8+0EVolpPiOrXTKoBKTiyrB', - 'UyxzodyJ8zmVJ3HMTEU/vidpQwzISwoc/ndDFMXQauq6xqBCD9m2BPQI3UdQzXnb', - 'LsAI52nWCIqOkzM5NAKWoKhyXK9Y4UH4v9LAYQgl/stIISvCgG4mJ8lzzEBWvRdf', - 'Qm2Ghb64/3V5NDdemwARAQAB/gMDAu7L//czBpE40iPcpLzL7GwBbWFhSWgSLy53', - 'Md99Kxw3cApWCok2E8R9/4VS0490xKZIa5y2I/K8thVhqk96Z8Kbt7MRMC1WLHgC', - 'qJvkeQCI6PrFM0PUIPLHAQtDJYKtaLXxYuexcAdKzZj3FHdtLNWCooK6n3vJlL1c', - 'WjZcHJ1PH7USlj1jup4XfxsbziuysRUSyXkjn92GZLm+64vCIiwhqAYoizF2NHHG', - 'hRTN4gQzxrxgkeVchl+ag7DkQUDANIIVI+A63JeLJgWJiH1fbYlwESByHW+zBFNt', - 'qStjfIOhjrfNIc3RvsggbDdWQLcbxmLZj4sB0ydPSgRKoaUdRHJY0S4vp9ouKOtl', - '2au/P1BP3bhD0fDXl91oeheYth+MSmsJFDg/vZJzCJhFaQ9dp+2EnjN5auNCNbaI', - 'beFJRHFf9cha8p3hh+AK54NRCT++B2MXYf+TPwqX88jYMBv8kk8vYUgo8128r1zQ', - 'EzjviQE9BBgBAgAJBQJSYS9OAhsuAKgJEEpjYTpNbkCUnSAEGQECAAYFAlJhL04A', - 'CgkQ4IT3RGwgLJe6ogQA2aaJEIBIXtgrs+8WSJ4k3DN4rRXcXaUZf667pjdD9nF2', - '3BzjFH6Z78JIGaxRHJdM7b05aE8YuzM8f3NIlT0F0OLq/TI2muYU9f/U2DQBuf+w', - 'KTB62+PELVgi9MsXC1Qv/u/o1LZtmmxTFFOD35xKsxZZI2OJj2pQpqObW27M8Nlc', - 'BQQAw2YA3fFc38qPK+PY4rZyTRdbvjyyX+1zeqIo8wn7QCQwXs+OGaH2fGoT35AI', - 'SXuqKcWqoEuO7OBSEFThCXBfUYMC01OrqKEswPm/V3zZkLu01q12UMwZach28QwK', - '/YZly4ioND2tdazj17u2rU2dwtiHPe1iMqGgVMoQirfLc+k=', - '=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)', - '', - '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'); + 'Version: Encryption Desktop 10.3.0 (Build 9307)', + '', + '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', + '=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