diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..baccf3a6 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,363 @@ +module.exports = { + "extends": "airbnb-base", + "parserOptions": { "sourceType": "module" }, + + "env": { + "browser": true, + "es6": true, + "node": true + }, + + "globals": { // TODO are all these necessary? + "console": true, + "Promise": true, + "importScripts": true, + "process": true, + "Event": true, + "describe": true, + "it": true, + "sinon": true, + "mocha": true, + "before": true, + "beforeEach": true, + "after": true, + "afterEach": true, + "escape": true, + "unescape": true, + "postMessage": true, + "resolves": true, + "rejects": true + }, + + "rules": { + // Auto generated rules: + "accessor-pairs": "error", + "array-bracket-newline": "error", + "array-bracket-spacing": [ + "error", + "never" + ], + "array-callback-return": "error", + "array-element-newline": "off", + "arrow-body-style": "off", + "arrow-parens": [ + "error", + "as-needed" + ], + "arrow-spacing": [ + "error", + { + "after": true, + "before": true + } + ], + "block-scoped-var": "off", + "block-spacing": [ + "error", + "always" + ], + "brace-style": "off", + "callback-return": "error", + "camelcase": [ + "error", + { + "properties": "never" + } + ], + "capitalized-comments": "off", + "class-methods-use-this": "error", + "comma-dangle": [ "error", "never" ], + "comma-spacing": "off", + "comma-style": [ + "error", + "last" + ], + "complexity": "off", + "computed-property-spacing": [ + "error", + "never" + ], + "consistent-return": "off", + "consistent-this": "error", + "curly": "error", + "default-case": "off", + "dot-location": "error", + "dot-notation": [ + "error", + { + "allowKeywords": true + } + ], + "eol-last": "off", + "eqeqeq": "error", + "for-direction": "error", + "func-call-spacing": "error", + "func-name-matching": "error", + "func-names": [ + "error", + "never" + ], + "func-style": "off", + "function-paren-newline": "off", + "generator-star-spacing": "error", + "getter-return": "error", + "global-require": "off", + "guard-for-in": "off", + "handle-callback-err": "error", + "id-blacklist": "error", + "id-length": "off", + "id-match": "error", + "implicit-arrow-linebreak": [ + "error", + "beside" + ], + "init-declarations": "off", + "jsx-quotes": "error", + "key-spacing": "off", + "keyword-spacing": "off", + "line-comment-position": "off", + "linebreak-style": [ + "error", + "unix" + ], + "lines-around-comment": "off", + "lines-around-directive": "error", + "lines-between-class-members": "error", + "max-depth": "off", + "max-len": "off", + "max-lines": "off", + "max-nested-callbacks": "error", + "max-params": "off", + "max-statements": "off", + "max-statements-per-line": "off", + "multiline-comment-style": "off", + "multiline-ternary": "off", + "new-parens": "error", + "newline-after-var": "off", + "newline-before-return": "off", + "newline-per-chained-call": "off", + "no-alert": "error", + "no-array-constructor": "error", + "no-await-in-loop": "error", + "no-bitwise": "off", + "no-buffer-constructor": "error", + "no-caller": "error", + "no-catch-shadow": "error", + "no-confusing-arrow": "error", + "no-continue": "off", + "no-div-regex": "error", + "no-duplicate-imports": "error", + "no-else-return": "off", + "no-empty": [ + "error", + { + "allowEmptyCatch": true + } + ], + "no-empty-function": "off", + "no-eq-null": "error", + "no-eval": "error", + "no-extend-native": "error", + "no-extra-bind": "error", + "no-extra-label": "error", + "no-extra-parens": "off", + "no-floating-decimal": "error", + "no-implicit-globals": "error", + "no-implied-eval": "error", + "no-inline-comments": "off", + "no-inner-declarations": [ + "error", + "functions" + ], + "no-invalid-this": "error", + "no-iterator": "error", + "no-label-var": "error", + "no-labels": "error", + "no-lone-blocks": "error", + "no-lonely-if": "error", + "no-loop-func": "error", + "no-magic-numbers": "off", + "no-mixed-operators": "off", + "no-mixed-requires": "error", + "no-multi-assign": "error", + "no-multi-spaces": [ + "error", + { + "ignoreEOLComments": true + } + ], + "no-multi-str": "error", + "no-multiple-empty-lines": "error", + "no-native-reassign": "error", + "no-negated-condition": "off", + "no-negated-in-lhs": "error", + "no-nested-ternary": "off", + "no-new": "error", + "no-new-func": "error", + "no-new-object": "error", + "no-new-require": "error", + "no-new-wrappers": "error", + "no-octal-escape": "error", + "no-param-reassign": "off", + "no-path-concat": "error", + "no-plusplus": "off", + "no-process-env": "error", + "no-process-exit": "error", + "no-proto": "error", + "no-prototype-builtins": "off", + "no-restricted-globals": "error", + "no-restricted-imports": "error", + "no-restricted-modules": "error", + "no-restricted-properties": "error", + "no-restricted-syntax": "error", + "no-return-assign": "error", + "no-return-await": "error", + "no-script-url": "error", + "no-self-compare": "error", + "no-shadow": "off", + "no-shadow-restricted-names": "error", + "no-spaced-func": "error", + "no-sync": "error", + "no-tabs": "error", + "no-template-curly-in-string": "error", + "no-ternary": "off", + "no-throw-literal": "error", + "no-undef-init": "error", + "no-undefined": "off", + "no-unmodified-loop-condition": "error", + "no-unneeded-ternary": [ + "error", + { + "defaultAssignment": true + } + ], + "no-unused-expressions": "error", + "no-use-before-define": "off", + "no-useless-call": "error", + "no-useless-computed-key": "error", + "no-useless-concat": "error", + "no-useless-constructor": "error", + "no-useless-rename": "error", + "no-useless-return": "error", + "no-var": "off", + "no-void": "error", + "no-warning-comments": "off", + "no-whitespace-before-property": "error", + "no-with": "error", + "nonblock-statement-body-position": "error", + "object-curly-newline": "off", + "object-curly-spacing": "off", + "object-property-newline": [ + "error", + { + "allowMultiplePropertiesPerLine": true + } + ], + "object-shorthand": "off", + "one-var": "off", + "one-var-declaration-per-line": [ + "error", + "initializations" + ], + "operator-assignment": "off", + "operator-linebreak": [ + "error", + "after" + ], + "padded-blocks": "off", + "padding-line-between-statements": "error", + "prefer-arrow-callback": "off", + "prefer-const": "off", + "prefer-destructuring": "off", + "prefer-numeric-literals": "error", + "prefer-promise-reject-errors": "error", + "prefer-reflect": "off", + "prefer-rest-params": "off", + "prefer-spread": "off", + "prefer-template": "off", + "quote-props": "off", + "quotes": "off", + "radix": [ + "error", + "as-needed" + ], + "require-await": "error", + "require-jsdoc": "off", + "rest-spread-spacing": "error", + "semi": "off", + "semi-spacing": [ + "error", + { + "after": true, + "before": false + } + ], + "semi-style": [ + "error", + "last" + ], + "sort-imports": "off", + "sort-keys": "off", + "sort-vars": "off", + "space-before-blocks": "off", + "space-before-function-paren": "off", + "space-in-parens": [ + "error", + "never" + ], + "space-infix-ops": "off", + "space-unary-ops": "error", + "spaced-comment": "off", + "strict": "off", + "switch-colon-spacing": "error", + "symbol-description": "error", + "template-curly-spacing": "error", + "template-tag-spacing": "error", + "unicode-bom": [ + "error", + "never" + ], + "valid-jsdoc": "off", + "vars-on-top": "off", + "wrap-iife": "error", + "wrap-regex": "off", + "yield-star-spacing": "error", + "yoda": [ + "error", + "never" + ], + + // Custom silencers: + "camelcase": 0, + "no-debugger": 0, + "require-await": 0, + "no-multi-assign": 0, + "no-underscore-dangle": 0, + "no-restricted-syntax": 0, + "one-var-declaration-per-line": 0, + + // Custom errors: + "no-undef": 2, + "no-trailing-spaces": 2, + "no-mixed-operators": [ 2, {"groups": [["&", "|", "^", "~", "<<", ">>", ">>>"], ["&&", "||"]]}], + "no-use-before-define": [ 2, { "functions": false, "classes": true, "variables": false }], + "no-unused-expressions": [ 2, { "allowShortCircuit": true } ], + + // Custom warnings: + "no-console": 0, + "no-unused-vars": 0, + "indent": [ 0, 2, { "SwitchCase": 1 } ], + + // TODO Consider fixing these: + "new-cap": [ 0, { "properties": false, "capIsNewExceptionPattern": "^type_.*" }], + "no-lonely-if": 0, + "no-fallthrough": 0, + "no-invalid-this": 0, + "import/extensions": 0, + "no-useless-escape": 0, + "no-array-constructor": 0, + "no-constant-condition": 0, + "no-buffer-constructor": 0, // deprecated + "no-restricted-properties": 0 // Math.pow + } +}; diff --git a/.jscsrc b/.jscsrc deleted file mode 100644 index 7d15b2b9..00000000 --- a/.jscsrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "disallowTrailingWhitespace": true, - "validateIndentation": 2 -} \ No newline at end of file diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 5091d409..00000000 --- a/.jshintrc +++ /dev/null @@ -1,36 +0,0 @@ -{ - "node": true, - "browser": true, - "nonew": true, - "curly": true, - "eqeqeq": true, - "immed": true, - "regexp": true, - "evil": true, - "eqnull": true, - "expr": true, - "undef": true, - "unused": true, - "esnext": true, - - "globals": { - "console": true, - "Promise": true, - "importScripts": true, - "process": true, - "Event": true, - "describe": true, - "it": true, - "sinon": true, - "mocha": true, - "before": true, - "beforeEach": true, - "after": true, - "afterEach": true, - "escape": true, - "unescape": true, - "postMessage": true, - "resolves": true, - "rejects": true - } -} \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js index 50aef8cf..12a980e3 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -15,14 +15,15 @@ module.exports = function(grunt) { 'src/crypto/public_key/elgamal.js', 'src/crypto/public_key/index.js', 'src/crypto/public_key/rsa.js', + 'src/crypto/public_key/elliptic/*.js', 'src/crypto/*.js', 'src/encoding/**/*.js', 'src/hkp/**/*.js', 'src/keyring/**/*.js', - 'src/packet/**/*.jss', + 'src/packet/**/*.js', 'src/type/**/*.js', 'src/worker/**/*.js', - 'src/*.js', + 'src/*.js' ]; // add more over time ... goal should be 100% coverage var version = grunt.option('release'); @@ -43,52 +44,72 @@ module.exports = function(grunt) { browserify: { openpgp: { files: { - 'dist/openpgp.js': [ './src/index.js' ] + 'dist/openpgp.js': ['./src/index.js'] }, options: { browserifyOptions: { standalone: 'openpgp' }, - external: [ 'crypto', 'buffer', 'node-localstorage', 'node-fetch' ], + // Don't bundle these packages with openpgp.js + external: ['crypto', 'buffer', 'node-localstorage', 'node-fetch', 'asn1.js', 'jwk-to-pem'], transform: [ ["babelify", { + plugins: ["transform-async-to-generator", + "syntax-async-functions", + "transform-regenerator", + "transform-runtime"], ignore: ['*.min.js'], - presets: ["es2015"] + presets: ["env"] }] ], - plugin: [ 'browserify-derequire' ] + plugin: ['browserify-derequire'] } }, openpgp_debug: { files: { - 'dist/openpgp_debug.js': [ './src/index.js' ] + 'dist/openpgp_debug.js': ['./src/index.js'] }, options: { browserifyOptions: { debug: true, standalone: 'openpgp' }, - external: [ 'crypto', 'buffer', 'node-localstorage', 'node-fetch' ], + external: ['crypto', 'buffer', 'node-localstorage', 'node-fetch', 'asn1.js', 'jwk-to-pem'], transform: [ ["babelify", { + plugins: ["transform-async-to-generator", + "syntax-async-functions", + "transform-regenerator", + "transform-runtime"], ignore: ['*.min.js'], - presets: ["es2015"] + presets: ["env"] }] ], - plugin: [ 'browserify-derequire' ] + plugin: ['browserify-derequire'] } }, worker: { files: { - 'dist/openpgp.worker.js': [ './src/worker/worker.js' ] + 'dist/openpgp.worker.js': ['./src/worker/worker.js'] } }, unittests: { files: { - 'test/lib/unittests-bundle.js': [ './test/unittests.js' ] + 'test/lib/unittests-bundle.js': ['./test/unittests.js'] }, options: { - external: [ 'crypto', 'buffer' , 'node-localstorage', 'node-fetch', 'openpgp', '../../dist/openpgp', '../../../dist/openpgp' ] + external: ['buffer', 'openpgp', '../../dist/openpgp', '../../../dist/openpgp'], + transform: [ + ["babelify", { + global: true, + plugins: ["transform-async-to-generator", + "syntax-async-functions", + "transform-regenerator", + "transform-remove-strict-mode"], + ignore: ['*.min.js'], + presets: ["env"] + }] + ] } } }, @@ -129,8 +150,8 @@ module.exports = function(grunt) { uglify: { openpgp: { files: { - 'dist/openpgp.min.js' : [ 'dist/openpgp.js' ], - 'dist/openpgp.worker.min.js' : [ 'dist/openpgp.worker.js' ] + 'dist/openpgp.min.js' : ['dist/openpgp.js'], + 'dist/openpgp.worker.min.js' : ['dist/openpgp.worker.js'] } }, options: { @@ -149,19 +170,9 @@ module.exports = function(grunt) { wrap_line_length: 120 } }, - jshint: { - src: lintFiles, - build: ['Gruntfile.js', '*.json'], - options: { - jshintrc: '.jshintrc' - } - }, - jscs: { - src: lintFiles, - build: ['Gruntfile.js'], - options: { - config: ".jscsrc" - } + eslint: { + target: lintFiles, + options: { configFile: '.eslintrc.js' } }, jsdoc: { dist: { @@ -177,7 +188,7 @@ module.exports = function(grunt) { src: 'test', options: { root: '.', - timeout: 240000, + timeout: 240000 } } }, @@ -187,7 +198,7 @@ module.exports = function(grunt) { reporter: 'spec', timeout: 120000 }, - src: [ 'test/unittests.js' ] + src: ['test/unittests.js'] } }, copy: { @@ -195,7 +206,7 @@ module.exports = function(grunt) { expand: true, flatten: true, cwd: 'node_modules/', - src: ['mocha/mocha.css', 'mocha/mocha.js', 'chai/chai.js', 'whatwg-fetch/fetch.js'], + src: ['mocha/mocha.css', 'mocha/mocha.js'], dest: 'test/lib/' }, zlib: { @@ -234,9 +245,9 @@ module.exports = function(grunt) { maxRetries: 3, throttled: 2, pollInterval: 4000, - statusCheckAttempts: 200 + statusCheckAttempts: 200, } - }, + } }, watch: { src: { @@ -247,7 +258,7 @@ module.exports = function(grunt) { files: ['test/*.js', 'test/crypto/**/*.js', 'test/general/**/*.js', 'test/worker/**/*.js'], tasks: ['browserify:unittests'] } - }, + } }); // Load the plugin(s) @@ -255,9 +266,8 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-text-replace'); grunt.loadNpmTasks('grunt-jsbeautifier'); - grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.loadNpmTasks('grunt-jscs'); grunt.loadNpmTasks('grunt-jsdoc'); + grunt.loadNpmTasks('gruntify-eslint'); grunt.loadNpmTasks('grunt-mocha-istanbul'); grunt.loadNpmTasks('grunt-mocha-test'); grunt.loadNpmTasks('grunt-contrib-copy'); @@ -304,8 +314,9 @@ module.exports = function(grunt) { grunt.registerTask('default', ['clean', 'copy:zlib', 'browserify', 'version', 'uglify', 'replace_min']); grunt.registerTask('documentation', ['jsdoc']); // Test/Dev tasks - grunt.registerTask('test', ['jshint', 'jscs', 'mochaTest']); + grunt.registerTask('test', ['eslint', 'mochaTest']); grunt.registerTask('coverage', ['mocha_istanbul:coverage']); grunt.registerTask('saucelabs', ['default', 'copy:browsertest', 'connect:test', 'saucelabs-mocha']); + grunt.registerTask('browsertest', ['default', 'copy:browsertest', 'connect:test', 'watch']); }; diff --git a/README.md b/README.md index 3d74c70c..c59a0ae4 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,62 @@ OpenPGP.js [![Build Status](https://travis-ci.org/openpgpjs/openpgpjs.svg?branch=master)](https://travis-ci.org/openpgpjs/openpgpjs) ========== - -[OpenPGP.js](http://openpgpjs.org/) is a Javascript implementation of the OpenPGP protocol. This is defined in [RFC 4880](http://tools.ietf.org/html/rfc4880). + +[OpenPGP.js](http://openpgpjs.org/) is a JavaScript implementation of the OpenPGP protocol. This is defined in [RFC 4880](http://tools.ietf.org/html/rfc4880). [![Saucelabs Test Status](https://saucelabs.com/browser-matrix/openpgpjs.svg)](https://saucelabs.com/u/openpgpjs) + + +**Table of Contents** +- [OpenPGP.js](#openpgpjs) + - [Platform Support](#platform-support) + - [Performance](#performance) + - [Getting started](#getting-started) + - [Npm](#npm) + - [Bower](#bower) + - [Examples](#examples) + - [Set up](#set-up) + - [Encrypt and decrypt *Uint8Array* data with a password](#encrypt-and-decrypt-uint8array-data-with-a-password) + - [Encrypt and decrypt *String* data with PGP keys](#encrypt-and-decrypt-string-data-with-pgp-keys) + - [Generate new key pair](#generate-new-key-pair) + - [Lookup public key on HKP server](#lookup-public-key-on-hkp-server) + - [Upload public key to HKP server](#upload-public-key-to-hkp-server) + - [Sign and verify cleartext messages](#sign-and-verify-cleartext-messages) + - [Create and verify *detached* signatures](#create-and-verify-detached-signatures) + - [Documentation](#documentation) + - [Security Audit](#security-audit) + - [Security recommendations](#security-recommendations) + - [Development](#development) + - [How do I get involved?](#how-do-i-get-involved) + - [License](#license) + - [Resources](#resources) -### Platform support + -* OpenPGP.js v2.x is written in ES6 but is transpiled to ES5 using [Babel](https://babeljs.io/) to run in most environments. We support node.js v0.12+ and browsers that implement [window.crypto.getRandomValues](http://caniuse.com/#feat=getrandomvalues). +### Platform Support -* The api uses ES6 promises which are available in [most modern browsers](http://caniuse.com/#feat=promises). If you need to support browsers that do not support Promises, fear not! There is a [polyfill](https://github.com/jakearchibald/es6-promise), which is included in our build. So no action required on your part! +* OpenPGP.js v3.x is written in ES7 but is transpiled to ES5 using [Babel](https://babeljs.io/) to run in most environments. We support Node.js v8+ and browsers that implement [window.crypto.getRandomValues](http://caniuse.com/#feat=getrandomvalues). -* For the OpenPGP HTTP Key Server (HKP) client the new [fetch api](http://caniuse.com/#feat=fetch) is used. There is a polyfill for both [browsers](https://github.com/github/fetch) and [node.js](https://github.com/bitinn/node-fetch) runtimes. The node module is included as a dependency if you install via npm, but we do not include the browser polyfill in our build. So you'll need to include it in your app if you intend to use the HKP client. +* The API uses the Async/Await syntax introduced in ES7 to return Promise objects. Async functions are available in [most modern browsers](https://caniuse.com/#feat=async-functions). If you need to support older browsers, fear not! We use [core-js](https://github.com/zloirock/core-js) to polyfill new features so that no action is required on your part! +* For the OpenPGP HTTP Key Server (HKP) client the new [fetch API](http://caniuse.com/#feat=fetch) is used. The module is polyfilled for [browsers](https://github.com/github/fetch) and is included as a dependency for [Node.js](https://github.com/bitinn/node-fetch) runtimes. ### Performance +* Version 3.0.0 of the library introduces support for public-key cryptography using [elliptic curves](https://wiki.gnupg.org/ECC). We use native implementations on browsers and Node.js when available or [Elliptic](https://github.com/indutny/elliptic) otherwise. Elliptic curve cryptography provides stronger security per bits of key, which allows for much faster operations. Currently the following curves are supported: ("Yes*" means "when available") + +>| Curve | Encryption | Signature | Elliptic | NodeCrypto | WebCrypto | +>|:---------- |:----------:|:---------:|:--------:|:----------:|:---------:| +>| p256 | ECDH | ECDSA | Yes | Yes* | Yes* | +>| p384 | ECDH | ECDSA | Yes | Yes* | Yes* | +>| p521 | ECDH | ECDSA | Yes | Yes* | Yes* | +>| secp256k1 | ECDH | ECDSA | Yes | Yes* | No | +>| curve25519 | ECDH | N/A | Yes | No | No | +>| ed25519 | N/A | EdDSA | Yes | No | No | + * Version 2.x of the library has been built from the ground up with Uint8Arrays. This allows for much better performance and memory usage than strings. -* If the user's browser supports [native WebCrypto](http://caniuse.com/#feat=cryptography) via the `window.crypto.subtle` api, this will be used. Under node.js the native [crypto module](https://nodejs.org/api/crypto.html#crypto_crypto) is used. This can be deactivated by setting `openpgp.config.use_native = false`. +* If the user's browser supports [native WebCrypto](http://caniuse.com/#feat=cryptography) via the `window.crypto.subtle` API, this will be used. Under Node.js the native [crypto module](https://nodejs.org/API/crypto.html#crypto_crypto) is used. This can be deactivated by setting `openpgp.config.use_native = false`. * The library implements the [IETF proposal](https://tools.ietf.org/html/draft-ford-openpgp-format-00) for authenticated encryption [using native AES-GCM](https://github.com/openpgpjs/openpgpjs/pull/430). This makes symmetric encryption about 30x faster on supported platforms. Since the specification has not been finalized and other OpenPGP implementations haven't adopted it yet, the feature is currently behind a flag. You can activate it by setting `openpgp.config.aead_protect = true`. **Note: activating this setting can break compatibility with other OpenPGP implementations, so be careful if that's one of your requirements.** @@ -41,7 +78,7 @@ Or just fetch a minified build under [dist](https://github.com/openpgpjs/openpgp ### Examples -Here are some examples of how to use the v2.x api. For more elaborate examples and working code, please check out the [public api unit tests](https://github.com/openpgpjs/openpgpjs/blob/master/test/general/openpgp.js). If you're upgrading from v1.x it might help to check out the [documentation](https://github.com/openpgpjs/openpgpjs#documentation). +Here are some examples of how to use the v2.x+ API. For more elaborate examples and working code, please check out the [public API unit tests](https://github.com/openpgpjs/openpgpjs/blob/master/test/general/openpgp.js). If you're upgrading from v1.x it might help to check out the [documentation](https://github.com/openpgpjs/openpgpjs#documentation). #### Set up @@ -118,13 +155,25 @@ openpgp.decrypt(options).then(function(plaintext) { #### Generate new key pair +RSA keys: ```js var options = { userIds: [{ name:'Jon Smith', email:'jon@example.com' }], // multiple user IDs numBits: 4096, // RSA key size passphrase: 'super long and hard to guess secret' // protects the private key }; +``` +ECC keys: +```js +var options = { + userIds: [{ name:'Jon Smith', email:'jon@example.com' }], // multiple user IDs + curve: "ed25519", // ECC curve (curve25519, p256, p384, p521, or secp256k1) + passphrase: 'super long and hard to guess secret' // protects the private key +}; +``` + +```js openpgp.generateKey(options).then(function(key) { var privkey = key.privateKeyArmored; // '-----BEGIN PGP PRIVATE KEY BLOCK ... ' var pubkey = key.publicKeyArmored; // '-----BEGIN PGP PUBLIC KEY BLOCK ... ' @@ -255,6 +304,10 @@ To create your own build of the library, just run the following command after cl npm install && npm test +For debugging browser errors, you can open `test/unittests.html` in a browser or, after running the following command, open [`http://localhost:3000/test/unittests.html`](http://localhost:3000/test/unittests.html): + + grunt browsertest + ### How do I get involved? You want to help, great! Go ahead and fork our repo, make your changes and send us a pull request. diff --git a/bower.json b/bower.json index 70d35cc1..9859d658 100644 --- a/bower.json +++ b/bower.json @@ -9,9 +9,7 @@ "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", - "dist/openpgp.min.js", - "dist/openpgp.worker.min.js" + "dist/openpgp.worker.js" ], "moduleType": [ "amd", diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index f3e79e6d..b97f5114 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "openpgp", - "version": "2.6.2", + "version": "3.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -14,12 +14,6 @@ "through": "2.3.8" } }, - "JSV": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz", - "integrity": "sha1-0Hf2glVx+CEy+d/67Vh7QCn+/1c=", - "dev": true - }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -42,6 +36,23 @@ "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", "dev": true }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "3.3.0" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, "agent-base": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-2.1.1.tgz", @@ -72,6 +83,12 @@ "json-schema-traverse": "0.3.1" } }, + "ajv-keywords": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "dev": true + }, "align-text": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", @@ -89,6 +106,12 @@ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, + "ansi-escapes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz", + "integrity": "sha512-O/klc27mWNUigtv0F8NJWbLF00OcegQalkqKURWdosW08YZKi4m6CnSUSvIZG1otNJbTWhN01Hhz389DW7mvDQ==", + "dev": true + }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -120,6 +143,16 @@ "sprintf-js": "1.0.3" } }, + "aria-query": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-0.7.1.tgz", + "integrity": "sha1-Jsu1r/ZBRLCoJb4YRuCxbPoAsR4=", + "dev": true, + "requires": { + "ast-types-flow": "0.0.7", + "commander": "2.12.2" + } + }, "arr-diff": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", @@ -147,6 +180,16 @@ "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", "dev": true }, + "array-includes": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", + "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", + "dev": true, + "requires": { + "define-properties": "1.1.2", + "es-abstract": "1.10.0" + } + }, "array-map": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", @@ -159,18 +202,42 @@ "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", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "1.0.3" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, "array-unique": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", "dev": true }, - "asmcrypto-lite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/asmcrypto-lite/-/asmcrypto-lite-1.1.0.tgz", - "integrity": "sha1-6Zb6AnHFjDwlqegBWMRJv6SY2fs=", + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true + }, + "asmcrypto-lite": { + "version": "git+https://github.com/openpgpjs/asmcrypto-lite.git#57ef213b6677d118c1b9668a35c74b6c716a5310" + }, "asn1": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", @@ -178,10 +245,9 @@ "dev": true }, "asn1.js": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.2.tgz", - "integrity": "sha512-b/OsSjvWEo8Pi8H0zsDd2P6Uqo2TK2pH8gNLSJtNLM2Db0v2QaAZ0pBQJXVjAn4gBuugeVDr7s63ZogpUIwWDg==", - "dev": true, + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.0.0.tgz", + "integrity": "sha512-Y+FKviD0uyIWWo/xE0XkUl0x1allKFhzEVJ+//2Dgqpy+n+B77MlPNqvyk7Vx50M9XyVzjnRhDqJAEAsyivlbA==", "requires": { "bn.js": "4.11.8", "inherits": "2.0.3", @@ -204,9 +270,15 @@ "dev": true }, "assertion-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", - "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", "dev": true }, "astw": { @@ -248,6 +320,15 @@ "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", "dev": true }, + "axobject-query": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-0.1.0.tgz", + "integrity": "sha1-YvWdvFnJ+SQnWco0mWDnov48NsA=", + "dev": true, + "requires": { + "ast-types-flow": "0.0.7" + } + }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", @@ -302,6 +383,17 @@ "trim-right": "1.0.1" } }, + "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": { + "babel-helper-explode-assignable-expression": "6.24.1", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0" + } + }, "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", @@ -326,6 +418,17 @@ "lodash": "4.17.4" } }, + "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": { + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, "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", @@ -380,6 +483,19 @@ "lodash": "4.17.4" } }, + "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": { + "babel-helper-function-name": "6.24.1", + "babel-runtime": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0" + } + }, "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", @@ -422,6 +538,35 @@ "babel-runtime": "6.26.0" } }, + "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 + }, + "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 + }, + "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": { + "babel-helper-remap-async-to-generator": "6.24.1", + "babel-plugin-syntax-async-functions": "6.13.0", + "babel-runtime": "6.26.0" + } + }, "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", @@ -656,6 +801,35 @@ "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.26.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.26.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.13.0", + "babel-runtime": "6.26.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", @@ -665,6 +839,21 @@ "regenerator-transform": "0.10.1" } }, + "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 + }, + "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": { + "babel-runtime": "6.26.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", @@ -675,13 +864,34 @@ "babel-types": "6.26.0" } }, - "babel-preset-es2015": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz", - "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=", + "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.3", + "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.6.1", + "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.6.1.tgz", + "integrity": "sha512-W6VIyA6Ch9ePMI7VptNn2wBM6dbG0eSz25HEiL40nQXCsXGTGZSTZu1Iap+cj3Q0S5a7T9+529l/5Bkvd+afNA==", "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.24.1", "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.26.0", @@ -704,9 +914,110 @@ "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.24.1", + "babel-plugin-transform-exponentiation-operator": "6.24.1", + "babel-plugin-transform-regenerator": "6.26.0", + "browserslist": "2.11.3", + "invariant": "2.2.2", + "semver": "5.4.1" + } + }, + "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.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.26.0", + "babel-plugin-transform-es2015-classes": "6.24.1", + "babel-plugin-transform-es2015-computed-properties": "6.24.1", + "babel-plugin-transform-es2015-destructuring": "6.23.0", + "babel-plugin-transform-es2015-duplicate-keys": "6.24.1", + "babel-plugin-transform-es2015-for-of": "6.23.0", + "babel-plugin-transform-es2015-function-name": "6.24.1", + "babel-plugin-transform-es2015-literals": "6.22.0", + "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", + "babel-plugin-transform-es2015-object-super": "6.24.1", + "babel-plugin-transform-es2015-parameters": "6.24.1", + "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", + "babel-plugin-transform-es2015-spread": "6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "6.24.1", + "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.24.1", "babel-plugin-transform-regenerator": "6.26.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.24.1", + "babel-runtime": "5.8.38", + "babel-template": "6.26.0", + "babel-types": "6.26.0" + } + }, + "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.26.0", + "babel-plugin-syntax-async-functions": "6.13.0", + "babel-plugin-transform-es2015-block-scoping": "6.26.0", + "babel-plugin-transform-es2015-for-of": "6.23.0", + "babel-runtime": "5.8.38", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "private": "0.1.8" + } + }, + "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.2.7" + } + }, + "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.22.0", + "babel-plugin-transform-es3-property-literals": "6.22.0" + } + }, "babel-register": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", @@ -715,7 +1026,7 @@ "requires": { "babel-core": "6.26.0", "babel-runtime": "6.26.0", - "core-js": "2.5.1", + "core-js": "2.5.3", "home-or-tmp": "2.0.0", "lodash": "4.17.4", "mkdirp": "0.5.1", @@ -728,7 +1039,7 @@ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "dev": true, "requires": { - "core-js": "2.5.1", + "core-js": "2.5.3", "regenerator-runtime": "0.11.0" } }, @@ -795,8 +1106,7 @@ "base64-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", - "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==", - "dev": true + "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==" }, "basic-auth": { "version": "2.0.0", @@ -838,8 +1148,7 @@ "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" }, "body-parser": { "version": "1.14.2", @@ -931,8 +1240,7 @@ "brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" }, "browser-pack": { "version": "6.0.2", @@ -1217,7 +1525,7 @@ "browserify-rsa": "4.0.1", "create-hash": "1.1.3", "create-hmac": "1.1.6", - "elliptic": "6.4.0", + "elliptic": "git+https://github.com/openpgpjs/elliptic.git#0bd7555723b84ac2b747e00c8cea1e64e99b4f4f", "inherits": "2.0.3", "parse-asn1": "5.1.0" } @@ -1231,11 +1539,20 @@ "pako": "1.0.6" } }, + "browserslist": { + "version": "2.11.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", + "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", + "dev": true, + "requires": { + "caniuse-lite": "1.0.30000792", + "electron-to-chromium": "1.3.32" + } + }, "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.2.1", "ieee754": "1.1.8" @@ -1271,6 +1588,21 @@ "integrity": "sha1-0JxLUoAKpMB44t2BqGmqyQ0uVOc=", "dev": true }, + "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", @@ -1295,6 +1627,12 @@ } } }, + "caniuse-lite": { + "version": "1.0.30000792", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000792.tgz", + "integrity": "sha1-0M6pgfgRjzlhRxr7tDyaHlu/AzI=", + "dev": true + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -1327,7 +1665,7 @@ "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", "dev": true, "requires": { - "assertion-error": "1.0.2", + "assertion-error": "1.1.0", "check-error": "1.0.2", "deep-eql": "3.0.1", "get-func-name": "2.0.0", @@ -1335,6 +1673,15 @@ "type-detect": "4.0.5" } }, + "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", @@ -1348,6 +1695,12 @@ "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", @@ -1381,48 +1734,26 @@ "safe-buffer": "5.1.1" } }, - "cli": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", - "integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=", + "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=", "dev": true, "requires": { - "exit": "0.1.2", - "glob": "7.1.2" - }, - "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.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - } + "restore-cursor": "2.0.0" } }, - "cli-table": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", - "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", - "dev": true, - "requires": { - "colors": "1.0.3" - }, - "dependencies": { - "colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", - "dev": true - } - } + "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", @@ -1453,6 +1784,21 @@ "integrity": "sha1-EpOLz5vhlI+gBvkuDEyegXBRCMA=", "dev": true }, + "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.3" + } + }, + "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", @@ -1494,15 +1840,6 @@ "integrity": "sha512-BFnaq5ZOGcDN7FlrtBT4xxkgIToalIIxwjxLWVJ8bGTpe1LroqMiqQXdA7ygc7CRvaYS+9zfPGFnJqFSayx+AA==", "dev": true }, - "comment-parser": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-0.3.2.tgz", - "integrity": "sha1-PAPwd2uGo239mgosl8YwfzMggv4=", - "dev": true, - "requires": { - "readable-stream": "2.3.3" - } - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1563,6 +1900,12 @@ "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 + }, "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", @@ -1576,9 +1919,9 @@ "dev": true }, "core-js": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz", - "integrity": "sha1-rmh03GaTd4m4B1T/VCjfZoGcpQs=", + "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": { @@ -1594,7 +1937,7 @@ "dev": true, "requires": { "bn.js": "4.11.8", - "elliptic": "6.4.0" + "elliptic": "git+https://github.com/openpgpjs/elliptic.git#0bd7555723b84ac2b747e00c8cea1e64e99b4f4f" } }, "create-hash": { @@ -1624,12 +1967,13 @@ } }, "cross-spawn": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", - "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", + "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.1.1", + "shebang-command": "1.2.0", "which": "1.2.14" }, "dependencies": { @@ -1684,17 +2028,6 @@ "randomfill": "1.0.3" } }, - "cst": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/cst/-/cst-0.4.10.tgz", - "integrity": "sha512-U5ETe1IOjq2h56ZcBE3oe9rT7XryCH6IKgPMv0L7sSk6w29yR3p5egCK0T3BDNHHV95OoUBgXsqiVG+3a900Ag==", - "dev": true, - "requires": { - "babel-runtime": "6.26.0", - "babylon": "6.18.0", - "source-map-support": "0.4.18" - } - }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -1704,12 +2037,6 @@ "array-find-index": "1.0.2" } }, - "cycle": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", - "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=", - "dev": true - }, "d": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", @@ -1719,6 +2046,12 @@ "es5-ext": "0.10.37" } }, + "damerau-levenshtein": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz", + "integrity": "sha1-AxkcQyy27qFou3fzpV/9zLiXhRQ=", + "dev": true + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -1768,12 +2101,6 @@ "type-detect": "4.0.5" } }, - "deep-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", - "dev": true - }, "deep-extend": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", @@ -1786,12 +2113,37 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "define-properties": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", + "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", + "dev": true, + "requires": { + "foreach": "2.0.5", + "object-keys": "1.0.11" + } + }, "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.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "rimraf": "2.2.8" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1913,28 +2265,13 @@ "randombytes": "2.0.5" } }, - "dom-serializer": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", - "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { - "domelementtype": "1.1.3", - "entities": "1.1.1" - }, - "dependencies": { - "domelementtype": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", - "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", - "dev": true - }, - "entities": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", - "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", - "dev": true - } + "esutils": "2.0.2" } }, "domain-browser": { @@ -1943,31 +2280,6 @@ "integrity": "sha1-hnqksJP6oF8d4IwG9NeyH9+GmLw=", "dev": true }, - "domelementtype": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", - "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", - "dev": true - }, - "domhandler": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", - "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", - "dev": true, - "requires": { - "domelementtype": "1.3.0" - } - }, - "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "dev": true, - "requires": { - "dom-serializer": "0.1.0", - "domelementtype": "1.3.0" - } - }, "duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", @@ -2006,11 +2318,14 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", "dev": true }, + "electron-to-chromium": { + "version": "1.3.32", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.32.tgz", + "integrity": "sha1-EdBoTAhA4APEvoko+KxfNdvCtOY=", + "dev": true + }, "elliptic": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", - "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", - "dev": true, + "version": "git+https://github.com/openpgpjs/elliptic.git#0bd7555723b84ac2b747e00c8cea1e64e99b4f4f", "requires": { "bn.js": "4.11.8", "brorand": "1.1.0", @@ -2021,6 +2336,12 @@ "minimalistic-crypto-utils": "1.0.1" } }, + "emoji-regex": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz", + "integrity": "sha512-PAHp6TxrCy7MGMFidro8uikr+zlJJKJ/Q6mm2ExZ7HwkyR9lSVFfE3kt36qcwa24BQL7y0G9axycGjK1A/0uNQ==", + "dev": true + }, "encodeurl": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", @@ -2035,12 +2356,6 @@ "iconv-lite": "0.4.19" } }, - "entities": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", - "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=", - "dev": true - }, "error-ex": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", @@ -2050,6 +2365,30 @@ "is-arrayish": "0.2.1" } }, + "es-abstract": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.10.0.tgz", + "integrity": "sha512-/uh/DhdqIOSkAWifU+8nG78vlQxdLckUdI/sPgy0VhuXi2qJ7T8czBmqIYtLQVpCIFYafChnsRsB5pyb1JdmCQ==", + "dev": true, + "requires": { + "es-to-primitive": "1.1.1", + "function-bind": "1.1.1", + "has": "1.0.1", + "is-callable": "1.1.3", + "is-regex": "1.0.4" + } + }, + "es-to-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", + "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", + "dev": true, + "requires": { + "is-callable": "1.1.3", + "is-date-object": "1.0.1", + "is-symbol": "1.0.1" + } + }, "es5-ext": { "version": "0.10.37", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.37.tgz", @@ -2085,12 +2424,6 @@ "event-emitter": "0.3.5" } }, - "es6-promise": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.1.1.tgz", - "integrity": "sha512-OaU1hHjgJf+b0NzsxCg7NdIYERD6Hy/PEmFLTjw+b65scuisG3Kt4QoTvJ66BBkPZ581gr0kpoVzKnxniM8nng==", - "dev": true - }, "es6-set": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", @@ -2181,12 +2514,366 @@ "estraverse": "4.2.0" } }, + "eslint": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.17.0.tgz", + "integrity": "sha512-AyxBUCANU/o/xC0ijGMKavo5Ls3oK6xykiOITlMdjFjrKOsqLrA7Nf5cnrDgcKrHzBirclAZt63XO7YZlVUPwA==", + "dev": true, + "requires": { + "ajv": "5.5.0", + "babel-code-frame": "6.26.0", + "chalk": "2.3.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.3", + "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.3.0", + "ignore": "3.3.7", + "imurmurhash": "0.1.4", + "inquirer": "3.3.0", + "is-resolvable": "1.1.0", + "js-yaml": "3.10.0", + "json-stable-stringify-without-jsonify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.17.4", + "minimatch": "3.0.4", + "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", + "require-uncached": "1.0.3", + "semver": "5.4.1", + "strip-ansi": "4.0.0", + "strip-json-comments": "2.0.1", + "table": "4.0.2", + "text-table": "0.2.0" + }, + "dependencies": { + "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.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", + "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.5.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" + } + }, + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true + }, + "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.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "globals": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.3.0.tgz", + "integrity": "sha512-kkpcKNlmQan9Z5ZmgqKH/SMbSmjxQ7QjyNqfXVc8VJcoBV2UEg+sxQD15GQofGRh2hfpwUb70VC31DR7Rq5Hdw==", + "dev": true + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "js-yaml": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", + "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", + "dev": true, + "requires": { + "argparse": "1.0.9", + "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": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "2.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.9", + "pkg-dir": "1.0.0" + } + }, + "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.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "0.3.2", + "eslint-module-utils": "2.1.1", + "has": "1.0.1", + "lodash.cond": "4.5.2", + "minimatch": "3.0.4", + "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.11", + "parse-json": "2.2.0", + "pify": "2.3.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.3.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.4.0", + "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.1.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-plugin-jsx-a11y": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.0.3.tgz", + "integrity": "sha1-VFg9GuRCSDFi4EDhPMMYZUZRAOU=", + "dev": true, + "requires": { + "aria-query": "0.7.1", + "array-includes": "3.0.3", + "ast-types-flow": "0.0.7", + "axobject-query": "0.1.0", + "damerau-levenshtein": "1.0.4", + "emoji-regex": "6.5.1", + "jsx-ast-utils": "2.0.1" + } + }, + "eslint-plugin-react": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.6.1.tgz", + "integrity": "sha512-30aMOHWX/DOaaLJVBHz6RMvYM2qy5GH63+y2PLFdIrYe4YLtODFmT3N1YA7ZqUnaBweVbedr4K4cqxOlWAPjIw==", + "dev": true, + "requires": { + "doctrine": "2.1.0", + "has": "1.0.1", + "jsx-ast-utils": "2.0.1", + "prop-types": "15.6.0" + } + }, + "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.2.0", + "estraverse": "4.2.0" + } + }, + "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 + }, + "espree": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.3.tgz", + "integrity": "sha512-Zy3tAJDORxQZLl2baguiRU1syPERAIg0L+JB2MWorORgTu/CplzvxS9WWA7Xh4+Q+eOQihNs/1o1Xep8cvCxWQ==", + "dev": true, + "requires": { + "acorn": "5.4.1", + "acorn-jsx": "3.0.1" + }, + "dependencies": { + "acorn": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.4.1.tgz", + "integrity": "sha512-XLmq3H/BVvW6/GbxKryGxWORz1ebilSsUDlyC27bXhWGWAZWkGwS6FLHjOlwFXNFoWFQEO/Df4u0YYd0K3BQgQ==", + "dev": true + } + } + }, "esprima": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", "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.2.0" + } + }, "esrecurse": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", @@ -2277,6 +2964,17 @@ "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", "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==", + "dev": true, + "requires": { + "chardet": "0.4.2", + "iconv-lite": "0.4.19", + "tmp": "0.0.33" + } + }, "extglob": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", @@ -2292,12 +2990,6 @@ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", "dev": true }, - "eyes": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", - "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=", - "dev": true - }, "fast-deep-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", @@ -2325,6 +3017,29 @@ "websocket-driver": "0.7.0" } }, + "fbjs": { + "version": "0.8.16", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz", + "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=", + "dev": true, + "requires": { + "core-js": "1.2.7", + "isomorphic-fetch": "2.2.1", + "loose-envify": "1.3.1", + "object-assign": "4.1.1", + "promise": "7.3.1", + "setimmediate": "1.0.5", + "ua-parser-js": "0.7.17" + }, + "dependencies": { + "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 + } + } + }, "fg-lodash": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/fg-lodash/-/fg-lodash-0.0.2.tgz", @@ -2359,6 +3074,16 @@ "object-assign": "4.1.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.3.0", + "object-assign": "4.1.1" + } + }, "file-sync-cmp": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz", @@ -2433,6 +3158,18 @@ } } }, + "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.3", + "del": "2.2.2", + "graceful-fs": "4.1.11", + "write": "0.2.1" + } + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -2448,6 +3185,12 @@ "for-in": "1.0.2" } }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", + "dev": true + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -2466,12 +3209,12 @@ } }, "formatio": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz", - "integrity": "sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", + "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", "dev": true, "requires": { - "samsam": "1.1.2" + "samsam": "1.3.0" } }, "fresh": { @@ -3396,6 +4139,12 @@ "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.2", "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.2.tgz", @@ -3477,6 +4226,20 @@ "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": { + "array-union": "1.0.2", + "arrify": "1.0.1", + "glob": "7.0.6", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, "globule": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz", @@ -3509,12 +4272,6 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, - "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 - }, "growl": { "version": "1.10.3", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", @@ -3647,17 +4404,6 @@ "file-sync-cmp": "0.1.1" } }, - "grunt-contrib-jshint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-jshint/-/grunt-contrib-jshint-1.1.0.tgz", - "integrity": "sha1-Np2QmyWTxA6L55lAshNAhQx5Oaw=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "hooker": "0.2.3", - "jshint": "2.9.5" - } - }, "grunt-contrib-uglify": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/grunt-contrib-uglify/-/grunt-contrib-uglify-3.2.1.tgz", @@ -3716,36 +4462,45 @@ } } }, - "grunt-jscs": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/grunt-jscs/-/grunt-jscs-3.0.1.tgz", - "integrity": "sha1-H65Q4+lV3546nZQlrsIqzK4AgJI=", - "dev": true, - "requires": { - "hooker": "0.2.3", - "jscs": "3.0.7", - "lodash": "4.6.1", - "vow": "0.4.17" - }, - "dependencies": { - "lodash": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.6.1.tgz", - "integrity": "sha1-3wDBFkrSNrGDz8OIel6NOMxjy7w=", - "dev": true - } - } - }, "grunt-jsdoc": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/grunt-jsdoc/-/grunt-jsdoc-2.2.0.tgz", - "integrity": "sha512-3/HzvzcG7gxlm4YefR5ELbsUB/bIFCeX3CbUeAANKGMfNUZ2tDQ+Pp0YRb/VWHjyu+v8wG6n1PD8yIjubjEDeg==", + "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": { "cross-spawn": "3.0.1", - "jsdoc": "3.5.5" + "jsdoc": "3.5.5", + "marked": "0.3.12" + }, + "dependencies": { + "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": { + "lru-cache": "4.1.1", + "which": "1.2.14" + } + }, + "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" + } + } } }, + "grunt-keepalive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-keepalive/-/grunt-keepalive-1.0.0.tgz", + "integrity": "sha1-ZkyOGFoNnqGvQYoswLs/FfL4vzM=", + "dev": true + }, "grunt-known-options": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.0.tgz", @@ -3858,6 +4613,15 @@ "integrity": "sha1-252c5Z4v5J2id+nbwZXD4Rz7FsI=", "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.17.0" + } + }, "gzip-size": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-1.0.0.tgz", @@ -4002,12 +4766,6 @@ "ansi-regex": "2.1.1" } }, - "has-color": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", - "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=", - "dev": true - }, "has-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", @@ -4027,7 +4785,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", - "dev": true, "requires": { "inherits": "2.0.3", "minimalistic-assert": "1.0.0" @@ -4055,7 +4812,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, "requires": { "hash.js": "1.1.3", "minimalistic-assert": "1.0.0", @@ -4096,45 +4852,6 @@ "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=", "dev": true }, - "htmlparser2": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", - "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", - "dev": true, - "requires": { - "domelementtype": "1.3.0", - "domhandler": "2.3.0", - "domutils": "1.5.1", - "entities": "1.0.0", - "readable-stream": "1.1.14" - }, - "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.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, - "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 - } - } - }, "http-errors": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", @@ -4187,12 +4904,6 @@ "extend": "3.0.1" } }, - "i": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/i/-/i-0.3.6.tgz", - "integrity": "sha1-2WyScyB28HJxG2sQ/X1PZa2O4j0=", - "dev": true - }, "iconv-lite": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", @@ -4201,7 +4912,12 @@ "ieee754": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", - "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=", + "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" + }, + "ignore": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz", + "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==", "dev": true }, "imurmurhash": { @@ -4234,17 +4950,10 @@ "wrappy": "1.0.2" } }, - "inherit": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/inherit/-/inherit-2.2.6.tgz", - "integrity": "sha1-8WFLBshUToEo5CKchjR9tzrZeI0=", - "dev": true - }, "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.5", @@ -4261,6 +4970,105 @@ "source-map": "0.5.7" } }, + "inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "dev": true, + "requires": { + "ansi-escapes": "3.0.0", + "chalk": "2.3.0", + "cli-cursor": "2.1.0", + "cli-width": "2.2.0", + "external-editor": "2.1.0", + "figures": "2.0.0", + "lodash": "4.17.4", + "mute-stream": "0.0.7", + "run-async": "2.3.0", + "rx-lite": "4.0.8", + "rx-lite-aggregates": "4.0.8", + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "through": "2.3.8" + }, + "dependencies": { + "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.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", + "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.5.0" + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5" + } + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "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 + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "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": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, "insert-module-globals": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.0.1.tgz", @@ -4391,6 +5199,18 @@ "builtin-modules": "1.1.1" } }, + "is-callable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", + "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=", + "dev": true + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, "is-dotfile": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", @@ -4454,6 +5274,30 @@ "kind-of": "3.2.2" } }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", + "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", + "dev": true, + "requires": { + "is-path-inside": "1.0.1" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "1.0.2" + } + }, "is-posix-bracket": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", @@ -4466,11 +5310,38 @@ "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", "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-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "1.0.1" + } + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, + "is-symbol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", + "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", + "dev": true + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -4504,6 +5375,16 @@ "isarray": "1.0.0" } }, + "isomorphic-fetch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", + "dev": true, + "requires": { + "node-fetch": "1.7.3", + "whatwg-fetch": "2.0.3" + } + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -4606,103 +5487,6 @@ "dev": true, "optional": true }, - "jscs": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/jscs/-/jscs-3.0.7.tgz", - "integrity": "sha1-cUG03/W4bjLQ6Z12S4NnZ8MNIBo=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "cli-table": "0.3.1", - "commander": "2.9.0", - "cst": "0.4.10", - "estraverse": "4.2.0", - "exit": "0.1.2", - "glob": "5.0.15", - "htmlparser2": "3.8.3", - "js-yaml": "3.4.6", - "jscs-jsdoc": "2.0.0", - "jscs-preset-wikimedia": "1.0.0", - "jsonlint": "1.6.2", - "lodash": "3.10.1", - "minimatch": "3.0.4", - "natural-compare": "1.2.2", - "pathval": "0.1.1", - "prompt": "0.2.14", - "reserved-words": "0.1.2", - "resolve": "1.1.7", - "strip-bom": "2.0.0", - "strip-json-comments": "1.0.4", - "to-double-quotes": "2.0.0", - "to-single-quotes": "2.0.1", - "vow": "0.4.17", - "vow-fs": "0.3.6", - "xmlbuilder": "3.1.0" - }, - "dependencies": { - "commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", - "dev": true, - "requires": { - "graceful-readlink": "1.0.1" - } - }, - "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.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "js-yaml": { - "version": "3.4.6", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.4.6.tgz", - "integrity": "sha1-a+GyP2JJ9T0pM3D9TRqqY84bTrA=", - "dev": true, - "requires": { - "argparse": "1.0.9", - "esprima": "2.7.3", - "inherit": "2.2.6" - } - }, - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", - "dev": true - }, - "pathval": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-0.1.1.tgz", - "integrity": "sha1-CPkRzcqczllCiA2ngXvAtyO2bYI=", - "dev": true - } - } - }, - "jscs-jsdoc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jscs-jsdoc/-/jscs-jsdoc-2.0.0.tgz", - "integrity": "sha1-9T684CmqMSW9iCkLpQ1k1FEKSHE=", - "dev": true, - "requires": { - "comment-parser": "0.3.2", - "jsdoctypeparser": "1.2.0" - } - }, - "jscs-preset-wikimedia": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/jscs-preset-wikimedia/-/jscs-preset-wikimedia-1.0.0.tgz", - "integrity": "sha1-//VjNCA4/C6IJre7cwnDrjQG/H4=", - "dev": true - }, "jsdoc": { "version": "3.5.5", "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.5.5.tgz", @@ -4715,7 +5499,7 @@ "escape-string-regexp": "1.0.5", "js2xmlparser": "3.0.0", "klaw": "2.0.0", - "marked": "0.3.6", + "marked": "0.3.12", "mkdirp": "0.5.1", "requizzle": "0.2.1", "strip-json-comments": "2.0.1", @@ -4728,35 +5512,6 @@ "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 - }, - "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 - }, - "underscore": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", - "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", - "dev": true - } - } - }, - "jsdoctypeparser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-1.2.0.tgz", - "integrity": "sha1-597cFToRhJ/8UUEUSuhqfvDCU5I=", - "dev": true, - "requires": { - "lodash": "3.10.1" - }, - "dependencies": { - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", - "dev": true } } }, @@ -4766,30 +5521,6 @@ "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", "dev": true }, - "jshint": { - "version": "2.9.5", - "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.9.5.tgz", - "integrity": "sha1-HnJSkVzmgbQIJ+4UJIxG006apiw=", - "dev": true, - "requires": { - "cli": "1.0.1", - "console-browserify": "1.1.0", - "exit": "0.1.2", - "htmlparser2": "3.8.3", - "lodash": "3.7.0", - "minimatch": "3.0.4", - "shelljs": "0.3.0", - "strip-json-comments": "1.0.4" - }, - "dependencies": { - "lodash": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.7.0.tgz", - "integrity": "sha1-Nni9irmVBXwHreg27S7wh9qBHUU=", - "dev": true - } - } - }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", @@ -4811,6 +5542,12 @@ "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 + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -4829,16 +5566,6 @@ "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", "dev": true }, - "jsonlint": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/jsonlint/-/jsonlint-1.6.2.tgz", - "integrity": "sha1-VzcEUIX1XrRVxosf9OvAG9UOiDA=", - "dev": true, - "requires": { - "JSV": "4.0.2", - "nomnom": "1.8.1" - } - }, "jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", @@ -4857,6 +5584,43 @@ "verror": "1.10.0" } }, + "jsx-ast-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz", + "integrity": "sha1-6AGxs5mF4g//yHtA43SAgOLcrH8=", + "dev": true, + "requires": { + "array-includes": "3.0.3" + } + }, + "just-extend": { + "version": "1.1.27", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-1.1.27.tgz", + "integrity": "sha512-mJVp13Ix6gFo3SBAy9U/kL+oeZqzlYYYLQBwXVBlVzIsZwBqGREnOro24oC/8s8aox+rJhtZ2DiQof++IrkA+g==", + "dev": true + }, + "jwk-to-pem": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/jwk-to-pem/-/jwk-to-pem-1.2.6.tgz", + "integrity": "sha1-1QfOzkAInFJI4J7GgmaiAwqcYyU=", + "requires": { + "asn1.js": "4.9.2", + "elliptic": "git+https://github.com/openpgpjs/elliptic.git#0bd7555723b84ac2b747e00c8cea1e64e99b4f4f", + "safe-buffer": "5.1.1" + }, + "dependencies": { + "asn1.js": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.2.tgz", + "integrity": "sha512-b/OsSjvWEo8Pi8H0zsDd2P6Uqo2TK2pH8gNLSJtNLM2Db0v2QaAZ0pBQJXVjAn4gBuugeVDr7s63ZogpUIwWDg==", + "requires": { + "bn.js": "4.11.8", + "inherits": "2.0.3", + "minimalistic-assert": "1.0.0" + } + } + } + }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -4948,12 +5712,42 @@ "strip-bom": "2.0.0" } }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "2.0.0", + "path-exists": "3.0.0" + }, + "dependencies": { + "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 + } + } + }, "lodash": { "version": "4.17.4", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", "dev": true }, + "lodash.cond": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/lodash.cond/-/lodash.cond-4.5.2.tgz", + "integrity": "sha1-9HGh2khr5g9quVXRcRVSPdHSVdU=", + "dev": true + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "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", @@ -4961,9 +5755,9 @@ "dev": true }, "lolex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.3.2.tgz", - "integrity": "sha1-fD2mL/yzDw9agKJWbKJORdigHzE=", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.3.2.tgz", + "integrity": "sha512-A5pN2tkFj7H0dGIAM6MFvHKMJcPnjZsOMvR7ujCjfgW5TbV6H9vb1PgxLtHvjqNZTHsUolz+6/WEO0N1xNx2ng==", "dev": true }, "longest": { @@ -5007,9 +5801,9 @@ "dev": true }, "marked": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.6.tgz", - "integrity": "sha1-ssbGGPzOzk74bE/Gy4p8v1rtqNc=", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.12.tgz", + "integrity": "sha512-k4NaW+vS7ytQn6MgJn3fYpQt20/mOgYM5Ft9BYMfQJDz2QT6yEeS9XJ8k2Nw8JTeWK/znPPW2n3UJGzyYEiMoA==", "dev": true }, "maxmin": { @@ -5130,17 +5924,21 @@ "mime-db": "1.30.0" } }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, "minimalistic-assert": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", - "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M=", - "dev": true + "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M=" }, "minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" }, "minimatch": { "version": "3.0.4", @@ -5167,9 +5965,9 @@ } }, "mocha": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.0.1.tgz", - "integrity": "sha512-evDmhkoA+cBNiQQQdSKZa2b9+W2mpLoj50367lhy+Klnx9OV8XlCIhigUnn1gaTFLQCa0kdNhEGDr0hCXOQFDw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.1.0.tgz", + "integrity": "sha512-0RVnjg1HJsXY2YFDoTNzcc1NKhYuXKRrBAG2gDygmJJA136Cs2QlRliZG1mA0ap7cuaT30mw16luAeln+4RiNA==", "dev": true, "requires": { "browser-stdout": "1.3.0", @@ -5230,6 +6028,15 @@ } } }, + "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": "4.1.1", "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-4.1.1.tgz", @@ -5331,15 +6138,9 @@ "optional": true }, "natural-compare": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.2.2.tgz", - "integrity": "sha1-H5bWDjFBysG20FZTzg2urHY69qo=", - "dev": true - }, - "ncp": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.4.2.tgz", - "integrity": "sha1-q8xsvT7C7Spyn/bnwfqPAXhKhXQ=", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, "negotiator": { @@ -5348,6 +6149,27 @@ "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", "dev": true }, + "nise": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.2.2.tgz", + "integrity": "sha512-rvxf+PSZeCKtP0DgmwMmNf1G3I6X1r4WHiP2H88PlIkOkt7mGqufdokjS8caoHBgZzVx0ee/5ytGcGHbZaUw8w==", + "dev": true, + "requires": { + "formatio": "1.2.0", + "just-extend": "1.1.27", + "lolex": "1.6.0", + "path-to-regexp": "1.7.0", + "text-encoding": "0.6.4" + }, + "dependencies": { + "lolex": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", + "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", + "dev": true + } + } + }, "node-fetch": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", @@ -5365,41 +6187,6 @@ "write-file-atomic": "1.3.4" } }, - "nomnom": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.8.1.tgz", - "integrity": "sha1-IVH3Ikcrp55Qp2/BJbuMjy5Nwqc=", - "dev": true, - "requires": { - "chalk": "0.4.0", - "underscore": "1.6.0" - }, - "dependencies": { - "ansi-styles": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", - "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=", - "dev": true - }, - "chalk": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", - "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", - "dev": true, - "requires": { - "ansi-styles": "1.0.0", - "has-color": "0.1.7", - "strip-ansi": "0.1.1" - } - }, - "strip-ansi": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", - "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=", - "dev": true - } - } - }, "nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", @@ -5448,6 +6235,12 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, + "object-keys": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", + "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", + "dev": true + }, "object.omit": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", @@ -5482,6 +6275,15 @@ "wrappy": "1.0.2" } }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "1.2.0" + } + }, "opn": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/opn/-/opn-4.0.2.tgz", @@ -5560,6 +6362,30 @@ "shell-quote": "1.6.1" } }, + "p-limit": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", + "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", + "dev": true, + "requires": { + "p-try": "1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "1.2.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, "pako": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", @@ -5586,6 +6412,19 @@ "create-hash": "1.1.3", "evp_bytestokey": "1.0.3", "pbkdf2": "3.0.14" + }, + "dependencies": { + "asn1.js": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.2.tgz", + "integrity": "sha512-b/OsSjvWEo8Pi8H0zsDd2P6Uqo2TK2pH8gNLSJtNLM2Db0v2QaAZ0pBQJXVjAn4gBuugeVDr7s63ZogpUIwWDg==", + "dev": true, + "requires": { + "bn.js": "4.11.8", + "inherits": "2.0.3", + "minimalistic-assert": "1.0.0" + } + } } }, "parse-glob": { @@ -5636,12 +6475,41 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "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", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, "path-type": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", @@ -5699,10 +6567,19 @@ "pinkie": "2.0.4" } }, - "pkginfo": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.4.1.tgz", - "integrity": "sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8=", + "pkg-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", + "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "dev": true, + "requires": { + "find-up": "1.1.2" + } + }, + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", "dev": true }, "portscanner": { @@ -5754,17 +6631,30 @@ "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", "dev": true }, - "prompt": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/prompt/-/prompt-0.2.14.tgz", - "integrity": "sha1-V3VPZPVD/XsIRXB8gY7OYY8F/9w=", + "progress": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "dev": true + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", "dev": true, "requires": { - "pkginfo": "0.4.1", - "read": "1.0.7", - "revalidator": "0.1.8", - "utile": "0.2.1", - "winston": "0.8.3" + "asap": "2.0.6" + } + }, + "prop-types": { + "version": "15.6.0", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz", + "integrity": "sha1-zq8IMCL8RrSjX2nhPvda7Q1jmFY=", + "dev": true, + "requires": { + "fbjs": "0.8.16", + "loose-envify": "1.3.1", + "object-assign": "4.1.1" } }, "proto-list": { @@ -5939,15 +6829,6 @@ } } }, - "read": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", - "dev": true, - "requires": { - "mute-stream": "0.0.7" - } - }, "read-only-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", @@ -6176,6 +7057,22 @@ "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": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "0.1.0", + "resolve-from": "1.0.1" + } + }, "requizzle": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.1.tgz", @@ -6183,26 +7080,38 @@ "dev": true, "requires": { "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 + } } }, - "reserved-words": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/reserved-words/-/reserved-words-0.1.2.tgz", - "integrity": "sha1-AKCUD5jNUBrqqsMWQR2a3FKzGrE=", - "dev": true - }, "resolve": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", "dev": true }, - "revalidator": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz", - "integrity": "sha1-/s5hv6DBtSoga9axgZgYS91SOjs=", + "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 }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "2.0.1", + "signal-exit": "3.0.2" + } + }, "right-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", @@ -6229,22 +7138,44 @@ "inherits": "2.0.3" } }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "2.1.0" + } + }, "rusha": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/rusha/-/rusha-0.8.7.tgz", - "integrity": "sha1-MGc7fpX6/g6+H+JN1tlf1gX5Tt4=", + "version": "0.8.12", + "resolved": "https://registry.npmjs.org/rusha/-/rusha-0.8.12.tgz", + "integrity": "sha1-XYOM4fzosUVnTudx6q1byyV15ks=" + }, + "rx-lite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", "dev": true }, + "rx-lite-aggregates": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "dev": true, + "requires": { + "rx-lite": "4.0.8" + } + }, "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", - "dev": true + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, "samsam": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.1.2.tgz", - "integrity": "sha1-vsEf3IOp/aBjQBIQ5AF2wwJNFWc=", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", + "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", "dev": true }, "sauce-tunnel": { @@ -6333,6 +7264,12 @@ "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", "dev": true }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, "setprototypeof": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", @@ -6359,6 +7296,21 @@ "sha.js": "2.4.9" } }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "1.0.0" + } + }, + "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", @@ -6371,12 +7323,6 @@ "jsonify": "0.0.0" } }, - "shelljs": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", - "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", - "dev": true - }, "sigmund": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", @@ -6390,15 +7336,35 @@ "dev": true }, "sinon": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.17.7.tgz", - "integrity": "sha1-RUKk9JugxFwF6y6d2dID4rjv4L8=", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.2.2.tgz", + "integrity": "sha512-BEa593xl+IkIc94nKo0O0LauQC/gQy8Gyv4DkzPwF/9DweC5phr1y+42zibCpn9abfkdHxt9r8AhD0R6u9DE/Q==", "dev": true, "requires": { - "formatio": "1.1.1", - "lolex": "1.3.2", - "samsam": "1.1.2", - "util": "0.10.3" + "diff": "3.3.1", + "formatio": "1.2.0", + "lodash.get": "4.4.2", + "lolex": "2.3.2", + "nise": "1.2.2", + "supports-color": "5.1.0", + "type-detect": "4.0.5" + }, + "dependencies": { + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "supports-color": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.1.0.tgz", + "integrity": "sha512-Ry0AwkoKjDpVKK4sV4h6o3UJmNRbjYm2uXhwfj3J56lMVdvnUNqzQVRztOOMGQ++w1K/TjNDFvpJk0F/LoeBCQ==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } } }, "slash": { @@ -6407,6 +7373,23 @@ "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", "dev": true }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0" + }, + "dependencies": { + "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 + } + } + }, "slide": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", @@ -6488,12 +7471,6 @@ "tweetnacl": "0.14.5" } }, - "stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", - "dev": true - }, "statuses": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", @@ -6597,9 +7574,9 @@ } }, "strip-json-comments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", - "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", + "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": { @@ -6634,12 +7611,106 @@ "acorn": "4.0.13" } }, + "table": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", + "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "dev": true, + "requires": { + "ajv": "5.5.0", + "ajv-keywords": "2.1.1", + "chalk": "2.3.0", + "lodash": "4.17.4", + "slice-ansi": "1.0.0", + "string-width": "2.1.1" + }, + "dependencies": { + "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.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", + "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.5.0" + } + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "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 + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "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": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, "taffydb": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=", "dev": true }, + "text-encoding": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", + "dev": true + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -6722,30 +7793,27 @@ } } }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "1.0.2" + } + }, "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-double-quotes": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-double-quotes/-/to-double-quotes-2.0.0.tgz", - "integrity": "sha1-qvIx1vqUiUn4GTAburRITYWI5Kc=", - "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-single-quotes": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/to-single-quotes/-/to-single-quotes-2.0.1.tgz", - "integrity": "sha1-fMKRUfD18sQZRvEZ9ZMv5VQXASU=", - "dev": true - }, "tough-cookie": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", @@ -6820,6 +7888,12 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, + "ua-parser-js": { + "version": "0.7.17", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz", + "integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g==", + "dev": true + }, "uglify-js": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.2.0.tgz", @@ -6852,9 +7926,9 @@ "dev": true }, "underscore": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", - "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", "dev": true }, "underscore-contrib": { @@ -6864,6 +7938,14 @@ "dev": true, "requires": { "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 + } } }, "underscore.string": { @@ -6925,40 +8007,12 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, - "utile": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/utile/-/utile-0.2.1.tgz", - "integrity": "sha1-kwyI6ZCY1iIINMNWy9mncFItkNc=", - "dev": true, - "requires": { - "async": "0.2.10", - "deep-equal": "1.0.1", - "i": "0.3.6", - "mkdirp": "0.5.1", - "ncp": "0.4.2", - "rimraf": "2.2.8" - }, - "dependencies": { - "async": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", - "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", - "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": "2.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", - "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=", - "dev": true - }, "validate-npm-package-license": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", @@ -6989,33 +8043,6 @@ "indexof": "0.0.1" } }, - "vow": { - "version": "0.4.17", - "resolved": "https://registry.npmjs.org/vow/-/vow-0.4.17.tgz", - "integrity": "sha512-A3/9bWFqf6gT0jLR4/+bT+IPTe1mQf+tdsW6+WI5geP9smAp8Kbbu4R6QQCDKZN/8TSCqTlXVQm12QliB4rHfg==", - "dev": true - }, - "vow-fs": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/vow-fs/-/vow-fs-0.3.6.tgz", - "integrity": "sha1-LUxZviLivyYY3fWXq0uqkjvnIA0=", - "dev": true, - "requires": { - "glob": "7.0.6", - "uuid": "2.0.3", - "vow": "0.4.17", - "vow-queue": "0.4.3" - } - }, - "vow-queue": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/vow-queue/-/vow-queue-0.4.3.tgz", - "integrity": "sha512-/poAKDTFL3zYbeQg7cl4BGcfP4sGgXKrHnRFSKj97dteUFu8oyXMwIcdwu8NSx/RmPGIuYx1Bik/y5vU4H/VKw==", - "dev": true, - "requires": { - "vow": "0.4.17" - } - }, "watchify": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/watchify/-/watchify-3.9.0.tgz", @@ -7093,41 +8120,6 @@ "dev": true, "optional": true }, - "winston": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/winston/-/winston-0.8.3.tgz", - "integrity": "sha1-ZLar9M0Brcrv1QCTk7HY6L7BnbA=", - "dev": true, - "requires": { - "async": "0.2.10", - "colors": "0.6.2", - "cycle": "1.0.3", - "eyes": "0.1.8", - "isstream": "0.1.2", - "pkginfo": "0.3.1", - "stack-trace": "0.0.10" - }, - "dependencies": { - "async": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", - "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=", - "dev": true - }, - "colors": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", - "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", - "dev": true - }, - "pkginfo": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz", - "integrity": "sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=", - "dev": true - } - } - }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -7150,6 +8142,15 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "0.5.1" + } + }, "write-file-atomic": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", @@ -7160,23 +8161,6 @@ "slide": "1.1.6" } }, - "xmlbuilder": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-3.1.0.tgz", - "integrity": "sha1-LIaIjy1OrehQ+jjKf3Ij9yCVFuE=", - "dev": true, - "requires": { - "lodash": "3.10.1" - }, - "dependencies": { - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", - "dev": true - } - } - }, "xmlcreate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-1.0.2.tgz", diff --git a/package.json b/package.json index bc7f0f1a..b5ae7875 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "openpgp", "description": "OpenPGP.js is a Javascript implementation of the OpenPGP protocol. This is defined in RFC 4880.", - "version": "2.6.2", + "version": "3.0.0", "license": "LGPL-3.0+", "homepage": "http://openpgpjs.org/", "engines": { @@ -32,38 +32,58 @@ "test": "grunt test" }, "devDependencies": { - "asmcrypto-lite": "^1.0.0", "babel-core": "^6.26.0", - "babel-preset-es2015": "^6.3.13", + "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.6.1", + "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", - "es6-promise": "^4.1.1", + "chai": "^4.1.2", + "chai-as-promised": "^7.1.1", + "core-js": "^2.5.3", + "eslint": "^4.16.0", + "eslint-config-airbnb": "^16.1.0", + "eslint-config-airbnb-base": "^12.1.0", + "eslint-plugin-import": "^2.8.0", + "eslint-plugin-jsx-a11y": "^6.0.3", + "eslint-plugin-react": "^7.6.1", "grunt": "~1.0.1", "grunt-browserify": "~5.2.0", "grunt-contrib-clean": "~1.1.0", "grunt-contrib-connect": "~1.0.2", "grunt-contrib-copy": "~1.0.0", - "grunt-contrib-jshint": "~1.1.0", "grunt-contrib-uglify": "~3.2.1", "grunt-contrib-watch": "^1.0.0", - "grunt-jsbeautifier": "~0.2.10", - "grunt-jscs": "^3.0.1", - "grunt-jsdoc": "~2.2.0", - "grunt-mocha-istanbul": "^5.0.1", - "grunt-mocha-test": "~0.13.3", + "grunt-jsbeautifier": "^0.2.13", + "grunt-jsdoc": "^2.2.1", + "grunt-keepalive": "^1.0.0", + "grunt-mocha-istanbul": "^5.0.2", + "grunt-mocha-test": "^0.13.3", "grunt-saucelabs": "9.0.0", "grunt-text-replace": "~0.4.0", - "istanbul": "~0.4.1", - "mocha": "~4.0.1", - "rusha": "^0.8.3", - "sinon": "^1.17.3", - "whatwg-fetch": "~2.0.3", + "gruntify-eslint": "^4.0.0", + "istanbul": "^0.4.5", + "mocha": "^4.1.0", + "sinon": "^4.2.2", + "whatwg-fetch": "^2.0.3", "zlibjs": "~0.3.1" }, "dependencies": { - "node-fetch": "^1.3.3", - "node-localstorage": "~1.3.0" + "asmcrypto-lite": "git+https://github.com/openpgpjs/asmcrypto-lite.git", + "asn1.js": "^5.0.0", + "bn.js": "^4.11.8", + "buffer": "^5.0.8", + "elliptic": "git+https://github.com/openpgpjs/elliptic.git", + "jwk-to-pem": "^1.2.6", + "node-fetch": "^1.7.3", + "node-localstorage": "~1.3.0", + "rusha": "^0.8.12" }, "repository": { "type": "git", diff --git a/src/cleartext.js b/src/cleartext.js index 94b56eb3..970621ea 100644 --- a/src/cleartext.js +++ b/src/cleartext.js @@ -20,16 +20,17 @@ * @requires encoding/armor * @requires enums * @requires packet + * @requires signature * @module cleartext */ 'use strict'; import config from './config'; +import armor from './encoding/armor'; +import enums from './enums'; import packet from './packet'; -import enums from './enums.js'; -import armor from './encoding/armor.js'; -import * as sigModule from './signature.js'; +import { Signature } from './signature'; /** * @class @@ -45,10 +46,10 @@ export function CleartextMessage(text, signature) { } // normalize EOL to canonical form this.text = text.replace(/\r/g, '').replace(/[\t ]+\n/g, "\n").replace(/\n/g,"\r\n"); - if (signature && !(signature instanceof sigModule.Signature)) { + if (signature && !(signature instanceof Signature)) { throw new Error('Invalid signature input'); } - this.signature = signature || new sigModule.Signature(new packet.List()); + this.signature = signature || new Signature(new packet.List()); } /** @@ -69,8 +70,8 @@ CleartextMessage.prototype.getSigningKeyIds = function() { * @param {Array} privateKeys private keys with decrypted secret key data for signing * @return {module:message~CleartextMessage} new cleartext message with signed content */ -CleartextMessage.prototype.sign = function(privateKeys) { - return new CleartextMessage(this.text, this.signDetached(privateKeys)); +CleartextMessage.prototype.sign = async function(privateKeys) { + return new CleartextMessage(this.text, await this.signDetached(privateKeys)); }; /** @@ -78,26 +79,34 @@ CleartextMessage.prototype.sign = function(privateKeys) { * @param {Array} privateKeys private keys with decrypted secret key data for signing * @return {module:signature~Signature} new detached signature of message content */ -CleartextMessage.prototype.signDetached = function(privateKeys) { +CleartextMessage.prototype.signDetached = async function(privateKeys) { var packetlist = new packet.List(); var literalDataPacket = new packet.Literal(); literalDataPacket.setText(this.text); - for (var i = 0; i < privateKeys.length; i++) { - if (privateKeys[i].isPublic()) { + await Promise.all(privateKeys.map(async function(privateKey) { + if (privateKey.isPublic()) { throw new Error('Need private key for signing'); } + await privateKey.verifyPrimaryUser(); + var signingKeyPacket = privateKey.getSigningKeyPacket(); + if (!signingKeyPacket) { + throw new Error('Could not find valid key packet for signing in key ' + + privateKey.primaryKey.getKeyId().toHex()); + } var signaturePacket = new packet.Signature(); signaturePacket.signatureType = enums.signature.text; signaturePacket.hashAlgorithm = config.prefer_hash_algorithm; - var signingKeyPacket = privateKeys[i].getSigningKeyPacket(); signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; if (!signingKeyPacket.isDecrypted) { throw new Error('Private key is not decrypted.'); } - signaturePacket.sign(signingKeyPacket, literalDataPacket); - packetlist.push(signaturePacket); - } - return new sigModule.Signature(packetlist); + await signaturePacket.sign(signingKeyPacket, literalDataPacket); + return signaturePacket; + })).then(signatureList => { + signatureList.forEach(signaturePacket => packetlist.push(signaturePacket)); + }); + + return new Signature(packetlist); }; /** @@ -115,36 +124,32 @@ CleartextMessage.prototype.verify = function(keys) { * @return {Array<{keyid: module:type/keyid, valid: Boolean}>} list of signer's keyid and validity of signature */ CleartextMessage.prototype.verifyDetached = function(signature, keys) { - var result = []; var signatureList = signature.packets; var literalDataPacket = new packet.Literal(); // we assume that cleartext signature is generated based on UTF8 cleartext literalDataPacket.setText(this.text); - for (var i = 0; i < signatureList.length; i++) { + return Promise.all(signatureList.map(async function(signature) { var keyPacket = null; - for (var j = 0; j < keys.length; j++) { - keyPacket = keys[j].getSigningKeyPacket(signatureList[i].issuerKeyId); - if (keyPacket) { - break; + await Promise.all(keys.map(async function(key) { + await key.verifyPrimaryUser(); + // Look for the unique key packet that matches issuerKeyId of signature + var result = key.getSigningKeyPacket(signature.issuerKeyId, config.verify_expired_keys); + if (result) { + keyPacket = result; } - } + })); - var verifiedSig = {}; - if (keyPacket) { - verifiedSig.keyid = signatureList[i].issuerKeyId; - verifiedSig.valid = signatureList[i].verify(keyPacket, literalDataPacket); - } else { - verifiedSig.keyid = signatureList[i].issuerKeyId; - verifiedSig.valid = null; - } + var verifiedSig = { + keyid: signature.issuerKeyId, + valid: keyPacket ? await signature.verify(keyPacket, literalDataPacket) : null + }; var packetlist = new packet.List(); - packetlist.push(signatureList[i]); - verifiedSig.signature = new sigModule.Signature(packetlist); + packetlist.push(signature); + verifiedSig.signature = new Signature(packetlist); - result.push(verifiedSig); - } - return result; + return verifiedSig; + })); }; /** @@ -184,7 +189,7 @@ export function readArmored(armoredText) { var packetlist = new packet.List(); packetlist.read(input.data); verifyHeaders(input.headers, packetlist); - var signature = new sigModule.Signature(packetlist); + var signature = new Signature(packetlist); var newMessage = new CleartextMessage(input.text, signature); return newMessage; } diff --git a/src/config/config.js b/src/config/config.js index 0e9b2287..a7ae24b0 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -16,16 +16,8 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA /** - * This object contains configuration values. + * This object contains global configuration values. * @requires enums - * @property {Integer} prefer_hash_algorithm - * @property {Integer} encryption_cipher - * @property {Integer} compression - * @property {Boolean} show_version - * @property {Boolean} show_comment - * @property {Boolean} integrity_protect - * @property {String} keyserver - * @property {Boolean} debug If enabled, debug messages will be printed * @module config/config */ @@ -34,23 +26,56 @@ import enums from '../enums.js'; export default { + /** @property {Integer} prefer_hash_algorithm Default hash algorithm {@link module:enums.hash} */ prefer_hash_algorithm: enums.hash.sha256, + /** @property {Integer} encryption_cipher Default encryption cipher {@link module:enums.symmetric} */ encryption_cipher: enums.symmetric.aes256, + /** @property {Integer} compression Default compression algorithm {@link module:enums.compression} */ compression: enums.compression.zip, - aead_protect: false, // use Authenticated Encryption with Additional Data (AEAD) protection for symmetric encryption - integrity_protect: true, // use integrity protection for symmetric encryption - ignore_mdc_error: false, // fail on decrypt if message is not integrity protected - checksum_required: false, // do not throw error when armor is missing a checksum - verify_expired_keys: true, // allow signature verification with expired keys - rsa_blinding: true, - use_native: true, // use native node.js crypto and Web Crypto apis (if available) - zero_copy: false, // use transferable objects between the Web Worker and main thread - debug: false, - tolerant: true, // ignore unsupported/unrecognizable packets instead of throwing an error + + /** + * Use Authenticated Encryption with Additional Data (AEAD) protection for symmetric encryption. + * **NOT INTEROPERABLE WITH OTHER OPENPGP IMPLEMENTATIONS** + * @property {Boolean} aead_protect + */ + aead_protect: false, + /** Use integrity protection for symmetric encryption + * @property {Boolean} integrity_protect */ + integrity_protect: true, + /** @property {Boolean} ignore_mdc_error Fail on decrypt if message is not integrity protected */ + ignore_mdc_error: false, + /** @property {Boolean} checksum_required Do not throw error when armor is missing a checksum */ + checksum_required: false, + /** @property {Boolean} verify_expired_keys Allow signature verification with expired keys */ + verify_expired_keys: true, + /** @property {Boolean} rsa_blinding */ + rsa_blinding: true, + /** Work-around for rare GPG decryption bug when encrypting with multiple passwords + * Slower and slightly less secure + * @property {Boolean} password_collision_check + */ + password_collision_check: false, + + /** @property {Boolean} use_native Use native Node.js crypto and WebCrypto API's when available */ + use_native: true, + /** @property {Boolean} Use transferable objects between the Web Worker and main thread */ + zero_copy: false, + /** @property {Boolean} debug If enabled, debug messages will be printed */ + debug: false, + /** @property {Boolean} tolerant Ignore unsupported/unrecognizable packets instead of throwing an error */ + tolerant: true, + + /** @property {Boolean} show_version Whether to include {@link module:config/config.versionstring} in armored messages */ show_version: true, + /** @property {Boolean} show_comment Whether to include {@link module:config/config.commentstring} in armored messages */ show_comment: true, + /** @property {String} versionstring A version string to be included in armored messages */ versionstring: "OpenPGP.js VERSION", + /** @property {String} commentstring A comment string to be included in armored messages */ commentstring: "https://openpgpjs.org", - keyserver: "https://keyserver.ubuntu.com", - node_store: './openpgp.store' + + /** @property {String} keyserver */ + keyserver: "https://keyserver.ubuntu.com", + /** @property {String} node_store */ + node_store: "./openpgp.store" }; diff --git a/src/crypto/aes_kw.js b/src/crypto/aes_kw.js new file mode 100644 index 00000000..e96172c5 --- /dev/null +++ b/src/crypto/aes_kw.js @@ -0,0 +1,132 @@ +// OpenPGP.js - An OpenPGP implementation in javascript +// Copyright (C) 2015-2016 Decentral +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 3.0 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +// Implementation of RFC 3394 AES Key Wrap & Key Unwrap funcions + +import cipher from './cipher'; +import util from '../util'; + +function wrap(key, data) { + var aes = new cipher["aes" + (key.length*8)](key); + var IV = new Uint32Array([0xA6A6A6A6, 0xA6A6A6A6]); + var P = unpack(data); + var A = IV; + var R = P; + var n = P.length/2; + var t = new Uint32Array([0, 0]); + var B = new Uint32Array(4); + for (var j = 0; j <= 5; ++j) { + for (var i = 0; i < n; ++i) { + t[1] = n * j + (1 + i); + // B = A + B[0] = A[0]; + B[1] = A[1]; + // B = A || R[i] + B[2] = R[2*i]; + B[3] = R[2*i+1]; + // B = AES(K, B) + B = unpack(aes.encrypt(pack(B))); + // A = MSB(64, B) ^ t + A = B.subarray(0, 2); + A[0] = A[0] ^ t[0]; + A[1] = A[1] ^ t[1]; + // R[i] = LSB(64, B) + R[2*i] = B[2]; + R[2*i+1] = B[3]; + } + } + return pack(A, R); +} + +function unwrap(key, data) { + var aes = new cipher["aes" + (key.length*8)](key); + var IV = new Uint32Array([0xA6A6A6A6, 0xA6A6A6A6]); + var C = unpack(data); + var A = C.subarray(0, 2); + var R = C.subarray(2); + var n = C.length/2-1; + var t = new Uint32Array([0, 0]); + var B = new Uint32Array(4); + for (var j = 5; j >= 0; --j) { + for (var i = n - 1; i >= 0; --i) { + t[1] = n * j + (i + 1); + // B = A ^ t + B[0] = A[0] ^ t[0]; + B[1] = A[1] ^ t[1]; + // B = (A ^ t) || R[i] + B[2] = R[2*i]; + B[3] = R[2*i+1]; + // B = AES-1(B) + B = unpack(aes.decrypt(pack(B))); + // A = MSB(64, B) + A = B.subarray(0, 2); + // R[i] = LSB(64, B) + R[2*i] = B[2]; + R[2*i+1] = B[3]; + } + } + if (A[0] === IV[0] && A[1] === IV[1]) { + return pack(R); + } + throw new Error("Key Data Integrity failed"); +} + +function createArrayBuffer(data) { + if (util.isString(data)) { + var length = data.length; + var buffer = new ArrayBuffer(length); + var view = new Uint8Array(buffer); + for (var j = 0; j < length; ++j) { + view[j] = data.charCodeAt(j); + } + return buffer; + } + return new Uint8Array(data).buffer; +} + +function unpack(data) { + var length = data.length; + var buffer = createArrayBuffer(data); + var view = new DataView(buffer); + var arr = new Uint32Array(length/4); + for (var i=0; i> 8) & 255); -} - -function B2(x) { - return ((x >> 16) & 255); -} - -function B3(x) { - return ((x >> 24) & 255); -} - -function F1(x0, x1, x2, x3) { - return B1(T1[x0 & 255]) | (B1(T1[(x1 >> 8) & 255]) << 8) | (B1(T1[(x2 >> 16) & 255]) << 16) | (B1(T1[x3 >>> 24]) << 24); -} - -function packBytes(octets) { - var i, j; - var len = octets.length; - var b = new Array(len / 4); - - if (!octets || len % 4) { - return; - } - - for (i = 0, j = 0; j < len; j += 4) { - b[i++] = octets[j] | (octets[j + 1] << 8) | (octets[j + 2] << 16) | (octets[j + 3] << 24); - } - - return b; -} - -function unpackBytes(packed) { - var j; - var i = 0, - l = packed.length; - var r = new Array(l * 4); - - for (j = 0; j < l; j++) { - r[i++] = B0(packed[j]); - r[i++] = B1(packed[j]); - r[i++] = B2(packed[j]); - r[i++] = B3(packed[j]); - } - return r; -} - -// ------------------------------------------------ - -var maxkc = 8; -var maxrk = 14; - -function keyExpansion(key) { - var kc, i, j, r, t; - var rounds; - var keySched = new Array(maxrk + 1); - var keylen = key.length; - var k = new Array(maxkc); - var tk = new Array(maxkc); - var rconpointer = 0; - - if (keylen === 16) { - rounds = 10; - kc = 4; - } else if (keylen === 24) { - rounds = 12; - kc = 6; - } else if (keylen === 32) { - rounds = 14; - kc = 8; - } else { - throw new Error('Invalid key-length for AES key:' + keylen); - } - - for (i = 0; i < maxrk + 1; i++) { - keySched[i] = new Uint32Array(4); - } - - for (i = 0, j = 0; j < keylen; j++, i += 4) { - k[j] = key[i] | (key[i + 1] << 8) | (key[i + 2] << 16) | (key[i + 3] << 24); - } - - for (j = kc - 1; j >= 0; j--) { - tk[j] = k[j]; - } - - r = 0; - t = 0; - for (j = 0; (j < kc) && (r < rounds + 1);) { - for (; (j < kc) && (t < 4); j++, t++) { - keySched[r][t] = tk[j]; - } - if (t === 4) { - r++; - t = 0; - } - } - - while (r < rounds + 1) { - var temp = tk[kc - 1]; - - tk[0] ^= S[B1(temp)] | (S[B2(temp)] << 8) | (S[B3(temp)] << 16) | (S[B0(temp)] << 24); - tk[0] ^= Rcon[rconpointer++]; - - if (kc !== 8) { - for (j = 1; j < kc; j++) { - tk[j] ^= tk[j - 1]; - } - } else { - for (j = 1; j < kc / 2; j++) { - tk[j] ^= tk[j - 1]; - } - - temp = tk[kc / 2 - 1]; - tk[kc / 2] ^= S[B0(temp)] | (S[B1(temp)] << 8) | (S[B2(temp)] << 16) | (S[B3(temp)] << 24); - - for (j = kc / 2 + 1; j < kc; j++) { - tk[j] ^= tk[j - 1]; - } - } - - for (j = 0; (j < kc) && (r < rounds + 1);) { - for (; (j < kc) && (t < 4); j++, t++) { - keySched[r][t] = tk[j]; - } - if (t === 4) { - r++; - t = 0; - } - } - } - - return { - rounds: rounds, - rk: keySched - }; -} - -function AESencrypt(block, ctx, t) { - var r, rounds, b; - - b = packBytes(block); - rounds = ctx.rounds; - - for (r = 0; r < rounds - 1; r++) { - t[0] = b[0] ^ ctx.rk[r][0]; - t[1] = b[1] ^ ctx.rk[r][1]; - t[2] = b[2] ^ ctx.rk[r][2]; - t[3] = b[3] ^ ctx.rk[r][3]; - - b[0] = T1[t[0] & 255] ^ T2[(t[1] >> 8) & 255] ^ T3[(t[2] >> 16) & 255] ^ T4[t[3] >>> 24]; - b[1] = T1[t[1] & 255] ^ T2[(t[2] >> 8) & 255] ^ T3[(t[3] >> 16) & 255] ^ T4[t[0] >>> 24]; - b[2] = T1[t[2] & 255] ^ T2[(t[3] >> 8) & 255] ^ T3[(t[0] >> 16) & 255] ^ T4[t[1] >>> 24]; - b[3] = T1[t[3] & 255] ^ T2[(t[0] >> 8) & 255] ^ T3[(t[1] >> 16) & 255] ^ T4[t[2] >>> 24]; - } - - // last round is special - r = rounds - 1; - - t[0] = b[0] ^ ctx.rk[r][0]; - t[1] = b[1] ^ ctx.rk[r][1]; - t[2] = b[2] ^ ctx.rk[r][2]; - t[3] = b[3] ^ ctx.rk[r][3]; - - b[0] = F1(t[0], t[1], t[2], t[3]) ^ ctx.rk[rounds][0]; - b[1] = F1(t[1], t[2], t[3], t[0]) ^ ctx.rk[rounds][1]; - b[2] = F1(t[2], t[3], t[0], t[1]) ^ ctx.rk[rounds][2]; - b[3] = F1(t[3], t[0], t[1], t[2]) ^ ctx.rk[rounds][3]; - - return unpackBytes(b); -} - -function makeClass(length) { +// TODO use webCrypto or nodeCrypto when possible. +export default function aes(length) { var c = function(key) { - this.key = keyExpansion(key); - this._temp = new Uint32Array(this.blockSize / 4); + this.key = Uint8Array.from(key); this.encrypt = function(block) { - return AESencrypt(block, this.key, this._temp); + block = Uint8Array.from(block); + return Array.from(asmCrypto.AES_ECB.encrypt(block, this.key, false)); + }; + + this.decrypt = function(block) { + block = Uint8Array.from(block); + return Array.from(asmCrypto.AES_ECB.decrypt(block, this.key, false)); }; }; @@ -506,9 +28,3 @@ function makeClass(length) { return c; } - -export default { - 128: makeClass(128), - 192: makeClass(192), - 256: makeClass(256) -}; \ No newline at end of file diff --git a/src/crypto/cipher/blowfish.js b/src/crypto/cipher/blowfish.js index e910b050..279e4497 100644 --- a/src/crypto/cipher/blowfish.js +++ b/src/crypto/cipher/blowfish.js @@ -312,8 +312,8 @@ Blowfish.prototype.encrypt_block = function(vector) { var ret = []; for (ii = 0; ii < this.BLOCKSIZE / 2; ++ii) { - ret[ii + 0] = (vals[0] >>> (24 - 8 * (ii)) & 0x00FF); - ret[ii + off] = (vals[1] >>> (24 - 8 * (ii)) & 0x00FF); + ret[ii + 0] = ((vals[0] >>> (24 - 8 * (ii))) & 0x00FF); + ret[ii + off] = ((vals[1] >>> (24 - 8 * (ii))) & 0x00FF); // vals[ 0 ] = ( vals[ 0 ] >>> 8 ); // vals[ 1 ] = ( vals[ 1 ] >>> 8 ); } @@ -404,4 +404,4 @@ export default function BF(key) { }; } BF.keySize = BF.prototype.keySize = 16; -BF.blockSize = BF.prototype.blockSize = 16; \ No newline at end of file +BF.blockSize = BF.prototype.blockSize = 16; diff --git a/src/crypto/cipher/cast5.js b/src/crypto/cipher/cast5.js index cf2ef6f1..3a4a2d0c 100644 --- a/src/crypto/cipher/cast5.js +++ b/src/crypto/cipher/cast5.js @@ -51,8 +51,8 @@ function OpenpgpSymencCast5() { var dst = new Array(src.length); for (var i = 0; i < src.length; i += 8) { - var l = src[i] << 24 | src[i + 1] << 16 | src[i + 2] << 8 | src[i + 3]; - var r = src[i + 4] << 24 | src[i + 5] << 16 | src[i + 6] << 8 | src[i + 7]; + var l = (src[i] << 24) | (src[i + 1] << 16) | (src[i + 2] << 8) | src[i + 3]; + var r = (src[i + 4] << 24) | (src[i + 5] << 16) | (src[i + 6] << 8) | src[i + 7]; var t; t = r; @@ -124,8 +124,8 @@ function OpenpgpSymencCast5() { var dst = new Array(src.length); for (var i = 0; i < src.length; i += 8) { - var l = src[i] << 24 | src[i + 1] << 16 | src[i + 2] << 8 | src[i + 3]; - var r = src[i + 4] << 24 | src[i + 5] << 16 | src[i + 6] << 8 | src[i + 7]; + var l = (src[i] << 24) | (src[i + 1] << 16) | (src[i + 2] << 8) | src[i + 3]; + var r = (src[i + 4] << 24) | (src[i + 5] << 16) | (src[i + 6] << 8) | src[i + 7]; var t; t = r; @@ -256,7 +256,7 @@ function OpenpgpSymencCast5() { for (i = 0; i < 4; i++) { j = i * 4; - t[i] = inn[j] << 24 | inn[j + 1] << 16 | inn[j + 2] << 8 | inn[j + 3]; + t[i] = (inn[j] << 24) | (inn[j + 1] << 16) | (inn[j + 2] << 8) | inn[j + 3]; } var x = [6, 7, 4, 5]; @@ -602,4 +602,4 @@ export default function Cast5(key) { } Cast5.blockSize = Cast5.prototype.blockSize = 8; -Cast5.keySize = Cast5.prototype.keySize = 16; \ No newline at end of file +Cast5.keySize = Cast5.prototype.keySize = 16; diff --git a/src/crypto/cipher/des.js b/src/crypto/cipher/des.js index 81c72a04..321f3bce 100644 --- a/src/crypto/cipher/des.js +++ b/src/crypto/cipher/des.js @@ -35,13 +35,13 @@ function des(keys, message, encrypt, mode, iv, padding) { 0x1010000, 0x1010400, 0x1000000, 0x1000000, 0x400, 0x1010004, 0x10000, 0x10400, 0x1000004, 0x400, 0x4, 0x1000404, 0x10404, 0x1010404, 0x10004, 0x1010000, 0x1000404, 0x1000004, 0x404, 0x10404, 0x1010400, 0x404, 0x1000400, 0x1000400, 0, 0x10004, 0x10400, 0, 0x1010004); - var spfunction2 = new Array(-0x7fef7fe0, -0x7fff8000, 0x8000, 0x108020, 0x100000, 0x20, -0x7fefffe0, -0x7fff7fe0, - - 0x7fffffe0, -0x7fef7fe0, -0x7fef8000, -0x80000000, -0x7fff8000, 0x100000, 0x20, -0x7fefffe0, 0x108000, 0x100020, - - 0x7fff7fe0, 0, -0x80000000, 0x8000, 0x108020, -0x7ff00000, 0x100020, -0x7fffffe0, 0, 0x108000, 0x8020, -0x7fef8000, - - 0x7ff00000, 0x8020, 0, 0x108020, -0x7fefffe0, 0x100000, -0x7fff7fe0, -0x7ff00000, -0x7fef8000, 0x8000, -0x7ff00000, - - 0x7fff8000, 0x20, -0x7fef7fe0, 0x108020, 0x20, 0x8000, -0x80000000, 0x8020, -0x7fef8000, 0x100000, -0x7fffffe0, - 0x100020, -0x7fff7fe0, -0x7fffffe0, 0x100020, 0x108000, 0, -0x7fff8000, 0x8020, -0x80000000, -0x7fefffe0, - - 0x7fef7fe0, 0x108000); + var spfunction2 = new Array(-0x7fef7fe0, -0x7fff8000, 0x8000, 0x108020, 0x100000, 0x20, -0x7fefffe0, -0x7fff7fe0, + -0x7fffffe0, -0x7fef7fe0, -0x7fef8000, -0x80000000, -0x7fff8000, 0x100000, 0x20, -0x7fefffe0, 0x108000, 0x100020, + -0x7fff7fe0, 0, -0x80000000, 0x8000, 0x108020, -0x7ff00000, 0x100020, -0x7fffffe0, 0, 0x108000, 0x8020, -0x7fef8000, + -0x7ff00000, 0x8020, 0, 0x108020, -0x7fefffe0, 0x100000, -0x7fff7fe0, -0x7ff00000, -0x7fef8000, 0x8000, -0x7ff00000, + -0x7fff8000, 0x20, -0x7fef7fe0, 0x108020, 0x20, 0x8000, -0x80000000, 0x8020, -0x7fef8000, 0x100000, -0x7fffffe0, + 0x100020, -0x7fff7fe0, -0x7fffffe0, 0x100020, 0x108000, 0, -0x7fff8000, 0x8020, -0x80000000, -0x7fefffe0, + -0x7fef7fe0, 0x108000); var spfunction3 = new Array(0x208, 0x8020200, 0, 0x8020008, 0x8000200, 0, 0x20208, 0x8000200, 0x20008, 0x8000008, 0x8000008, 0x20000, 0x8020208, 0x20008, 0x8020000, 0x208, 0x8000000, 0x8, 0x8020200, 0x200, 0x20200, 0x8020000, 0x8020008, 0x20208, 0x8000208, 0x20200, 0x20000, 0x8000208, 0x8, 0x8020208, 0x200, 0x8000000, 0x8020200, 0x8000000, @@ -220,7 +220,6 @@ function des(keys, message, encrypt, mode, iv, padding) { } //end of des - //des_createKeys //this takes as input a 64 bit key (even though only 56 bits are used) //as an array of 2 integers, and returns 16 48 bit keys @@ -427,4 +426,4 @@ export default { des: Des, /** @static */ originalDes: OriginalDes -}; \ No newline at end of file +}; diff --git a/src/crypto/cipher/index.js b/src/crypto/cipher/index.js index ec09835a..3f1274a6 100644 --- a/src/crypto/cipher/index.js +++ b/src/crypto/cipher/index.js @@ -8,7 +8,7 @@ 'use strict'; -import aes from'./aes.js'; +import aes from './aes.js'; import desModule from './des.js'; import cast5 from './cast5.js'; import twofish from './twofish.js'; @@ -16,9 +16,9 @@ import blowfish from './blowfish.js'; export default { /** @see module:crypto/cipher/aes */ - aes128: aes[128], - aes192: aes[192], - aes256: aes[256], + aes128: aes(128), + aes192: aes(192), + aes256: aes(256), /** @see module:crypto/cipher/des.originalDes */ des: desModule.originalDes, /** @see module:crypto/cipher/des.des */ @@ -33,4 +33,4 @@ export default { idea: function() { throw new Error('IDEA symmetric-key algorithm not implemented'); } -}; \ No newline at end of file +}; diff --git a/src/crypto/crypto.js b/src/crypto/crypto.js index f6e17403..3ba98cf7 100644 --- a/src/crypto/crypto.js +++ b/src/crypto/crypto.js @@ -21,7 +21,10 @@ * @requires crypto/cipher * @requires crypto/public_key * @requires crypto/random + * @requires type/ecdh_symkey + * @requires type/kdf_params * @requires type/mpi + * @requires type/oid * @module crypto/crypto */ @@ -30,66 +33,82 @@ import random from './random.js'; import cipher from './cipher'; import publicKey from './public_key'; +import type_ecdh_symkey from '../type/ecdh_symkey.js'; +import type_kdf_params from '../type/kdf_params.js'; import type_mpi from '../type/mpi.js'; +import type_oid from '../type/oid.js'; + + +function constructParams(types, data) { + return types.map(function(type, i) { + if (data && data[i]) { + return new type(data[i]); + } else { + return new type(); + } + }); +} export default { /** * Encrypts data using the specified public key multiprecision integers * and the specified algorithm. * @param {module:enums.publicKey} algo Algorithm to be used (See {@link http://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1}) - * @param {Array} publicMPIs Algorithm dependent multiprecision integers + * @param {Array} publicParams Algorithm dependent params * @param {module:type/mpi} data Data to be encrypted as MPI - * @return {Array} if RSA an module:type/mpi; - * if elgamal encryption an array of two module:type/mpi is returned; otherwise null + * @param {String} fingerprint Recipient fingerprint + * @return {Array} encrypted session key parameters */ - publicKeyEncrypt: function(algo, publicMPIs, data) { - var result = (function() { + publicKeyEncrypt: async function(algo, publicParams, data, fingerprint) { + var types = this.getEncSessionKeyParamTypes(algo); + return (async function() { var m; switch (algo) { case 'rsa_encrypt': case 'rsa_encrypt_sign': var rsa = new publicKey.rsa(); - var n = publicMPIs[0].toBigInteger(); - var e = publicMPIs[1].toBigInteger(); + var n = publicParams[0].toBigInteger(); + var e = publicParams[1].toBigInteger(); m = data.toBigInteger(); - return [rsa.encrypt(m, e, n)]; + return constructParams(types, [rsa.encrypt(m, e, n)]); case 'elgamal': var elgamal = new publicKey.elgamal(); - var p = publicMPIs[0].toBigInteger(); - var g = publicMPIs[1].toBigInteger(); - var y = publicMPIs[2].toBigInteger(); + var p = publicParams[0].toBigInteger(); + var g = publicParams[1].toBigInteger(); + var y = publicParams[2].toBigInteger(); m = data.toBigInteger(); - return elgamal.encrypt(m, g, p, y); + return constructParams(types, elgamal.encrypt(m, g, p, y)); + + case 'ecdh': + var ecdh = publicKey.elliptic.ecdh; + var curve = publicParams[0]; + var kdf_params = publicParams[2]; + var R = publicParams[1].toBigInteger(); + var res = await ecdh.encrypt( + curve.oid, kdf_params.cipher, kdf_params.hash, data, R, fingerprint + ); + return constructParams(types, [res.V, res.C]); default: return []; } - })(); - - return result.map(function(bn) { - var mpi = new type_mpi(); - mpi.fromBigInteger(bn); - return mpi; - }); + }()); }, /** * Decrypts data using the specified public key multiprecision integers of the private key, * the specified secretMPIs of the private key and the specified algorithm. * @param {module:enums.publicKey} algo Algorithm to be used (See {@link http://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1}) - * @param {Array} publicMPIs Algorithm dependent multiprecision integers - * of the public key part of the private key - * @param {Array} secretMPIs Algorithm dependent multiprecision integers - * of the private key used - * @param {module:type/mpi} data Data to be encrypted as MPI + * @param {Array} keyIntegers Algorithm dependent params + * @param {Array} dataIntegers encrypted session key parameters + * @param {String} fingerprint Recipient fingerprint * @return {module:type/mpi} returns a big integer containing the decrypted data; otherwise null */ - publicKeyDecrypt: function(algo, keyIntegers, dataIntegers) { + publicKeyDecrypt: async function(algo, keyIntegers, dataIntegers, fingerprint) { var p; - - var bn = (function() { + return new type_mpi(await (async function() { switch (algo) { case 'rsa_encrypt_sign': case 'rsa_encrypt': @@ -111,21 +130,27 @@ export default { var c2 = dataIntegers[1].toBigInteger(); p = keyIntegers[0].toBigInteger(); return elgamal.decrypt(c1, c2, p, x); + + case 'ecdh': + var ecdh = publicKey.elliptic.ecdh; + var curve = keyIntegers[0]; + var kdf_params = keyIntegers[2]; + var V = dataIntegers[0].toBigInteger(); + var C = dataIntegers[1].data; + var r = keyIntegers[3].toBigInteger(); + return ecdh.decrypt(curve.oid, kdf_params.cipher, kdf_params.hash, V, C, r, fingerprint); + default: return null; } - })(); - - var result = new type_mpi(); - result.fromBigInteger(bn); - return result; + }())); }, - /** Returns the number of integers comprising the private key of an algorithm + /** Returns the types comprising the private key of an algorithm * @param {String} algo The public key algorithm - * @return {Integer} The number of integers. + * @return {Array} The array of types */ - getPrivateMpiCount: function(algo) { + getPrivKeyParamTypes: function(algo) { switch (algo) { case 'rsa_encrypt': case 'rsa_encrypt_sign': @@ -135,22 +160,31 @@ export default { // - 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. - return 4; + return [type_mpi, type_mpi, type_mpi, type_mpi]; case 'elgamal': // Algorithm-Specific Fields for Elgamal secret keys: // - MPI of Elgamal secret exponent x. - return 1; + return [type_mpi]; case 'dsa': // Algorithm-Specific Fields for DSA secret keys: // - MPI of DSA secret exponent x. - return 1; + return [type_mpi]; + case 'ecdh': + case 'ecdsa': + case 'eddsa': + // Algorithm-Specific Fields for ECDSA or ECDH secret keys: + // - MPI of an integer representing the secret key. + return [type_mpi]; default: throw new Error('Unknown algorithm'); } }, - getPublicMpiCount: function(algo) { - // - A series of multiprecision integers comprising the key material: + /** Returns the types comprising the public key of an algorithm + * @param {String} algo The public key algorithm + * @return {Array} The array of types + */ + getPubKeyParamTypes: function(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. @@ -158,29 +192,72 @@ export default { case 'rsa_encrypt': case 'rsa_encrypt_sign': case 'rsa_sign': - return 2; - + 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 'elgamal': - return 3; - + 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 'dsa': - return 4; + 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 'ecdsa': + case '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 'ecdh': + return [type_oid, type_mpi, type_kdf_params]; + default: + throw new Error('Unknown algorithm.'); + } + }, + + /** Returns the types comprising the encrypted session key of an algorithm + * @param {String} algo The public key algorithm + * @return {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 'rsa_encrypt': + case '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 '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 'ecdh': + return [type_mpi, type_ecdh_symkey]; default: throw new Error('Unknown algorithm.'); } }, - generateMpi: function(algo, bits) { + /** Generate algorithm-specific key parameters + * @param {String} algo The public key algorithm + * @return {Array} The array of parameters + */ + generateParams: function(algo, bits, curve) { + var types = this.getPubKeyParamTypes(algo).concat(this.getPrivKeyParamTypes(algo)); switch (algo) { case 'rsa_encrypt': case 'rsa_encrypt_sign': @@ -188,29 +265,25 @@ export default { //remember "publicKey" refers to the crypto/public_key dir var rsa = new publicKey.rsa(); return rsa.generate(bits, "10001").then(function(keyObject) { - var output = []; - output.push(keyObject.n); - output.push(keyObject.ee); - output.push(keyObject.d); - output.push(keyObject.p); - output.push(keyObject.q); - output.push(keyObject.u); - return mapResult(output); + return constructParams(types, [keyObject.n, keyObject.ee, keyObject.d, keyObject.p, keyObject.q, keyObject.u]); }); + + case 'ecdsa': + case 'eddsa': + return publicKey.elliptic.generate(curve).then(function (keyObject) { + return constructParams(types, [keyObject.oid, keyObject.Q, keyObject.d]); + }); + + case 'ecdh': + return publicKey.elliptic.generate(curve).then(function (keyObject) { + return constructParams(types, [keyObject.oid, keyObject.Q, [keyObject.hash, keyObject.cipher], keyObject.d]); + }); + default: throw new Error('Unsupported algorithm for key generation.'); } - - function mapResult(result) { - return result.map(function(bn) { - var mpi = new type_mpi(); - mpi.fromBigInteger(bn); - return mpi; - }); - } }, - /** * generate random byte prefix as string for the specified algorithm * @param {module:enums.symmetric} algo Algorithm to use (see {@link http://tools.ietf.org/html/rfc4880#section-9.2|RFC 4880 9.2}) @@ -228,5 +301,7 @@ export default { */ generateSessionKey: function(algo) { return random.getRandomBytes(cipher[algo].keySize); - } + }, + + constructParams: constructParams }; diff --git a/src/crypto/gcm.js b/src/crypto/gcm.js index b06b7318..39786cc8 100644 --- a/src/crypto/gcm.js +++ b/src/crypto/gcm.js @@ -22,14 +22,15 @@ 'use strict'; +import asmCrypto from 'asmcrypto-lite'; import util from '../util.js'; import config from '../config'; -import asmCrypto from 'asmcrypto-lite'; + const webCrypto = util.getWebCrypto(); // no GCM support in IE11, Safari 9 const nodeCrypto = util.getNodeCrypto(); const Buffer = util.getNodeBuffer(); -export const ivLength = 12; // size of the IV in bytes +const ivLength = 12; // size of the IV in bytes const TAG_LEN = 16; // size of the tag in bytes const ALGO = 'AES-GCM'; @@ -41,7 +42,7 @@ const ALGO = 'AES-GCM'; * @param {Uint8Array} iv The initialization vector (12 bytes) * @return {Promise} The ciphertext output */ -export function encrypt(cipher, plaintext, key, iv) { +function encrypt(cipher, plaintext, key, iv) { if (cipher.substr(0,3) !== 'aes') { return Promise.reject(new Error('GCM mode supports only AES cipher')); } @@ -49,7 +50,7 @@ export function encrypt(cipher, plaintext, key, iv) { if (webCrypto && config.use_native && key.length !== 24) { // WebCrypto (no 192 bit support) see: https://www.chromium.org/blink/webcrypto#TOC-AES-support return webEncrypt(plaintext, key, iv); } else if (nodeCrypto && config.use_native) { // Node crypto library - return nodeEncrypt(plaintext, key, iv) ; + return nodeEncrypt(plaintext, key, iv); } else { // asm.js fallback return Promise.resolve(asmCrypto.AES_GCM.encrypt(plaintext, key, iv)); } @@ -63,7 +64,7 @@ export function encrypt(cipher, plaintext, key, iv) { * @param {Uint8Array} iv The initialization vector (12 bytes) * @return {Promise} The plaintext output */ -export function decrypt(cipher, ciphertext, key, iv) { +function decrypt(cipher, ciphertext, key, iv) { if (cipher.substr(0,3) !== 'aes') { return Promise.reject(new Error('GCM mode supports only AES cipher')); } @@ -77,6 +78,12 @@ export function decrypt(cipher, ciphertext, key, iv) { } } +export default { + ivLength, + encrypt, + decrypt +}; + ////////////////////////// // // @@ -114,4 +121,4 @@ function nodeDecrypt(ct, key, iv) { de.setAuthTag(ct.slice(ct.length - TAG_LEN, ct.length)); // read auth tag at end of ciphertext const pt = Buffer.concat([de.update(ct.slice(0, ct.length - TAG_LEN)), de.final()]); return Promise.resolve(new Uint8Array(pt)); -} \ No newline at end of file +} diff --git a/src/crypto/hash/index.js b/src/crypto/hash/index.js index 27be2d77..98a22b61 100644 --- a/src/crypto/hash/index.js +++ b/src/crypto/hash/index.js @@ -8,9 +8,9 @@ 'use strict'; -import sha from './sha.js'; -import asmCrypto from 'asmcrypto-lite'; import Rusha from 'rusha'; +import asmCrypto from 'asmcrypto-lite'; +import sha from './sha.js'; import md5 from './md5.js'; import ripemd from './ripe-md.js'; import util from '../../util.js'; @@ -55,6 +55,7 @@ if(nodeCrypto) { // Use Node native crypto for all hash functions sha256: asmCrypto.SHA256.bytes, /** @see module:crypto/hash/sha.sha384 */ sha384: sha.sha384, + // TODO: compare sha512 in asmcrypto.js and jsSHA /** @see module:crypto/hash/sha.sha512 */ sha512: sha.sha512, /** @see module:crypto/hash/ripe-md */ diff --git a/src/crypto/hash/ripe-md.js b/src/crypto/hash/ripe-md.js index 48d74d2e..cae10d56 100644 --- a/src/crypto/hash/ripe-md.js +++ b/src/crypto/hash/ripe-md.js @@ -92,7 +92,6 @@ function mixOneRound(a, b, c, d, e, x, s, roundNumber) { default: throw new Error("Bogus round number"); - break; } a = ROL(a, s) + e; diff --git a/src/crypto/hash/sha.js b/src/crypto/hash/sha.js index e13780ae..6e3c89e0 100644 --- a/src/crypto/hash/sha.js +++ b/src/crypto/hash/sha.js @@ -1320,7 +1320,7 @@ var jsSHA = function(srcString, inputFormat, encoding) } /* Validate the numRounds argument */ - if ((numRounds !== parseInt(numRounds, 10)) || (1 > numRounds)) + if ((numRounds !== parseInt(numRounds)) || (1 > numRounds)) { throw "numRounds must a integer >= 1"; } @@ -1594,7 +1594,6 @@ export default { sha384: function(str) { var shaObj = new jsSHA(str, "TYPED", "UTF8"); return shaObj.getHash("SHA-384", "TYPED"); - }, /** SHA512 hash */ sha512: function(str) { diff --git a/src/crypto/index.js b/src/crypto/index.js index e337b4cb..c1530db2 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -8,12 +8,14 @@ import cipher from './cipher'; import hash from './hash'; import cfb from './cfb'; -import * as gcm from './gcm'; +import gcm from './gcm'; import publicKey from './public_key'; import signature from './signature'; import random from './random'; import pkcs1 from './pkcs1'; +import pkcs5 from './pkcs5.js'; import crypto from './crypto.js'; +import aes_kw from './aes_kw.js'; const mod = { /** @see module:crypto/cipher */ @@ -32,6 +34,10 @@ const mod = { random: random, /** @see module:crypto/pkcs1 */ pkcs1: pkcs1, + /** @see module:crypto/pkcs5 */ + pkcs5: pkcs5, + /** @see module:crypto/aes_kw */ + aes_kw: aes_kw }; for (var i in crypto) { diff --git a/src/crypto/pkcs1.js b/src/crypto/pkcs1.js index 17632a55..8f033cf2 100644 --- a/src/crypto/pkcs1.js +++ b/src/crypto/pkcs1.js @@ -37,22 +37,17 @@ import hash from './hash'; */ var hash_headers = []; hash_headers[1] = [0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05, 0x00, 0x04, - 0x10 -]; + 0x10]; hash_headers[2] = [0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14]; hash_headers[3] = [0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x24, 0x03, 0x02, 0x01, 0x05, 0x00, 0x04, 0x14]; hash_headers[8] = [0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, - 0x04, 0x20 -]; + 0x04, 0x20]; hash_headers[9] = [0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, - 0x04, 0x30 -]; + 0x04, 0x30]; hash_headers[10] = [0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, - 0x00, 0x04, 0x40 -]; + 0x00, 0x04, 0x40]; hash_headers[11] = [0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, - 0x00, 0x04, 0x1C -]; + 0x00, 0x04, 0x1C]; /** * Create padding with secure random data diff --git a/src/crypto/pkcs5.js b/src/crypto/pkcs5.js new file mode 100644 index 00000000..f7e166a4 --- /dev/null +++ b/src/crypto/pkcs5.js @@ -0,0 +1,54 @@ +// OpenPGP.js - An OpenPGP implementation in javascript +// Copyright (C) 2015-2016 Decentral +// +// 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 + +// Functions to add and remove PKCS5 padding + +/** + * Add pkcs5 padding to a text. + * @param {String} msg Text to add padding + * @return {String} Text with padding added + */ +function encode(msg) { + const c = 8 - (msg.length % 8); + const padding = String.fromCharCode(c).repeat(c); + return msg + padding; +} + +/** + * Remove pkcs5 padding from a string. + * @param {String} msg Text to remove padding from + * @return {String} Text with padding removed + */ +function decode(msg) { + const len = msg.length; + if (len > 0) { + const c = msg.charCodeAt(len - 1); + if (c >= 1 && c <= 8) { + 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'); +} + +module.exports = { + encode: encode, + decode: decode +}; diff --git a/src/crypto/public_key/elliptic/curves.js b/src/crypto/public_key/elliptic/curves.js new file mode 100644 index 00000000..9b7cf974 --- /dev/null +++ b/src/crypto/public_key/elliptic/curves.js @@ -0,0 +1,251 @@ +// OpenPGP.js - An OpenPGP implementation in javascript +// Copyright (C) 2015-2016 Decentral +// +// 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 + +// Wrapper of an instance of an Elliptic Curve + +/** + * @requires crypto/public_key/elliptic/key + * @requires crypto/public_key/jsbn + * @requires enums + * @requires util + * @module crypto/public_key/elliptic/curve + */ + +'use strict'; + +import { ec as EC, eddsa as EdDSA } from 'elliptic'; +import { KeyPair } from './key'; +import BigInteger from '../jsbn'; +import random from '../../random'; +import config from '../../../config'; +import enums from '../../../enums'; +import util from '../../../util'; +import OID from '../../../type/oid'; +import base64 from '../../../encoding/base64'; + +const webCrypto = util.getWebCrypto(); +const nodeCrypto = util.getNodeCrypto(); + +var webCurves = {}, nodeCurves = {}; +webCurves = { + 'p256': 'P-256', + 'p384': 'P-384', + 'p521': 'P-521' +}; +if (nodeCrypto && config.use_native) { + var knownCurves = nodeCrypto.getCurves(); + nodeCurves = { + 'secp256k1': knownCurves.includes('secp256k1') ? 'secp256k1' : undefined, + 'p256': knownCurves.includes('prime256v1') ? 'prime256v1' : undefined, + 'p384': knownCurves.includes('secp384r1') ? 'secp384r1' : undefined, + 'p521': knownCurves.includes('secp521r1') ? 'secp521r1' : undefined + // TODO add more here + }; +} + +const curves = { + p256: { + oid: util.bin2str([0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07]), + keyType: enums.publicKey.ecdsa, + hash: enums.hash.sha256, + cipher: enums.symmetric.aes128, + node: nodeCurves.p256, + web: webCurves.p256, + payloadSize: 32 + }, + p384: { + oid: util.bin2str([0x2B, 0x81, 0x04, 0x00, 0x22]), + keyType: enums.publicKey.ecdsa, + hash: enums.hash.sha384, + cipher: enums.symmetric.aes192, + node: nodeCurves.p384, + web: webCurves.p384, + payloadSize: 48 + }, + p521: { + oid: util.bin2str([0x2B, 0x81, 0x04, 0x00, 0x23]), + keyType: enums.publicKey.ecdsa, + hash: enums.hash.sha512, + cipher: enums.symmetric.aes256, + node: nodeCurves.p521, + web: webCurves.p521, + payloadSize: 66 + }, + secp256k1: { + oid: util.bin2str([0x2B, 0x81, 0x04, 0x00, 0x0A]), + keyType: enums.publicKey.ecdsa, + hash: enums.hash.sha256, + cipher: enums.symmetric.aes128, + node: false // FIXME when we replace jwk-to-pem or it supports this curve + }, + ed25519: { + oid: util.bin2str([0x2B, 0x06, 0x01, 0x04, 0x01, 0xDA, 0x47, 0x0F, 0x01]), + keyType: enums.publicKey.eddsa, + hash: enums.hash.sha512, + payloadSize: 32 + }, + curve25519: { + oid: util.bin2str([0x2B, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01]), + keyType: enums.publicKey.ecdsa, + hash: enums.hash.sha256, + cipher: enums.symmetric.aes128 + }, + brainpoolP256r1: { // TODO 1.3.36.3.3.2.8.1.1.7 + oid: util.bin2str([0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07]) + }, + brainpoolP384r1: { // TODO 1.3.36.3.3.2.8.1.1.11 + oid: util.bin2str([0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B]) + }, + brainpoolP512r1: { // TODO 1.3.36.3.3.2.8.1.1.13 + oid: util.bin2str([0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D]) + } +}; + +function Curve(name, params) { + this.keyType = params.keyType; + switch (this.keyType) { + case enums.publicKey.eddsa: + this.curve = new EdDSA(name); + break; + case enums.publicKey.ecdsa: + this.curve = new EC(name); + break; + default: + throw new Error('Unknown elliptic key type;'); + } + this.name = name; + this.oid = curves[name].oid; + this.hash = params.hash; + this.cipher = params.cipher; + this.node = params.node && curves[name].node; + this.web = params.web && curves[name].web; + this.payloadSize = curves[name].payloadSize; +} + +Curve.prototype.keyFromPrivate = function (priv) { // Not for ed25519 + return new KeyPair(this.curve, { priv: priv }); +}; + +Curve.prototype.keyFromSecret = function (secret) { // Only for ed25519 + return new KeyPair(this.curve, { secret: secret }); +}; + +Curve.prototype.keyFromPublic = function (pub) { + return new KeyPair(this.curve, { pub: pub }); +}; + +Curve.prototype.genKeyPair = async function () { + var keyPair; + if (webCrypto && config.use_native && this.web) { + // If browser doesn't support a curve, we'll catch it + try { + keyPair = await webGenKeyPair(this.name); + return new KeyPair(this.curve, keyPair); + } catch (err) { + util.print_debug("Browser did not support signing: " + err.message); + } + } else if (nodeCrypto && config.use_native && this.node) { + keyPair = await nodeGenKeyPair(this.name); + return new KeyPair(this.curve, keyPair); + } + var compact = this.curve.curve.type === 'edwards' || this.curve.curve.type === 'mont'; + var r = await this.curve.genKeyPair(); + if (this.keyType === enums.publicKey.eddsa) { + keyPair = { secret: r.getSecret() }; + } else { + keyPair = { pub: r.getPublic('array', compact), priv: r.getPrivate().toArray() }; + } + return new KeyPair(this.curve, keyPair); +}; + +function get(oid_or_name) { + var name; + if (OID.prototype.isPrototypeOf(oid_or_name) && + enums.curve[oid_or_name.toHex()]) { + name = enums.write(enums.curve, oid_or_name.toHex()); // by curve OID + return new Curve(name, curves[name]); + } else if (enums.curve[oid_or_name]) { + name = enums.write(enums.curve, oid_or_name); // by curve name + return new Curve(name, curves[name]); + } else if (enums.curve[util.hexstrdump(oid_or_name)]) { + name = enums.write(enums.curve, util.hexstrdump(oid_or_name)); // by oid string + return new Curve(name, curves[name]); + } + throw new Error('Not valid curve'); +} + +async function generate(curve) { + curve = get(curve); + var keyPair = await curve.genKeyPair(); + return { + oid: curve.oid, + Q: new BigInteger(keyPair.getPublic()), + d: new BigInteger(keyPair.getPrivate()), + hash: curve.hash, + cipher: curve.cipher + }; +} + +function getPreferredHashAlgo(oid) { + return curves[enums.write(enums.curve, oid.toHex())].hash; +} + +module.exports = { + Curve: Curve, + curves: curves, + webCurves: webCurves, + nodeCurves: nodeCurves, + getPreferredHashAlgo: getPreferredHashAlgo, + generate: generate, + get: get +}; + + +////////////////////////// +// // +// Helper functions // +// // +////////////////////////// + + +async function webGenKeyPair(name) { + // Note: keys generated with ECDSA and ECDH are structurally equivalent + var webCryptoKey = await webCrypto.generateKey( + { name: "ECDSA", namedCurve: webCurves[name] }, true, ["sign", "verify"] + ); + + var privateKey = await webCrypto.exportKey("jwk", webCryptoKey.privateKey); + var publicKey = await webCrypto.exportKey("jwk", webCryptoKey.publicKey); + + return { + pub: { + x: base64.decode(publicKey.x, true), + y: base64.decode(publicKey.y, true) + }, + priv: base64.decode(privateKey.d, true) + }; +} + +async function nodeGenKeyPair(name) { + var ecdh = nodeCrypto.createECDH(nodeCurves[name]); + await ecdh.generateKeys(); + + return { + pub: ecdh.getPublicKey().toJSON().data, + priv: ecdh.getPrivateKey().toJSON().data + }; +} diff --git a/src/crypto/public_key/elliptic/ecdh.js b/src/crypto/public_key/elliptic/ecdh.js new file mode 100644 index 00000000..ff5d3f4e --- /dev/null +++ b/src/crypto/public_key/elliptic/ecdh.js @@ -0,0 +1,125 @@ +// OpenPGP.js - An OpenPGP implementation in javascript +// Copyright (C) 2015-2016 Decentral +// +// 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 + +// Key encryption and decryption for RFC 6637 ECDH + +/** + * @requires crypto/public_key/elliptic/curves + * @requires crypto/public_key/jsbn + * @requires crypto/cipher + * @requires crypto/hash + * @requires crypto/aes_kw + * @requires type/oid + * @requires type/kdf_params + * @requires enums + * @requires util + * @module crypto/public_key/elliptic/ecdh + */ + +'use strict'; + +import curves from './curves'; +import BigInteger from '../jsbn'; +import cipher from '../../cipher'; +import hash from '../../hash'; +import aes_kw from '../../aes_kw'; +import type_kdf_params from '../../../type/kdf_params'; +import type_oid from '../../../type/oid'; +import enums from '../../../enums'; +import util from '../../../util'; + + +// Build Param for ECDH algorithm (RFC 6637) +function buildEcdhParam(public_algo, oid, cipher_algo, hash_algo, fingerprint) { + oid = new type_oid(oid); + const kdf_params = new type_kdf_params([hash_algo, cipher_algo]); + return util.concatUint8Array([ + oid.write(), + new Uint8Array([public_algo]), + kdf_params.write(), + util.str2Uint8Array("Anonymous Sender "), + fingerprint + ]); +} + +// Key Derivation Function (RFC 6637) +function kdf(hash_algo, X, length, param) { + return hash.digest(hash_algo, util.concatUint8Array([ + new Uint8Array([0, 0, 0, 1]), + new Uint8Array(X), + param + ])).subarray(0, length); +} + + +/** + * Encrypt and wrap a session key + * + * @param {String} oid OID of the curve to use + * @param {Enums} cipher_algo Symmetric cipher to use + * @param {Enums} hash_algo Hash to use + * @param {Uint8Array} m Value derived from session key (RFC 6637) + * @param {BigInteger} Q Recipient public key + * @param {String} fingerprint Recipient fingerprint + * @return {{V: BigInteger, C: Uint8Array}} Returns ephemeral key and encoded session key + */ +async function encrypt(oid, cipher_algo, hash_algo, m, Q, fingerprint) { + fingerprint = util.hex2Uint8Array(fingerprint); + const param = buildEcdhParam(enums.publicKey.ecdh, oid, cipher_algo, hash_algo, fingerprint); + const curve = curves.get(oid); + cipher_algo = enums.read(enums.symmetric, cipher_algo); + const v = await curve.genKeyPair(); + Q = curve.keyFromPublic(Q.toByteArray()); + const S = v.derive(Q); + const Z = kdf(hash_algo, S, cipher[cipher_algo].keySize, param); + const C = aes_kw.wrap(Z, m.toBytes()); + return { + V: new BigInteger(v.getPublic()), + C: C + }; +} + +/** + * Decrypt and unwrap the value derived from session key + * + * @param {String} oid Curve OID + * @param {Enums} cipher_algo Symmetric cipher to use + * @param {Enums} hash_algo Hash algorithm to use + * @param {BigInteger} V Public part of ephemeral key + * @param {Uint8Array} C Encrypted and wrapped value derived from session key + * @param {BigInteger} d Recipient private key + * @param {String} fingerprint Recipient fingerprint + * @return {Uint8Array} Value derived from session + */ +async function decrypt(oid, cipher_algo, hash_algo, V, C, d, fingerprint) { + fingerprint = util.hex2Uint8Array(fingerprint); + const param = buildEcdhParam(enums.publicKey.ecdh, oid, cipher_algo, hash_algo, fingerprint); + const curve = curves.get(oid); + cipher_algo = enums.read(enums.symmetric, cipher_algo); + V = curve.keyFromPublic(V.toByteArray()); + d = curve.keyFromPrivate(d.toByteArray()); + const S = d.derive(V); + const Z = kdf(hash_algo, S, cipher[cipher_algo].keySize, param); + return new BigInteger(aes_kw.unwrap(Z, C)); +} + +module.exports = { + buildEcdhParam: buildEcdhParam, + kdf: kdf, + encrypt: encrypt, + decrypt: decrypt +}; diff --git a/src/crypto/public_key/elliptic/ecdsa.js b/src/crypto/public_key/elliptic/ecdsa.js new file mode 100644 index 00000000..0e2dd6e3 --- /dev/null +++ b/src/crypto/public_key/elliptic/ecdsa.js @@ -0,0 +1,71 @@ +// OpenPGP.js - An OpenPGP implementation in javascript +// Copyright (C) 2015-2016 Decentral +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 3.0 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +// Implementation of ECDSA following RFC6637 for Openpgpjs + +/** + * @requires crypto/hash + * @requires crypto/public_key/jsbn + * @requires crypto/public_key/elliptic/curves + * @module crypto/public_key/elliptic/ecdsa + */ + +'use strict'; + +import hash from '../../hash'; +import curves from './curves'; +import BigInteger from '../jsbn'; + +/** + * Sign a message using the provided key + * @param {String} oid Elliptic curve for the key + * @param {enums.hash} hash_algo Hash algorithm used to sign + * @param {Uint8Array} m Message to sign + * @param {BigInteger} d Private key used to sign + * @return {{r: BigInteger, s: BigInteger}} Signature of the message + */ +async function sign(oid, hash_algo, m, d) { + const curve = curves.get(oid); + const key = curve.keyFromPrivate(d.toByteArray()); + const signature = await key.sign(m, hash_algo); + return { + r: new BigInteger(signature.r.toArray()), + s: new BigInteger(signature.s.toArray()) + }; +} + +/** + * Verifies if a signature is valid for a message + * @param {String} oid Elliptic curve for the key + * @param {enums.hash} hash_algo Hash algorithm used in the signature + * @param {{r: BigInteger, s: BigInteger}} signature Signature to verify + * @param {Uint8Array} m Message to verify + * @param {BigInteger} Q Public key used to verify the message + * @return {Boolean} + */ +async function verify(oid, hash_algo, signature, m, Q) { + const curve = curves.get(oid); + const key = curve.keyFromPublic(Q.toByteArray()); + return key.verify( + m, { r: signature.r.toByteArray(), s: signature.s.toByteArray() }, hash_algo + ); +} + +module.exports = { + sign: sign, + verify: verify +}; diff --git a/src/crypto/public_key/elliptic/eddsa.js b/src/crypto/public_key/elliptic/eddsa.js new file mode 100644 index 00000000..4cf1e85b --- /dev/null +++ b/src/crypto/public_key/elliptic/eddsa.js @@ -0,0 +1,76 @@ +// OpenPGP.js - An OpenPGP implementation in javascript +// Copyright (C) 2018 Proton Technologies AG +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 3.0 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +// Implementation of EdDSA following RFC4880bis-03 for OpenPGP + +/** + * @requires bn.js + * @requires crypto/hash + * @requires crypto/public_key/elliptic/curves + * @module crypto/public_key/elliptic/eddsa + */ + +'use strict'; + +import BN from 'bn.js'; +import hash from '../../hash'; +import curves from './curves'; + +/** + * Sign a message using the provided key + * @param {String} oid Elliptic curve for the key + * @param {enums.hash} hash_algo Hash algorithm used to sign + * @param {Uint8Array} m Message to sign + * @param {BigInteger} d Private key used to sign + * @return {{R: BN, S: BN}} Signature of the message + */ +async function sign(oid, hash_algo, m, d) { + const curve = curves.get(oid); + const key = curve.keyFromSecret(d.toByteArray()); + const signature = await key.sign(m, hash_algo); + // EdDSA signature params are returned in little-endian format + return { + R: new BN(Array.from(signature.Rencoded()).reverse()), + S: new BN(Array.from(signature.Sencoded()).reverse()) + }; +} + +/** + * Verifies if a signature is valid for a message + * @param {String} oid Elliptic curve for the key + * @param {enums.hash} hash_algo Hash algorithm used in the signature + * @param {{R: BigInteger, S: BigInteger}} signature Signature to verify + * @param {Uint8Array} m Message to verify + * @param {BigInteger} Q Public key used to verify the message + * @return {Boolean} + */ +async function verify(oid, hash_algo, signature, m, Q) { + const curve = curves.get(oid); + const key = curve.keyFromPublic(Q.toByteArray()); + // EdDSA signature params are expected in little-endian format + const R = Array.from(signature.R.toByteArray()).reverse(), + S = Array.from(signature.S.toByteArray()).reverse(); + return key.verify( + m, { R: [].concat(R, Array(curve.payloadSize - R.length).fill(0)), + S: [].concat(S, Array(curve.payloadSize - S.length).fill(0)) }, hash_algo + ); +} + +module.exports = { + sign: sign, + verify: verify +}; diff --git a/src/crypto/public_key/elliptic/index.js b/src/crypto/public_key/elliptic/index.js new file mode 100644 index 00000000..39108f0a --- /dev/null +++ b/src/crypto/public_key/elliptic/index.js @@ -0,0 +1,42 @@ +// OpenPGP.js - An OpenPGP implementation in javascript +// Copyright (C) 2015-2016 Decentral +// +// 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 + +// Function to access Elliptic Curve Cryptography + +/** + * @requires crypto/public_key/elliptic/curve + * @requires crypto/public_key/elliptic/ecdh + * @requires crypto/public_key/elliptic/ecdsa + * @requires crypto/public_key/elliptic/eddsa + * @module crypto/public_key/elliptic + */ + +'use strict'; + +import {get, generate, getPreferredHashAlgo} from './curves'; +import ecdsa from './ecdsa'; +import eddsa from './eddsa'; +import ecdh from './ecdh'; + +module.exports = { + ecdsa: ecdsa, + eddsa: eddsa, + ecdh: ecdh, + get: get, + generate: generate, + getPreferredHashAlgo: getPreferredHashAlgo +}; diff --git a/src/crypto/public_key/elliptic/key.js b/src/crypto/public_key/elliptic/key.js new file mode 100644 index 00000000..cc9d7404 --- /dev/null +++ b/src/crypto/public_key/elliptic/key.js @@ -0,0 +1,248 @@ +// OpenPGP.js - An OpenPGP implementation in javascript +// Copyright (C) 2015-2016 Decentral +// +// 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 + +// Wrapper for a KeyPair of an Elliptic Curve + +/** + * @requires crypto/public_key/elliptic/curves + * @requires crypto/public_key/jsbn + * @requires crypto/hash + * @requires util + * @requires enums + * @requires config + * @requires encoding/base64 + * @requires jwk-to-pem + * @requires asn1.js + * @module crypto/public_key/elliptic/key + */ + +'use strict'; + +import curves from './curves'; +import BigInteger from '../jsbn'; +import hash from '../../hash'; +import util from '../../../util'; +import enums from '../../../enums'; +import config from '../../../config'; +import base64 from '../../../encoding/base64'; + +const webCrypto = util.getWebCrypto(); +const webCurves = curves.webCurves; +const nodeCrypto = util.getNodeCrypto(); +const nodeCurves = curves.nodeCurves; + +const jwkToPem = nodeCrypto ? require('jwk-to-pem') : undefined; +const ECDSASignature = nodeCrypto ? + require('asn1.js').define('ECDSASignature', function() { + this.seq().obj( + this.key('r').int(), + this.key('s').int() + ); + }) : undefined; + +function KeyPair(curve, options) { + this.curve = curve; + this.keyType = curve.curve.type === 'edwards' ? enums.publicKey.eddsa : enums.publicKey.ecdsa; + this.keyPair = this.curve.keyPair(options); +} + +KeyPair.prototype.sign = async function (message, hash_algo) { + if (util.isString(message)) { + message = util.str2Uint8Array(message); + } + if (webCrypto && config.use_native && this.curve.web) { + // If browser doesn't support a curve, we'll catch it + try { + return webSign(this.curve, hash_algo, message, this.keyPair); + } catch (err) { + util.print_debug("Browser did not support signing: " + err.message); + } + } else if (nodeCrypto && config.use_native && this.curve.node) { + return nodeSign(this.curve, hash_algo, message, this.keyPair); + } + const digest = (typeof hash_algo === 'undefined') ? message : hash.digest(hash_algo, message); + return this.keyPair.sign(digest); +}; + +KeyPair.prototype.verify = async function (message, signature, hash_algo) { + if (util.isString(message)) { + message = util.str2Uint8Array(message); + } + if (webCrypto && config.use_native && this.curve.web) { + // If browser doesn't support a curve, we'll catch it + try { + return webVerify(this.curve, hash_algo, signature, message, this.keyPair.getPublic()); + } catch (err) { + util.print_debug("Browser did not support signing: " + err.message); + } + } else if (nodeCrypto && config.use_native && this.curve.node) { + return nodeVerify(this.curve, hash_algo, signature, message, this.keyPair.getPublic()); + } + const digest = (typeof hash_algo === 'undefined') ? message : hash.digest(hash_algo, message); + return this.keyPair.verify(digest, signature); +}; + +KeyPair.prototype.derive = function (pub) { + if (this.keyType === enums.publicKey.eddsa) { + throw new Error('Key can only be used for EdDSA'); + } + return this.keyPair.derive(pub.keyPair.getPublic()); +}; + +KeyPair.prototype.getPublic = function () { + var compact = (this.curve.curve.type === 'edwards' || this.curve.curve.type === 'mont'); + return this.keyPair.getPublic('array', compact); +}; + +KeyPair.prototype.getPrivate = function () { + if (this.keyType === enums.publicKey.eddsa) { + return this.keyPair.getSecret(); + } else { + return this.keyPair.getPrivate().toArray(); + } +}; + +module.exports = { + KeyPair: KeyPair +}; + + +////////////////////////// +// // +// Helper functions // +// // +////////////////////////// + + +async function webSign(curve, hash_algo, message, keyPair) { + var l = curve.payloadSize; + const key = await webCrypto.importKey( + "jwk", + { + "kty": "EC", + "crv": webCurves[curve.name], + "x": base64.encode(new Uint8Array(keyPair.getPublic().getX().toArray('be', l)), true), + "y": base64.encode(new Uint8Array(keyPair.getPublic().getY().toArray('be', l)), true), + "d": base64.encode(new Uint8Array(keyPair.getPrivate().toArray('be', l)), true), + "use": "sig", + "kid": "ECDSA Private Key" + }, + { + "name": "ECDSA", + "namedCurve": webCurves[curve.name], + "hash": { name: enums.read(enums.webHash, curve.hash) } + }, + false, + ["sign"] + ); + + const signature = new Uint8Array(await webCrypto.sign( + { + "name": 'ECDSA', + "namedCurve": webCurves[curve.name], + "hash": { name: enums.read(enums.webHash, hash_algo) } + }, + key, + message + )); + return { + r: signature.slice(0, l), + s: signature.slice(l, 2 * l) + }; +} + +async function webVerify(curve, hash_algo, {r, s}, message, publicKey) { + var l = curve.payloadSize; + r = Array(l - r.length).fill(0).concat(r); + s = Array(l - s.length).fill(0).concat(s); + var signature = new Uint8Array(r.concat(s)).buffer; + const key = await webCrypto.importKey( + "jwk", + { + "kty": "EC", + "crv": webCurves[curve.name], + "x": base64.encode(new Uint8Array(publicKey.getX().toArray('be', l)), true), + "y": base64.encode(new Uint8Array(publicKey.getY().toArray('be', l)), true), + "use": "sig", + "kid": "ECDSA Public Key" + }, + { + "name": "ECDSA", + "namedCurve": webCurves[curve.name], + "hash": { name: enums.read(enums.webHash, curve.hash) } + }, + false, + ["verify"] + ); + + return webCrypto.verify( + { + "name": 'ECDSA', + "namedCurve": webCurves[curve.name], + "hash": { name: enums.read(enums.webHash, hash_algo) } + }, + key, + signature, + message + ); +} + + +async function nodeSign(curve, hash_algo, message, keyPair) { + const key = jwkToPem( + { + "kty": "EC", + "crv": webCurves[curve.name], + "x": base64.encode(new Uint8Array(keyPair.getPublic().getX().toArray())), + "y": base64.encode(new Uint8Array(keyPair.getPublic().getY().toArray())), + "d": base64.encode(new Uint8Array(keyPair.getPrivate().toArray())), + "use": "sig", + "kid": "ECDSA Private Key" + }, + { private: true } + ); + + const sign = nodeCrypto.createSign(enums.read(enums.hash, hash_algo)); + sign.write(message); + sign.end(); + const signature = await ECDSASignature.decode(sign.sign(key), 'der'); + return { + r: signature.r.toArray(), + s: signature.s.toArray() + }; +} + +async function nodeVerify(curve, hash_algo, {r, s}, message, publicKey) { + var signature = ECDSASignature.encode({ r: new BigInteger(r), s: new BigInteger(s) }, 'der'); + const key = jwkToPem( + { + "kty": "EC", + "crv": webCurves[curve.name], + "x": base64.encode(new Uint8Array(publicKey.getX().toArray())), + "y": base64.encode(new Uint8Array(publicKey.getY().toArray())), + "use": "sig", + "kid": "ECDSA Public Key" + }, + { private: false } + ); + + const verify = nodeCrypto.createVerify(enums.read(enums.hash, hash_algo)); + verify.write(message); + verify.end(); + const result = await verify.verify(key, signature); + return result; +} diff --git a/src/crypto/public_key/index.js b/src/crypto/public_key/index.js index 8e5dfc7b..ed29c250 100644 --- a/src/crypto/public_key/index.js +++ b/src/crypto/public_key/index.js @@ -1,6 +1,7 @@ /** * @requires crypto/public_key/dsa * @requires crypto/public_key/elgamal + * @requires crypto/public_key/elliptic * @requires crypto/public_key/rsa * @module crypto/public_key */ @@ -11,11 +12,14 @@ import rsa from './rsa.js'; /** @see module:crypto/public_key/elgamal */ import elgamal from './elgamal.js'; +/** @see module:crypto/public_key/elliptic */ +import elliptic from './elliptic'; /** @see module:crypto/public_key/dsa */ import dsa from './dsa.js'; export default { rsa: rsa, elgamal: elgamal, + elliptic: elliptic, dsa: dsa }; diff --git a/src/crypto/public_key/jsbn.js b/src/crypto/public_key/jsbn.js index bc8b6a3f..bcfe7705 100644 --- a/src/crypto/public_key/jsbn.js +++ b/src/crypto/public_key/jsbn.js @@ -37,7 +37,7 @@ * @module crypto/public_key/jsbn */ -import util from '../../util.js'; +import util from '../../util'; // Basic JavaScript BN library - subset useful for RSA encryption. @@ -53,7 +53,7 @@ var j_lm = ((canary & 0xffffff) == 0xefcafe); export default function BigInteger(a, b, c) { if (a != null) if ("number" == typeof a) this.fromNumber(a, b, c); - else if (b == null && "string" != typeof a) this.fromString(a, 256); + else if (b == null && !util.isString(a)) this.fromString(a, 256); else this.fromString(a, b); } @@ -114,7 +114,7 @@ function am3(i, x, w, j, c, n) { return c; } /*if(j_lm && (navigator != undefined && - navigator.appName == "Microsoft Internet Explorer")) { + navigator.appName == "Microsoft Internet Explorer")) { BigInteger.prototype.am = am2; dbits = 30; } diff --git a/src/crypto/random.js b/src/crypto/random.js index 4e2331a4..dafa16b7 100644 --- a/src/crypto/random.js +++ b/src/crypto/random.js @@ -27,6 +27,7 @@ import type_mpi from '../type/mpi.js'; import util from '../util.js'; + const nodeCrypto = util.detectNode() && require('crypto'); export default { @@ -113,8 +114,7 @@ export default { randomBits.charCodeAt(0)) + randomBits.substring(1); } - var mpi = new type_mpi(); - mpi.fromBytes(randomBits); + var mpi = new type_mpi(randomBits); return mpi.toBigInteger(); }, diff --git a/src/crypto/signature.js b/src/crypto/signature.js index 6ab0ce9f..92083877 100644 --- a/src/crypto/signature.js +++ b/src/crypto/signature.js @@ -21,8 +21,12 @@ export default { * @param {Uint8Array} data Data on where the signature was computed on. * @return {Boolean} true if signature (sig_data was equal to data over hash) */ - verify: function(algo, hash_algo, msg_MPIs, publickey_MPIs, data) { + verify: async function(algo, hash_algo, msg_MPIs, publickey_MPIs, data) { var m; + var r; + var s; + var Q; + var curve; data = util.Uint8Array2str(data); @@ -56,6 +60,24 @@ export default { m = data; var dopublic = dsa.verify(hash_algo, s1, s2, m, p, q, g, y); return dopublic.compareTo(s1) === 0; + case 19: + // ECDSA + var ecdsa = publicKey.elliptic.ecdsa; + curve = publickey_MPIs[0]; + r = msg_MPIs[0].toBigInteger(); + s = msg_MPIs[1].toBigInteger(); + m = data; + Q = publickey_MPIs[1].toBigInteger(); + return ecdsa.verify(curve.oid, hash_algo, {r: r, s: s}, m, Q); + case 22: + // EdDSA + var eddsa = publicKey.elliptic.eddsa; + curve = publickey_MPIs[0]; + r = msg_MPIs[0].toBigInteger(); + s = msg_MPIs[1].toBigInteger(); + m = data; + Q = publickey_MPIs[1].toBigInteger(); + return eddsa.verify(curve.oid, hash_algo, { R: r, S: s }, m, Q); default: throw new Error('Invalid signature algorithm.'); } @@ -65,18 +87,18 @@ export default { * Create a signature on data using the specified algorithm * @param {module:enums.hash} hash_algo hash Algorithm to use (See {@link http://tools.ietf.org/html/rfc4880#section-9.4|RFC 4880 9.4}) * @param {module:enums.publicKey} algo Asymmetric cipher algorithm to use (See {@link http://tools.ietf.org/html/rfc4880#section-9.1|RFC 4880 9.1}) - * @param {Array} publicMPIs Public key multiprecision integers - * of the private key - * @param {Array} secretMPIs Private key multiprecision - * integers which is used to sign the data + * @param {Array} keyIntegers Public followed by Private key multiprecision algorithm-specific parameters * @param {Uint8Array} data Data to be signed * @return {Array} */ - sign: function(hash_algo, algo, keyIntegers, data) { + sign: async function(hash_algo, algo, keyIntegers, data) { data = util.Uint8Array2str(data); var m; + var d; + var curve; + var signature; switch (algo) { case 1: @@ -86,7 +108,7 @@ export default { case 3: // RSA Sign-Only [HAC] var rsa = new publicKey.rsa(); - var d = keyIntegers[2].toBigInteger(); + d = keyIntegers[2].toBigInteger(); var n = keyIntegers[0].toBigInteger(); m = pkcs1.emsa.encode(hash_algo, data, keyIntegers[0].byteLength()); @@ -102,11 +124,31 @@ export default { var x = keyIntegers[4].toBigInteger(); m = data; var result = dsa.sign(hash_algo, m, g, p, q, x); - return util.str2Uint8Array(result[0].toString() + result[1].toString()); case 16: // Elgamal (Encrypt-Only) [ELGAMAL] [HAC] throw new Error('Signing with Elgamal is not defined in the OpenPGP standard.'); + + case 19: + // ECDSA + var ecdsa = publicKey.elliptic.ecdsa; + curve = keyIntegers[0]; + d = keyIntegers[2].toBigInteger(); + m = data; + signature = await ecdsa.sign(curve.oid, hash_algo, m, d); + return util.str2Uint8Array(signature.r.toMPI() + signature.s.toMPI()); + case 22: + // EdDSA + var eddsa = publicKey.elliptic.eddsa; + curve = keyIntegers[0]; + d = keyIntegers[2].toBigInteger(); + m = data; + signature = await eddsa.sign(curve.oid, hash_algo, m, d); + return new Uint8Array([].concat( + util.Uint8Array2MPI(signature.R.toArrayLike(Uint8Array, 'le', 32)), + util.Uint8Array2MPI(signature.S.toArrayLike(Uint8Array, 'le', 32)) + )); + default: throw new Error('Invalid signature algorithm.'); } diff --git a/src/encoding/armor.js b/src/encoding/armor.js index 8db6ff36..4c854910 100644 --- a/src/encoding/armor.js +++ b/src/encoding/armor.js @@ -115,7 +115,6 @@ function addheader() { } - /** * Calculates a checksum over the given data and returns it base64 encoded * @param {String} data Data to create a CRC-24 checksum for diff --git a/src/encoding/base64.js b/src/encoding/base64.js index 298b1858..e4d22cfe 100644 --- a/src/encoding/base64.js +++ b/src/encoding/base64.js @@ -17,18 +17,21 @@ 'use strict'; -var b64s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; +var b64s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; // Standard radix-64 +var b64u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; // URL-safe radix-64 /** * Convert binary array to radix-64 * @param {Uint8Array} t Uint8Array to convert + * @param {bool} u if true, output is URL-safe * @returns {string} radix-64 version of input string * @static */ -function s2r(t, o) { +function s2r(t, u = false) { // TODO check btoa alternative + var b64 = u ? b64u : b64s; var a, c, n; - var r = o ? o : [], + var r = [], l = 0, s = 0; var tl = t.length; @@ -36,21 +39,21 @@ function s2r(t, o) { for (n = 0; n < tl; n++) { c = t[n]; if (s === 0) { - r.push(b64s.charAt((c >> 2) & 63)); + r.push(b64.charAt((c >> 2) & 63)); a = (c & 3) << 4; } else if (s === 1) { - r.push(b64s.charAt((a | (c >> 4) & 15))); + r.push(b64.charAt(a | ((c >> 4) & 15))); a = (c & 15) << 2; } else if (s === 2) { - r.push(b64s.charAt(a | ((c >> 6) & 3))); + r.push(b64.charAt(a | ((c >> 6) & 3))); l += 1; - if ((l % 60) === 0) { + if ((l % 60) === 0 && !u) { r.push("\n"); } - r.push(b64s.charAt(c & 63)); + r.push(b64.charAt(c & 63)); } l += 1; - if ((l % 60) === 0) { + if ((l % 60) === 0 && !u) { r.push("\n"); } @@ -60,23 +63,21 @@ function s2r(t, o) { } } if (s > 0) { - r.push(b64s.charAt(a)); + r.push(b64.charAt(a)); l += 1; - if ((l % 60) === 0) { + if ((l % 60) === 0 && !u) { + r.push("\n"); + } + if (!u) { + r.push('='); + l += 1; + } + } + if (s === 1 && !u) { + if ((l % 60) === 0 && !u) { r.push("\n"); } r.push('='); - l += 1; - } - if (s === 1) { - if ((l % 60) === 0) { - r.push("\n"); - } - r.push('='); - } - if (o) - { - return; } return r.join(''); } @@ -84,11 +85,13 @@ function s2r(t, o) { /** * Convert radix-64 to binary array * @param {String} t radix-64 string to convert + * @param {bool} u if true, input is interpreted as URL-safe * @returns {Uint8Array} binary array version of input string * @static */ -function r2s(t) { +function r2s(t, u) { // TODO check atob alternative + var b64 = u ? b64u : b64s; var c, n; var r = [], s = 0, @@ -96,10 +99,10 @@ function r2s(t) { var tl = t.length; for (n = 0; n < tl; n++) { - c = b64s.indexOf(t.charAt(n)); + c = b64.indexOf(t.charAt(n)); if (c >= 0) { if (s) { - r.push(a | (c >> (6 - s)) & 255); + r.push(a | ((c >> (6 - s)) & 255)); } s = (s + 2) & 7; a = (c << s) & 255; diff --git a/src/enums.js b/src/enums.js index 861f2f01..db73a09b 100644 --- a/src/enums.js +++ b/src/enums.js @@ -6,6 +6,58 @@ export default { + /** Maps curve names under various standards to one + * @enum {String} + * @readonly + */ + curve: { + /** NIST P-256 Curve */ + "p256": "p256", + "P-256": "p256", + "secp256r1": "p256", + "prime256v1": "p256", + "1.2.840.10045.3.1.7": "p256", + "2a8648ce3d030107": "p256", + "2A8648CE3D030107": "p256", + + /** NIST P-384 Curve */ + "p384": "p384", + "P-384": "p384", + "secp384r1": "p384", + "1.3.132.0.34": "p384", + "2b81040022": "p384", + "2B81040022": "p384", + + /** NIST P-521 Curve */ + "p521": "p521", + "P-521": "p521", + "secp521r1": "p521", + "1.3.132.0.35": "p521", + "2b81040023": "p521", + "2B81040023": "p521", + + /** SECP256k1 Curve */ + "secp256k1": "secp256k1", + "1.3.132.0.10": "secp256k1", + "2b8104000a": "secp256k1", + "2B8104000A": "secp256k1", + + /** Ed25519 Curve */ + "ed25519": "ed25519", + "Ed25519": "ed25519", + "1.3.6.1.4.1.11591.15.1": "ed25519", + "2b06010401da470f01": "ed25519", + "2B06010401DA470F01": "ed25519", + + /** Curve25519 */ + "cv25519": "curve25519", + "curve25519": "curve25519", + "Curve25519": "curve25519", + "1.3.6.1.4.1.3029.1.5.1": "curve25519", + "2b060104019755010501": "curve25519", + "2B060104019755010501": "curve25519" + }, + /** A string to key specifier type * @enum {Integer} * @readonly @@ -26,7 +78,10 @@ export default { rsa_encrypt: 2, rsa_sign: 3, elgamal: 16, - dsa: 17 + dsa: 17, + ecdh: 18, + ecdsa: 19, + eddsa: 22 }, /** {@link http://tools.ietf.org/html/rfc4880#section-9.2|RFC4880, section 9.2} @@ -73,6 +128,17 @@ export default { sha224: 11 }, + /** A list of hash names as accepted by webCrypto functions. + * {@link https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest|Parameters, algo} + * @enum {String} + */ + webHash: { + 'SHA-1': 2, + 'SHA-256': 8, + 'SHA-384': 9, + 'SHA-512': 10 + }, + /** A list of packet types and numeric tags associated with them. * @enum {Integer} * @readonly diff --git a/src/index.js b/src/index.js index fea4726e..1b242b87 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,5 @@ +/* eslint-disable import/newline-after-import, import/first */ + 'use strict'; /** @@ -17,7 +19,12 @@ export default openpgp; * import { encryptMessage } from 'openpgp.js' * encryptMessage(keys, text) */ -export * from './openpgp'; +export { + encrypt, decrypt, sign, verify, + generateKey, reformatKey, decryptKey, + encryptSessionKey, decryptSessionKeys, + initWorker, getWorker, destroyWorker +} from './openpgp'; /** * @see module:key @@ -77,6 +84,24 @@ export { default as S2K } from './type/s2k'; */ 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 module:encoding/armor * @name module:openpgp.armor @@ -117,4 +142,4 @@ export { default as AsyncProxy } from './worker/async_proxy'; * @see module:hkp * @name module:openpgp.HKP */ -export { default as HKP } from './hkp'; \ No newline at end of file +export { default as HKP } from './hkp'; diff --git a/src/key.js b/src/key.js index b4ad9052..40201560 100644 --- a/src/key.js +++ b/src/key.js @@ -17,19 +17,22 @@ /** * @requires config + * @requires crypto * @requires encoding/armor * @requires enums + * @requires util * @requires packet * @module key */ 'use strict'; -import packet from './packet'; -import enums from './enums.js'; -import armor from './encoding/armor.js'; import config from './config'; +import crypto from './crypto'; +import armor from './encoding/armor'; +import enums from './enums'; import util from './util'; +import packet from './packet'; /** * @class @@ -299,68 +302,81 @@ Key.prototype.armor = function() { */ Key.prototype.getSigningKeyPacket = function(keyId, allowExpired=false) { var primaryUser = this.getPrimaryUser(allowExpired); - if (primaryUser && - isValidSigningKeyPacket(this.primaryKey, primaryUser.selfCertificate) && - (!keyId || this.primaryKey.getKeyId().equals(keyId)) && - this.verifyPrimaryKey(allowExpired) === enums.keyStatus.valid) { + if (primaryUser && (!keyId || this.primaryKey.getKeyId().equals(keyId)) && + isValidSigningKeyPacket(this.primaryKey, primaryUser.selfCertificate, allowExpired)) { return this.primaryKey; } if (this.subKeys) { for (var i = 0; i < this.subKeys.length; i++) { - if (this.subKeys[i].isValidSigningKey(this.primaryKey, allowExpired) && - (!keyId || this.subKeys[i].subKey.getKeyId().equals(keyId))) { - return this.subKeys[i].subKey; + if (!keyId || this.subKeys[i].subKey.getKeyId().equals(keyId)) { + for (var j = 0; j < this.subKeys[i].bindingSignatures.length; j++) { + if (isValidSigningKeyPacket( + this.subKeys[i].subKey, this.subKeys[i].bindingSignatures[j], allowExpired)) { + return this.subKeys[i].subKey; + } + } } } } return null; }; -/** - * Returns preferred signature hash algorithm of this key - * @return {String} - */ -Key.prototype.getPreferredHashAlgorithm = function() { - var primaryUser = this.getPrimaryUser(); - if (primaryUser && primaryUser.selfCertificate.preferredHashAlgorithms) { - return primaryUser.selfCertificate.preferredHashAlgorithms[0]; - } - return config.prefer_hash_algorithm; -}; - -function isValidEncryptionKeyPacket(keyPacket, signature) { +function isValidEncryptionKeyPacket(keyPacket, signature, allowExpired=false) { return keyPacket.algorithm !== enums.read(enums.publicKey, enums.publicKey.dsa) && 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.encrypt_communication) !== 0 || - (signature.keyFlags[0] & enums.keyFlags.encrypt_storage) !== 0); + (signature.keyFlags[0] & enums.keyFlags.encrypt_storage) !== 0) && + (allowExpired || (!signature.isExpired() && + // check expiration time of V3 key packet + !(keyPacket.version === 3 && keyPacket.expirationTimeV3 !== 0 && + Date.now() > (keyPacket.created.getTime() + keyPacket.expirationTimeV3*24*3600*1000)) && + // check expiration time of V4 key packet + !(keyPacket.version === 4 && signature.keyNeverExpires === false && + Date.now() > (keyPacket.created.getTime() + signature.keyExpirationTime*1000)))); } -function isValidSigningKeyPacket(keyPacket, signature) { - return (keyPacket.algorithm === enums.read(enums.publicKey, enums.publicKey.dsa) || - keyPacket.algorithm === enums.read(enums.publicKey, enums.publicKey.rsa_sign) || - keyPacket.algorithm === enums.read(enums.publicKey, enums.publicKey.rsa_encrypt_sign)) && +function isValidSigningKeyPacket(keyPacket, signature, allowExpired=false) { + 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.sign_data) !== 0); + (signature.keyFlags[0] & enums.keyFlags.sign_data) !== 0) && + (allowExpired || (!signature.isExpired() && + // check expiration time of V3 key packet + !(keyPacket.version === 3 && keyPacket.expirationTimeV3 !== 0 && + Date.now() > (keyPacket.created.getTime() + keyPacket.expirationTimeV3*24*3600*1000)) && + // check expiration time of V4 key packet + !(keyPacket.version === 4 && signature.keyNeverExpires === false && + Date.now() > (keyPacket.created.getTime() + signature.keyExpirationTime*1000)))); + } /** - * Returns the first valid encryption key packet for this key + * Returns first key packet or key packet by given keyId that is available for encryption or decryption + * @param {module:type/keyid} keyId, optional * @returns {(module:packet/public_subkey|module:packet/secret_subkey|module:packet/secret_key|module:packet/public_key|null)} key packet or null if no encryption key has been found */ -Key.prototype.getEncryptionKeyPacket = function() { +Key.prototype.getEncryptionKeyPacket = function(keyId) { // V4: by convention subkeys are preferred for encryption service // V3: keys MUST NOT have subkeys if (this.subKeys) { for (var i = 0; i < this.subKeys.length; i++) { - if (this.subKeys[i].isValidEncryptionKey(this.primaryKey)) { - return this.subKeys[i].subKey; + if (!keyId || this.subKeys[i].subKey.getKeyId().equals(keyId)) { + for (var j = 0; j < this.subKeys[i].bindingSignatures.length; j++) { + if (isValidEncryptionKeyPacket( + this.subKeys[i].subKey, this.subKeys[i].bindingSignatures[j])) { + return this.subKeys[i].subKey; + } + } } } } // if no valid subkey for encryption, evaluate primary key var primaryUser = this.getPrimaryUser(); - if (primaryUser && primaryUser.selfCertificate && !primaryUser.selfCertificate.isExpired() && + if (primaryUser && (!keyId || this.primaryKey.getKeyId().equals(keyId)) && isValidEncryptionKeyPacket(this.primaryKey, primaryUser.selfCertificate)) { return this.primaryKey; } @@ -379,7 +395,7 @@ Key.prototype.encrypt = function(passphrase) { var keys = this.getAllKeyPackets(); for (var i = 0; i < keys.length; i++) { keys[i].encrypt(passphrase); - keys[i].clearPrivateMPIs(); + keys[i].clearPrivateParams(); } }; @@ -435,11 +451,12 @@ Key.prototype.decryptKeyPacket = function(keyIds, passphrase) { * @param {Boolean} allowExpired allows signature verification with expired keys * @return {module:enums.keyStatus} The status of the primary key */ -Key.prototype.verifyPrimaryKey = function(allowExpired=false) { +Key.prototype.verifyPrimaryKey = async function(allowExpired=false) { + // TODO clarify OpenPGP's behavior given an expired revocation signature // check revocation signature if (this.revocationSignature && !this.revocationSignature.isExpired() && (this.revocationSignature.verified || - this.revocationSignature.verify(this.primaryKey, {key: this.primaryKey}))) { + await this.revocationSignature.verify(this.primaryKey, {key: this.primaryKey}))) { return enums.keyStatus.revoked; } // check V3 expiration time @@ -449,17 +466,12 @@ Key.prototype.verifyPrimaryKey = function(allowExpired=false) { } // check for at least one self signature. Self signature of user ID not mandatory // See {@link http://tools.ietf.org/html/rfc4880#section-11.1} - var selfSigned = false; - for (var i = 0; i < this.users.length; i++) { - if (this.users[i].userId && this.users[i].selfCertifications) { - selfSigned = true; - } - } - if (!selfSigned) { + if (!this.users.some(user => user.userId && user.selfCertifications)) { return enums.keyStatus.no_self_cert; } // check for valid self signature - var primaryUser = this.getPrimaryUser(); + await this.verifyPrimaryUser(); + var primaryUser = this.getPrimaryUser(allowExpired); if (!primaryUser) { return enums.keyStatus.invalid; } @@ -509,36 +521,30 @@ function getExpirationTime(keyPacket, selfCertificate) { * @return {{user: Array, selfCertificate: Array}|null} The primary user and the self signature */ Key.prototype.getPrimaryUser = function(allowExpired=false) { - var primUser = []; + var primaryUsers = []; for (var i = 0; i < this.users.length; i++) { + // here we only check the primary user ID, ignoring the primary user attribute if (!this.users[i].userId || !this.users[i].selfCertifications) { continue; } for (var j = 0; j < this.users[i].selfCertifications.length; j++) { - primUser.push({index: i, user: this.users[i], selfCertificate: this.users[i].selfCertifications[j]}); + // only consider already validated certificates + if (!this.users[i].selfCertifications[j].verified || + this.users[i].selfCertifications[j].revoked || + (this.users[i].selfCertifications[j].isExpired() && !allowExpired)) { + continue; + } + primaryUsers.push( + { index: i, user: this.users[i], selfCertificate: this.users[i].selfCertifications[j] } + ); } } // sort by primary user flag and signature creation time - primUser = primUser.sort(function(a, b) { - if (a.selfCertificate.isPrimaryUserID > b.selfCertificate.isPrimaryUserID) { - return -1; - } else if (a.selfCertificate.isPrimaryUserID < b.selfCertificate.isPrimaryUserID) { - return 1; - } else if (a.selfCertificate.created > b.selfCertificate.created) { - return -1; - } else if (a.selfCertificate.created < b.selfCertificate.created) { - return 1; - } else { - return 0; - } + primaryUsers = primaryUsers.sort(function(a, b) { + var A = a.selfCertificate, B = b.selfCertificate; + return (B.isPrimaryUserID - A.isPrimaryUserID) || (B.created - A.created); }); - // return first valid - for (var k = 0; k < primUser.length; k++) { - if (primUser[k].user.isValidSelfCertificate(this.primaryKey, primUser[k].selfCertificate, allowExpired)) { - return primUser[k]; - } - } - return null; + return primaryUsers.pop(); }; /** @@ -549,9 +555,9 @@ Key.prototype.getPrimaryUser = function(allowExpired=false) { * the destination key is transformed to a private key. * @param {module:key~Key} key source key to merge */ -Key.prototype.update = function(key) { +Key.prototype.update = async function(key) { var that = this; - if (key.verifyPrimaryKey() === enums.keyStatus.invalid) { + if (await key.verifyPrimaryKey() === enums.keyStatus.invalid) { return; } if (this.primaryKey.getFingerprint() !== key.primaryKey.getFingerprint()) { @@ -570,44 +576,45 @@ Key.prototype.update = function(key) { } this.primaryKey = key.primaryKey; } + // TODO clarify OpenPGP's behavior given an expired revocation signature // revocation signature if (!this.revocationSignature && key.revocationSignature && !key.revocationSignature.isExpired() && (key.revocationSignature.verified || - key.revocationSignature.verify(key.primaryKey, {key: key.primaryKey}))) { + await key.revocationSignature.verify(key.primaryKey, {key: key.primaryKey}))) { this.revocationSignature = key.revocationSignature; } // direct signatures - mergeSignatures(key, this, 'directSignatures'); + await mergeSignatures(key, this, 'directSignatures'); + // TODO replace when Promise.some or Promise.any are implemented // users - key.users.forEach(function(srcUser) { + await Promise.all(key.users.map(async function(srcUser) { var found = false; - for (var i = 0; i < that.users.length; i++) { - if (srcUser.userId && (srcUser.userId.userid === that.users[i].userId.userid) || - srcUser.userAttribute && (srcUser.userAttribute.equals(that.users[i].userAttribute))) { - that.users[i].update(srcUser, that.primaryKey); + await Promise.all(that.users.map(async function(dstUser) { + if ((srcUser.userId && (srcUser.userId.userid === dstUser.userId.userid)) || + (srcUser.userAttribute && (srcUser.userAttribute.equals(dstUser.userAttribute)))) { + await dstUser.update(srcUser, that.primaryKey); found = true; - break; } - } + })); if (!found) { that.users.push(srcUser); } - }); + })); + // TODO replace when Promise.some or Promise.any are implemented // subkeys if (key.subKeys) { - key.subKeys.forEach(function(srcSubKey) { + await Promise.all(key.subKeys.map(async function(srcSubKey) { var found = false; - for (var i = 0; i < that.subKeys.length; i++) { - if (srcSubKey.subKey.getFingerprint() === that.subKeys[i].subKey.getFingerprint()) { - that.subKeys[i].update(srcSubKey, that.primaryKey); + await Promise.all(that.subKeys.map(async function(dstSubKey) { + if (srcSubKey.subKey.getFingerprint() === dstSubKey.subKey.getFingerprint()) { + await dstSubKey.update(srcSubKey, that.primaryKey); found = true; - break; } - } + })); if (!found) { that.subKeys.push(srcSubKey); } - }); + })); } }; @@ -619,20 +626,20 @@ Key.prototype.update = function(key) { * @param {String} attr * @param {Function} checkFn optional, signature only merged if true */ -function mergeSignatures(source, dest, attr, checkFn) { +async function mergeSignatures(source, dest, attr, checkFn) { source = source[attr]; if (source) { if (!dest[attr]) { dest[attr] = source; } else { - source.forEach(function(sourceSig) { - if (!sourceSig.isExpired() && (!checkFn || checkFn(sourceSig)) && + await Promise.all(source.map(async function(sourceSig) { + if (!sourceSig.isExpired() && (!checkFn || await checkFn(sourceSig)) && !dest[attr].some(function(destSig) { return util.equalsUint8Array(destSig.signature,sourceSig.signature); })) { dest[attr].push(sourceSig); } - }); + })); } } } @@ -647,12 +654,13 @@ Key.prototype.revoke = function() { * @param {Array} privateKey decrypted private keys for signing * @return {module:key~Key} new public key with new certificate signature */ -Key.prototype.signPrimaryUser = function(privateKeys) { +Key.prototype.signPrimaryUser = async function(privateKeys) { + await this.verifyPrimaryUser(); var {index, user} = this.getPrimaryUser() || {}; if (!user) { throw new Error('Could not find primary user'); } - user = user.sign(this.primaryKey, privateKeys); + user = await user.sign(this.primaryKey, privateKeys); var key = new Key(this.toPacketlist()); key.users[index] = user; return key; @@ -663,41 +671,77 @@ Key.prototype.signPrimaryUser = function(privateKeys) { * @param {Array} privateKeys decrypted private keys for signing * @return {module:key~Key} new public key with new certificate signature */ -Key.prototype.signAllUsers = function(privateKeys) { - var users = this.users.map(user => user.sign(this.primaryKey, privateKeys)); +Key.prototype.signAllUsers = async function(privateKeys) { + var that = this; var key = new Key(this.toPacketlist()); - key.users = users; + key.users = await Promise.all(this.users.map(function(user) { + return user.sign(that.primaryKey, 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 * @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature */ -Key.prototype.verifyPrimaryUser = function(keys) { - var {user} = this.getPrimaryUser() || {}; - if (!user) { - throw new Error('Could not find primary user'); - } - return user.verifyAllSignatures(this.primaryKey, keys); +Key.prototype.verifyPrimaryUser = async function(keys) { + var primaryKey = this.primaryKey, primaryUsers = []; + var lastCreated = null, lastPrimaryUserID = null; + await Promise.all(this.users.map(async function(user) { + // here we verify both the primary user ID or the primary user attribute + if (!(user.userId || user.userAttribute) || !user.selfCertifications) { + return; + } + var dataToVerify = { userid: user.userId || user.userAttribute, key: primaryKey }; + await Promise.all(user.selfCertifications.map(async function(selfCertification) { + // skip if certificate is not the most recent + if ((selfCertification.isPrimaryUserID && + selfCertification.isPrimaryUserID < lastPrimaryUserID) || + (!lastPrimaryUserID && selfCertification.created < lastCreated)) { + return; + } + // skip if certificates is not valid + if (!(selfCertification.verified || await selfCertification.verify(primaryKey, dataToVerify)) || + (selfCertification.revoked || await user.isRevoked(primaryKey, selfCertification)) || + selfCertification.isExpired()) { + return; + } + lastPrimaryUserID = selfCertification.isPrimaryUserID; + lastCreated = selfCertification.created; + primaryUsers.push(user); + })); + })); + var user = primaryUsers.pop(); + var results = !user ? [] : keys ? await user.verifyAllCertifications(primaryKey, keys) : + [{ keyid: primaryKey.keyid, valid: await user.verify(primaryKey) === enums.keyStatus.valid }]; + 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 * @return {Array<({userid: String, keyid: module:type/keyid, valid: Boolean})>} list of userid, signer's keyid and validity of signature */ -Key.prototype.verifyAllUsers = function(keys) { - return this.users.reduce((signatures, user) => { - return signatures.concat( - user.verifyAllSignatures(this.primaryKey, keys).map(signature => ({ +Key.prototype.verifyAllUsers = async function(keys) { + var results = []; + var primaryKey = this.primaryKey; + await Promise.all(this.users.map(async function(user) { + var signatures = keys ? await user.verifyAllCertifications(primaryKey, keys) : + [{ keyid: primaryKey.keyid, valid: await user.verify(primaryKey) === enums.keyStatus.valid }]; + signatures.forEach(signature => { + results.push({ userid: user.userId.userid, keyid: signature.keyid, valid: signature.valid - })) - ); - }, []); + }); + }); + })); + return results; }; /** @@ -729,102 +773,112 @@ User.prototype.toPacketlist = function() { }; /** - * Checks if a self signature of the user is revoked - * @param {module:packet/signature} certificate + * Checks if a self certificate of the user is revoked * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet - * @return {Boolean} True if the certificate is revoked + * @param {module:packet/signature} certificate The certificate to verify + * @param {module:packet/public_subkey|module:packet/public_key| + * module:packet/secret_subkey|module:packet/secret_key} key, optional The key to verify the signature + * @return {Boolean} True if the certificate is revoked */ -User.prototype.isRevoked = function(certificate, primaryKey) { +User.prototype.isRevoked = async function(primaryKey, certificate, key) { if (this.revocationCertifications) { - var that = this; - return this.revocationCertifications.some(function(revCert) { + var dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; + // TODO clarify OpenPGP's behavior given an expired revocation signature + var results = await Promise.all(this.revocationCertifications.map(async function(revCert) { return revCert.issuerKeyId.equals(certificate.issuerKeyId) && - !revCert.isExpired() && - (revCert.verified || - revCert.verify(primaryKey, {userid: that.userId || that.userAttribute, key: primaryKey})); - }); + !revCert.isExpired() && + (revCert.verified || revCert.verify(key ? key : primaryKey, dataToVerify)); + })); + certificate.revoked = results.some(result => result === true); + return certificate.revoked; } else { return false; } }; -/** - * Returns true if the self certificate is valid - * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet - * @param {module:packet/signature} selfCertificate A self certificate of this user - * @param {Boolean} allowExpired allows signature verification with expired keys - * @return {Boolean} - */ -User.prototype.isValidSelfCertificate = function(primaryKey, selfCertificate, allowExpired=false) { - if (this.isRevoked(selfCertificate, primaryKey)) { - return false; - } - if ((!selfCertificate.isExpired() || allowExpired) && - (selfCertificate.verified || - selfCertificate.verify(primaryKey, {userid: this.userId || this.userAttribute, key: primaryKey}))) { - return true; - } - return false; -}; - /** * Signs user * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet * @param {Array} privateKeys decrypted private keys for signing * @return {module:key~Key} new user with new certificate signatures */ -User.prototype.sign = function(primaryKey, privateKeys) { - var user, dataToSign, signingKeyPacket, signaturePacket; - dataToSign = {}; - dataToSign.key = primaryKey; - dataToSign.userid = this.userId || this.userAttribute; - user = new User(this.userId || this.userAttribute); - user.otherCertifications = []; - privateKeys.forEach(privateKey => { +User.prototype.sign = async function(primaryKey, privateKeys) { + const dataToSign = { userid: this.userId || this.userAttribute, key: primaryKey }; + const user = new User(dataToSign.userid); + user.otherCertifications = await Promise.all(privateKeys.map(async function(privateKey) { if (privateKey.isPublic()) { throw new Error('Need private key for signing'); } if (privateKey.primaryKey.getFingerprint() === primaryKey.getFingerprint()) { throw new Error('Not implemented for self signing'); } - signingKeyPacket = privateKey.getSigningKeyPacket(); + await privateKey.verifyPrimaryUser(); + const signingKeyPacket = privateKey.getSigningKeyPacket(); if (!signingKeyPacket) { - throw new Error('Could not find valid signing key packet'); + throw new Error('Could not find valid signing key packet in key ' + + privateKey.primaryKey.getKeyId().toHex()); } if (!signingKeyPacket.isDecrypted) { throw new Error('Private key is not decrypted.'); } - signaturePacket = new packet.Signature(); + const signaturePacket = new packet.Signature(); // Most OpenPGP implementations use generic certification (0x10) signaturePacket.signatureType = enums.write(enums.signature, enums.signature.cert_generic); signaturePacket.keyFlags = [enums.keyFlags.certify_keys | enums.keyFlags.sign_data]; - signaturePacket.hashAlgorithm = privateKey.getPreferredHashAlgorithm(); signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; + signaturePacket.hashAlgorithm = getPreferredHashAlgo(privateKey); signaturePacket.signingKeyId = signingKeyPacket.getKeyId(); signaturePacket.sign(signingKeyPacket, dataToSign); - user.otherCertifications.push(signaturePacket); - }); - user.update(this, primaryKey); + return signaturePacket; + })); + await user.update(this, primaryKey); return user; }; /** - * Verifies all user signatures + * Verifies the user certificate + * @param {module:packet/secret_key|module:packet/public_key} 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 {Boolean} allowExpired allows signature verification with expired keys + * @return {module:enums.keyStatus} status of the certificate + */ +User.prototype.verifyCertificate = async function(primaryKey, certificate, keys, allowExpired=false) { + var that = this; + var keyid = certificate.issuerKeyId; + var dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; + var results = await Promise.all(keys.map(async function(key) { + if (!key.getKeyIds().some(id => id.equals(keyid))) { return; } + await key.verifyPrimaryUser(); + var keyPacket = key.getSigningKeyPacket(keyid, allowExpired); + if (certificate.revoked || await that.isRevoked(primaryKey, certificate, keyPacket)) { + return enums.keyStatus.revoked; + } + if (!(certificate.verified || await certificate.verify(keyPacket, dataToVerify))) { + return enums.keyStatus.invalid; + } + if (certificate.isExpired()) { + return enums.keyStatus.expired; + } + return enums.keyStatus.valid; + })); + return results.find(result => result !== undefined); +}; + +/** + * Verifies all user certificates * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet * @param {Array} keys array of keys to verify certificate signatures * @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature */ -User.prototype.verifyAllSignatures = function(primaryKey, keys) { - var dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; - var certificates = this.selfCertifications.concat(this.otherCertifications || []); - return certificates.map(signaturePacket => { - var keyPackets = keys.filter(key => key.getSigningKeyPacket(signaturePacket.issuerKeyId)); - var valid = null; - if (keyPackets.length > 0) { - valid = keyPackets.some(keyPacket => signaturePacket.verify(keyPacket.primaryKey, dataToVerify)); - } - return { keyid: signaturePacket.issuerKeyId, valid: valid }; - }); +User.prototype.verifyAllCertifications = async function(primaryKey, keys) { + var that = this; + var certifications = this.selfCertifications.concat(this.otherCertifications || []); + return Promise.all(certifications.map(async function(certification) { + var status = await that.verifyCertificate(primaryKey, certification, keys); + return { keyid: certification.issuerKeyId, + valid: status === undefined ? null : status === enums.keyStatus.valid }; + })); }; /** @@ -833,29 +887,28 @@ User.prototype.verifyAllSignatures = function(primaryKey, keys) { * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet * @return {module:enums.keyStatus} status of user */ -User.prototype.verify = function(primaryKey) { +User.prototype.verify = async function(primaryKey) { if (!this.selfCertifications) { return enums.keyStatus.no_self_cert; } - var status; - for (var i = 0; i < this.selfCertifications.length; i++) { - if (this.isRevoked(this.selfCertifications[i], primaryKey)) { - status = enums.keyStatus.revoked; - continue; - } - if (!(this.selfCertifications[i].verified || - this.selfCertifications[i].verify(primaryKey, {userid: this.userId || this.userAttribute, key: primaryKey}))) { - status = enums.keyStatus.invalid; - continue; - } - if (this.selfCertifications[i].isExpired()) { - status = enums.keyStatus.expired; - continue; - } - status = enums.keyStatus.valid; - break; - } - return status; + var that = this; + var dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; + // TODO replace when Promise.some or Promise.any are implemented + var results = [enums.keyStatus.invalid].concat( + await Promise.all(this.selfCertifications.map(async function(selfCertification, i) { + if (selfCertification.revoked || await that.isRevoked(primaryKey, selfCertification)) { + return enums.keyStatus.revoked; + } + if (!(selfCertification.verified || await selfCertification.verify(primaryKey, dataToVerify))) { + return enums.keyStatus.invalid; + } + if (selfCertification.isExpired()) { + return enums.keyStatus.expired; + } + return enums.keyStatus.valid; + }))); + return results.some(status => status === enums.keyStatus.valid) ? + enums.keyStatus.valid : results.pop(); }; /** @@ -863,17 +916,16 @@ User.prototype.verify = function(primaryKey) { * @param {module:key~User} user source user to merge * @param {module:packet/signature} primaryKey primary key used for validation */ -User.prototype.update = function(user, primaryKey) { - var that = this; +User.prototype.update = async function(user, primaryKey) { + var dataToVerify = { userid: this.userId || this.userAttribute, key: primaryKey }; // self signatures - mergeSignatures(user, this, 'selfCertifications', function(srcSelfSig) { - return srcSelfSig.verified || - srcSelfSig.verify(primaryKey, {userid: that.userId || that.userAttribute, key: primaryKey}); + await mergeSignatures(user, this, 'selfCertifications', async function(srcSelfSig) { + return srcSelfSig.verified || srcSelfSig.verify(primaryKey, dataToVerify); }); // other signatures - mergeSignatures(user, this, 'otherCertifications'); + await mergeSignatures(user, this, 'otherCertifications'); // revocation signatures - mergeSignatures(user, this, 'revocationCertifications'); + await mergeSignatures(user, this, 'revocationCertifications'); }; /** @@ -908,8 +960,8 @@ SubKey.prototype.toPacketlist = function() { * @param {module:packet/secret_key|module:packet/public_key} primaryKey The primary key packet * @return {Boolean} */ -SubKey.prototype.isValidEncryptionKey = function(primaryKey) { - if(this.verify(primaryKey) !== enums.keyStatus.valid) { +SubKey.prototype.isValidEncryptionKey = async function(primaryKey) { + if(await this.verify(primaryKey) !== enums.keyStatus.valid) { return false; } for(var i = 0; i < this.bindingSignatures.length; i++) { @@ -926,12 +978,12 @@ SubKey.prototype.isValidEncryptionKey = function(primaryKey) { * @param {Boolean} allowExpired allows signature verification with expired keys * @return {Boolean} */ -SubKey.prototype.isValidSigningKey = function(primaryKey, allowExpired=false) { - if(this.verify(primaryKey, allowExpired) !== enums.keyStatus.valid) { +SubKey.prototype.isValidSigningKey = async function(primaryKey, allowExpired=false) { + if(await this.verify(primaryKey, allowExpired) !== enums.keyStatus.valid) { return false; } for(var i = 0; i < this.bindingSignatures.length; i++) { - if(isValidSigningKeyPacket(this.subKey, this.bindingSignatures[i])) { + if(isValidSigningKeyPacket(this.subKey, this.bindingSignatures[i], allowExpired)) { return true; } } @@ -945,11 +997,13 @@ SubKey.prototype.isValidSigningKey = function(primaryKey, allowExpired=false) { * @param {Boolean} allowExpired allows signature verification with expired keys * @return {module:enums.keyStatus} The status of the subkey */ -SubKey.prototype.verify = function(primaryKey, allowExpired=false) { +SubKey.prototype.verify = async function(primaryKey, allowExpired=false) { + var that = this; + // TODO clarify OpenPGP's behavior given an expired revocation signature // check subkey revocation signature if (this.revocationSignature && !this.revocationSignature.isExpired() && (this.revocationSignature.verified || - this.revocationSignature.verify(primaryKey, {key:primaryKey, bind: this.subKey}))) { + await this.revocationSignature.verify(primaryKey, {key:primaryKey, bind: this.subKey}))) { return enums.keyStatus.revoked; } // check V3 expiration time @@ -958,38 +1012,29 @@ SubKey.prototype.verify = function(primaryKey, allowExpired=false) { return enums.keyStatus.expired; } // check subkey binding signatures (at least one valid binding sig needed) - for(var i = 0; i < this.bindingSignatures.length; i++) { - var isLast = (i === this.bindingSignatures.length - 1); - var sig = this.bindingSignatures[i]; + // TODO replace when Promise.some or Promise.any are implemented + var results = [enums.keyStatus.invalid].concat( + await Promise.all(this.bindingSignatures.map(async function(bindingSignature) { // check binding signature is not expired - if(!allowExpired && sig.isExpired()) { - if(isLast) { - return enums.keyStatus.expired; // last expired binding signature - } else { - continue; - } + if(!allowExpired && bindingSignature.isExpired()) { + return enums.keyStatus.expired; // last expired binding signature } // check binding signature can verify - if (!(sig.verified || sig.verify(primaryKey, {key: primaryKey, bind: this.subKey}))) { - if(isLast) { - return enums.keyStatus.invalid; // last invalid binding signature - } else { - continue; - } + if (!(bindingSignature.verified || + await bindingSignature.verify(primaryKey, {key: primaryKey, bind: that.subKey}))) { + return enums.keyStatus.invalid; // last invalid binding signature } // check V4 expiration time - if (this.subKey.version === 4) { - if(!allowExpired && sig.keyNeverExpires === false && Date.now() > (this.subKey.created.getTime() + sig.keyExpirationTime*1000)) { - if(isLast) { - return enums.keyStatus.expired; // last V4 expired binding signature - } else { - continue; - } + if (that.subKey.version === 4) { + if(!allowExpired && bindingSignature.keyNeverExpires === false && + Date.now() > (that.subKey.created.getTime() + bindingSignature.keyExpirationTime*1000)) { + return enums.keyStatus.expired; // last V4 expired binding signature } } return enums.keyStatus.valid; // found a binding signature that passed all checks - } - return enums.keyStatus.invalid; // no binding signatures to check + }))); + return results.some(status => status === enums.keyStatus.valid) ? + enums.keyStatus.valid : results.pop(); }; /** @@ -1015,8 +1060,8 @@ SubKey.prototype.getExpirationTime = function() { * @param {module:key~SubKey} subKey source subkey to merge * @param {module:packet/signature} primaryKey primary key used for validation */ -SubKey.prototype.update = function(subKey, primaryKey) { - if (subKey.verify(primaryKey) === enums.keyStatus.invalid) { +SubKey.prototype.update = async function(subKey, primaryKey) { + if (await subKey.verify(primaryKey) === enums.keyStatus.invalid) { return; } if (this.subKey.getFingerprint() !== subKey.subKey.getFingerprint()) { @@ -1028,18 +1073,28 @@ SubKey.prototype.update = function(subKey, primaryKey) { this.subKey = subKey.subKey; } // update missing binding signatures - if(this.bindingSignatures.length < subKey.bindingSignatures.length) { - for(var i = this.bindingSignatures.length; i < subKey.bindingSignatures.length; i++) { - var newSig = subKey.bindingSignatures[i]; - if (newSig.verified || newSig.verify(primaryKey, {key: primaryKey, bind: this.subKey})) { - this.bindingSignatures.push(newSig); + var that = this; + await Promise.all(subKey.bindingSignatures.map(async function(newBindingSignature) { + if (newBindingSignature.verified || + await newBindingSignature.verify(primaryKey, {key: primaryKey, bind: that.subKey })) { + for (var i = 0; i < that.bindingSignatures.length; i++) { + if (that.bindingSignatures[i].issuerKeyId.equals(newBindingSignature.issuerKeyId)) { + that.bindingSignatures[i] = newBindingSignature; + return; + } } + that.bindingSignatures.push(newBindingSignature); } - } + })); + // TODO clarify OpenPGP's behavior given an expired revocation signature // revocation signature - if (!this.revocationSignature && subKey.revocationSignature && !subKey.revocationSignature.isExpired() && - (subKey.revocationSignature.verified || - subKey.revocationSignature.verify(primaryKey, {key: primaryKey, bind: this.subKey}))) { + if (!this.revocationSignature && + subKey.revocationSignature && + !subKey.revocationSignature.isExpired() && + (subKey.revocationSignature.verified || + await subKey.revocationSignature.verify( + primaryKey, {key: primaryKey, bind: this.subKey} + ))) { this.revocationSignature = subKey.revocationSignature; } }; @@ -1098,7 +1153,7 @@ export function readArmored(armoredText) { } /** - * Generates a new OpenPGP key. Currently only supports RSA keys. + * Generates a new OpenPGP key. Supports RSA and ECC keys. * 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 http://tools.ietf.org/html/rfc4880#section-9.1} @@ -1114,15 +1169,43 @@ export function readArmored(armoredText) { export function generate(options) { var secretKeyPacket, secretSubkeyPacket; return Promise.resolve().then(() => { - options.keyType = options.keyType || enums.publicKey.rsa_encrypt_sign; - if (options.keyType !== enums.publicKey.rsa_encrypt_sign) { // RSA Encrypt-Only and RSA Sign-Only are deprecated and SHOULD NOT be generated - throw new Error('Only RSA Encrypt or Sign supported'); + + 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.keyType = options.keyType || enums.publicKey.eddsa; + } else { + options.keyType = options.keyType || enums.publicKey.ecdsa; + } + options.subkeyType = options.subkeyType || enums.publicKey.ecdh; + } else if (options.numBits) { + options.keyType = options.keyType || enums.publicKey.rsa_encrypt_sign; + options.subkeyType = options.subkeyType || enums.publicKey.rsa_encrypt_sign; + } else { + throw new Error('Key type not specified.'); + } + + if (options.keyType !== enums.publicKey.rsa_encrypt_sign && + options.keyType !== enums.publicKey.ecdsa && + options.keyType !== enums.publicKey.eddsa) { + // RSA Encrypt-Only and RSA Sign-Only are deprecated and SHOULD NOT be generated + throw new Error('Unsupported key type'); + } + + if (options.subkeyType !== enums.publicKey.rsa_encrypt_sign && + options.subkeyType !== enums.publicKey.ecdh) { + // RSA Encrypt-Only and RSA Sign-Only are deprecated and SHOULD NOT be generated + throw new Error('Unsupported subkey type'); } if (!options.passphrase) { // Key without passphrase is unlocked by definition options.unlocked = true; } - if (String.prototype.isPrototypeOf(options.userIds) || typeof options.userIds === 'string') { + if (util.isString(options.userIds)) { options.userIds = [options.userIds]; } @@ -1133,14 +1216,18 @@ export function generate(options) { function generateSecretKey() { secretKeyPacket = new packet.SecretKey(); + secretKeyPacket.packets = null; secretKeyPacket.algorithm = enums.read(enums.publicKey, options.keyType); - return secretKeyPacket.generate(options.numBits); + options.curve = options.curve === enums.curve.curve25519 ? enums.curve.ed25519 : options.curve; + return secretKeyPacket.generate(options.numBits, options.curve); } function generateSecretSubkey() { secretSubkeyPacket = new packet.SecretSubkey(); - secretSubkeyPacket.algorithm = enums.read(enums.publicKey, options.keyType); - return secretSubkeyPacket.generate(options.numBits); + secretKeyPacket.packets = null; + secretSubkeyPacket.algorithm = enums.read(enums.publicKey, options.subkeyType); + options.curve = options.curve === enums.curve.ed25519 ? enums.curve.curve25519 : options.curve; + return secretSubkeyPacket.generate(options.numBits, options.curve); } } @@ -1172,22 +1259,24 @@ export function reformat(options) { if (!options.passphrase) { // Key without passphrase is unlocked by definition options.unlocked = true; } - if (String.prototype.isPrototypeOf(options.userIds) || typeof options.userIds === 'string') { + if (util.isString(options.userIds)) { options.userIds = [options.userIds]; } var packetlist = options.privateKey.toPacketlist(); for (var i = 0; i < packetlist.length; i++) { if (packetlist[i].tag === enums.packet.secretKey) { secretKeyPacket = packetlist[i]; + options.keyType = secretKeyPacket.algorithm; } else if (packetlist[i].tag === enums.packet.secretSubkey) { secretSubkeyPacket = packetlist[i]; + options.subkeyType = secretSubkeyPacket.algorithm; } } return wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options); }); } -function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { +async function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { // set passphrase protection if (options.passphrase) { secretKeyPacket.encrypt(options.passphrase); @@ -1198,7 +1287,7 @@ function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { packetlist.push(secretKeyPacket); - options.userIds.forEach(function(userId, index) { + await Promise.all(options.userIds.map(async function(userId, index) { var userIdPacket = new packet.Userid(); userIdPacket.read(util.str2Uint8Array(userId)); @@ -1209,7 +1298,7 @@ function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { var signaturePacket = new packet.Signature(); signaturePacket.signatureType = enums.signature.cert_generic; signaturePacket.publicKeyAlgorithm = options.keyType; - signaturePacket.hashAlgorithm = config.prefer_hash_algorithm; + signaturePacket.hashAlgorithm = getPreferredHashAlgo(secretKeyPacket); signaturePacket.keyFlags = [enums.keyFlags.certify_keys | enums.keyFlags.sign_data]; signaturePacket.preferredSymmetricAlgorithms = []; // prefer aes256, aes128, then aes192 (no WebCrypto support: https://www.chromium.org/blink/webcrypto#TOC-AES-support) @@ -1237,11 +1326,14 @@ function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { signaturePacket.keyExpirationTime = options.keyExpirationTime; signaturePacket.keyNeverExpires = false; } - signaturePacket.sign(secretKeyPacket, dataToSign); - - packetlist.push(userIdPacket); - packetlist.push(signaturePacket); + await signaturePacket.sign(secretKeyPacket, dataToSign); + return {userIdPacket, signaturePacket}; + })).then(list => { + list.forEach(({userIdPacket, signaturePacket}) => { + packetlist.push(userIdPacket); + packetlist.push(signaturePacket); + }); }); var dataToSign = {}; @@ -1250,25 +1342,58 @@ function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) { var subkeySignaturePacket = new packet.Signature(); subkeySignaturePacket.signatureType = enums.signature.subkey_binding; subkeySignaturePacket.publicKeyAlgorithm = options.keyType; - subkeySignaturePacket.hashAlgorithm = config.prefer_hash_algorithm; + subkeySignaturePacket.hashAlgorithm = getPreferredHashAlgo(secretSubkeyPacket); subkeySignaturePacket.keyFlags = [enums.keyFlags.encrypt_communication | enums.keyFlags.encrypt_storage]; if (options.keyExpirationTime > 0) { subkeySignaturePacket.keyExpirationTime = options.keyExpirationTime; subkeySignaturePacket.keyNeverExpires = false; } - subkeySignaturePacket.sign(secretKeyPacket, dataToSign); + await subkeySignaturePacket.sign(secretKeyPacket, dataToSign); packetlist.push(secretSubkeyPacket); packetlist.push(subkeySignaturePacket); if (!options.unlocked) { - secretKeyPacket.clearPrivateMPIs(); - secretSubkeyPacket.clearPrivateMPIs(); + secretKeyPacket.clearPrivateParams(); + secretSubkeyPacket.clearPrivateParams(); } return new Key(packetlist); } +/** + * Returns the preferred signature hash algorithm of a key + * @param {object} key + * @return {String} + */ +export function getPreferredHashAlgo(key) { + var hash_algo = config.prefer_hash_algorithm, + pref_algo = hash_algo; + if (Key.prototype.isPrototypeOf(key)) { + var primaryUser = key.getPrimaryUser(); + if (primaryUser && primaryUser.selfCertificate.preferredHashAlgorithms) { + pref_algo = primaryUser.selfCertificate.preferredHashAlgorithms[0]; + hash_algo = crypto.hash.getHashByteLength(hash_algo) <= crypto.hash.getHashByteLength(pref_algo) ? + pref_algo : hash_algo; + } + key = key.getSigningKeyPacket(); + } + switch(Object.getPrototypeOf(key)) { + case packet.SecretKey.prototype: + case packet.PublicKey.prototype: + case packet.SecretSubkey.prototype: + case packet.PublicSubkey.prototype: + switch(key.algorithm) { + case 'ecdh': + case 'ecdsa': + case 'eddsa': + pref_algo = crypto.publicKey.elliptic.getPreferredHashAlgo(key.params[0]); + } + } + return crypto.hash.getHashByteLength(hash_algo) <= crypto.hash.getHashByteLength(pref_algo) ? + pref_algo : hash_algo; +} + /** * Returns the preferred symmetric algorithm for a set of keys * @param {Array} keys Set of keys diff --git a/src/keyring/index.js b/src/keyring/index.js index 54bea772..430e4caa 100644 --- a/src/keyring/index.js +++ b/src/keyring/index.js @@ -5,8 +5,8 @@ * @module keyring */ import Keyring from './keyring.js'; - import localstore from './localstore.js'; + Keyring.localstore = localstore; export default Keyring; diff --git a/src/keyring/keyring.js b/src/keyring/keyring.js index 97e8975f..7bb89ba9 100644 --- a/src/keyring/keyring.js +++ b/src/keyring/keyring.js @@ -17,22 +17,22 @@ /** * The class that deals with storage of the keyring. Currently the only option is to use HTML5 local storage. - * @requires enums * @requires key - * @requires util + * @requires keyring/localstore * @module keyring/keyring + * @param {keyring/localstore} [storeHandler] class implementing loadPublic(), loadPrivate(), storePublic(), and storePrivate() methods */ 'use strict'; -import * as keyModule from '../key.js'; -import LocalStore from './localstore.js'; +import { readArmored } from '../key'; +import LocalStore from './localstore'; /** * Initialization routine for the keyring. This method reads the * keyring from HTML5 local storage and initializes this instance. * @constructor - * @param {class} [storeHandler] class implementing loadPublic(), loadPrivate(), storePublic(), and storePrivate() methods + * @param {keyring/localstore} [storeHandler] class implementing loadPublic(), loadPrivate(), storePublic(), and storePrivate() methods */ export default function Keyring(storeHandler) { this.storeHandler = storeHandler || new LocalStore(); @@ -181,14 +181,14 @@ KeyArray.prototype.getForId = function (keyId, deep) { * @return {Array|null} array of error objects or null */ KeyArray.prototype.importKey = function (armored) { - var imported = keyModule.readArmored(armored); + var imported = readArmored(armored); var that = this; - imported.keys.forEach(function(key) { + imported.keys.forEach(async function(key) { // check if key already in key array var keyidHex = key.primaryKey.getKeyId().toHex(); var keyFound = that.getForId(keyidHex); if (keyFound) { - keyFound.update(key); + await keyFound.update(key); } else { that.push(key); } diff --git a/src/keyring/localstore.js b/src/keyring/localstore.js index b190359a..4bbc8588 100644 --- a/src/keyring/localstore.js +++ b/src/keyring/localstore.js @@ -18,6 +18,8 @@ /** * The class that deals with storage of the keyring. Currently the only option is to use HTML5 local storage. * @requires config + * @requires key + * @requires util * @module keyring/localstore * @param {String} prefix prefix for itemnames in localstore */ @@ -25,8 +27,8 @@ 'use strict'; import config from '../config'; -import * as keyModule from '../key.js'; -import util from '../util.js'; +import { readArmored } from '../key'; +import util from '../util'; export default function LocalStore(prefix) { prefix = prefix || 'openpgp-'; @@ -67,7 +69,7 @@ function loadKeys(storage, itemname) { if (armoredKeys !== null && armoredKeys.length !== 0) { var key; for (var i = 0; i < armoredKeys.length; i++) { - key = keyModule.readArmored(armoredKeys[i]); + key = readArmored(armoredKeys[i]); if (!key.err) { keys.push(key.keys[0]); } else { diff --git a/src/message.js b/src/message.js index 139ca4de..ebe6b285 100644 --- a/src/message.js +++ b/src/message.js @@ -20,20 +20,24 @@ * @requires crypto * @requires encoding/armor * @requires enums + * @requires util * @requires packet + * @requires signature + * @requires key * @module message */ 'use strict'; -import util from './util.js'; -import packet from './packet'; -import enums from './enums.js'; -import armor from './encoding/armor.js'; import config from './config'; import crypto from './crypto'; -import * as sigModule from './signature.js'; -import * as keyModule from './key.js'; +import armor from './encoding/armor'; +import enums from './enums'; +import util from './util'; +import packet from './packet'; +import { Signature } from './signature'; +import { getPreferredHashAlgo, getPreferredSymAlgo } from './key'; + /** * @class @@ -92,90 +96,112 @@ Message.prototype.getSigningKeyIds = function() { * @param {String} password (optional) password used to decrypt * @return {Message} new message with decrypted content */ -Message.prototype.decrypt = function(privateKey, sessionKey, password) { - return Promise.resolve().then(() => { - const keyObj = sessionKey || this.decryptSessionKey(privateKey, password); - if (!keyObj || !util.isUint8Array(keyObj.data) || !util.isString(keyObj.algorithm)) { +Message.prototype.decrypt = async function(privateKey, sessionKey, password) { + let keyObjs = sessionKey || await this.decryptSessionKeys(privateKey, password); + if (!util.isArray(keyObjs)) { + keyObjs = [keyObjs]; + } + + const symEncryptedPacketlist = this.packets.filterByTag( + enums.packet.symmetricallyEncrypted, + enums.packet.symEncryptedIntegrityProtected, + enums.packet.symEncryptedAEADProtected + ); + + if (symEncryptedPacketlist.length === 0) { + return; + } + + const symEncryptedPacket = symEncryptedPacketlist[0]; + let exception = null; + for (let i = 0; i < keyObjs.length; i++) { + if (!keyObjs[i] || !util.isUint8Array(keyObjs[i].data) || !util.isString(keyObjs[i].algorithm)) { throw new Error('Invalid session key for decryption.'); } - const symEncryptedPacketlist = this.packets.filterByTag( - enums.packet.symmetricallyEncrypted, - enums.packet.symEncryptedIntegrityProtected, - enums.packet.symEncryptedAEADProtected - ); - - if (symEncryptedPacketlist.length === 0) { - return; + try { + // eslint-disable-next-line no-await-in-loop + await symEncryptedPacket.decrypt(keyObjs[i].algorithm, keyObjs[i].data); + break; } + catch(e) { + exception = e; + } + } - const symEncryptedPacket = symEncryptedPacketlist[0]; - return symEncryptedPacket.decrypt(keyObj.algorithm, keyObj.data).then(() => { - const resultMsg = new Message(symEncryptedPacket.packets); - symEncryptedPacket.packets = new packet.List(); // remove packets after decryption - return resultMsg; - }); - }); + if (!symEncryptedPacket.packets || !symEncryptedPacket.packets.length) { + throw exception ? exception : new Error('Decryption failed.'); + } + + const resultMsg = new Message(symEncryptedPacket.packets); + symEncryptedPacket.packets = new packet.List(); // remove packets after decryption + + return resultMsg; }; /** * Decrypt an encrypted session key either with a private key or a password. * @param {Key} privateKey (optional) private key with decrypted secret data * @param {String} password (optional) password used to decrypt - * @return {Object} object with sessionKey, algorithm in the form: - * { data:Uint8Array, algorithm:String } + * @return {Array<{ data:Uint8Array, algorithm:String }>} array of object with potential sessionKey, algorithm pairs */ -Message.prototype.decryptSessionKey = function(privateKey, password) { - var keyPacket; - - if (password) { - var symEncryptedSessionKeyPacketlist = this.packets.filterByTag(enums.packet.symEncryptedSessionKey); - var symLength = symEncryptedSessionKeyPacketlist.length; - for (var i = 0; i < symLength; i++) { - keyPacket = symEncryptedSessionKeyPacketlist[i]; - try { - keyPacket.decrypt(password); - break; +Message.prototype.decryptSessionKeys = function(privateKey, password) { + var keyPackets = []; + return Promise.resolve().then(async () => { + if (password) { + var symESKeyPacketlist = this.packets.filterByTag(enums.packet.symEncryptedSessionKey); + if (!symESKeyPacketlist) { + throw new Error('No symmetrically encrypted session key packet found.'); } - catch(err) { - if (i === (symLength - 1)) { - throw err; + await Promise.all(symESKeyPacketlist.map(async function(packet) { + try { + await packet.decrypt(password); + keyPackets.push(packet); + } catch (err) {} + })); + + } else if (privateKey) { + var pkESKeyPacketlist = this.packets.filterByTag(enums.packet.publicKeyEncryptedSessionKey); + if (!pkESKeyPacketlist) { + throw new Error('No public key encrypted session key packet found.'); + } + var privateKeyPacket = privateKey.getKeyPacket(this.getEncryptionKeyIds()); + if (!privateKeyPacket.isDecrypted) { + throw new Error('Private key is not decrypted.'); + } + await Promise.all(pkESKeyPacketlist.map(async function(packet) { + if (packet.publicKeyId.equals(privateKeyPacket.getKeyId())) { + try { + await packet.decrypt(privateKeyPacket); + keyPackets.push(packet); + } catch (err) {} } + })); + } else { + throw new Error('No key or password specified.'); + } + }).then(() => { + + if (keyPackets.length) { + + // Return only unique session keys + if (keyPackets.length > 1) { + var seen = {}; + keyPackets = keyPackets.filter(function(item) { + var k = item.sessionKeyAlgorithm + util.Uint8Array2str(item.sessionKey); + if (seen.hasOwnProperty(k)) { + return false; + } + seen[k] = true; + return true; + }); } - } - if (!keyPacket) { - throw new Error('No symmetrically encrypted session key packet found.'); - } - } else if (privateKey) { - var encryptionKeyIds = this.getEncryptionKeyIds(); - if (!encryptionKeyIds.length) { - // nothing to decrypt - return; + return keyPackets.map(packet => ({ data: packet.sessionKey, algorithm: packet.sessionKeyAlgorithm })); + } else { + throw new Error('Session key decryption failed.'); } - var privateKeyPacket = privateKey.getKeyPacket(encryptionKeyIds); - if (!privateKeyPacket.isDecrypted) { - throw new Error('Private key is not decrypted.'); - } - var pkESKeyPacketlist = this.packets.filterByTag(enums.packet.publicKeyEncryptedSessionKey); - for (var j = 0; j < pkESKeyPacketlist.length; j++) { - if (pkESKeyPacketlist[j].publicKeyId.equals(privateKeyPacket.getKeyId())) { - keyPacket = pkESKeyPacketlist[j]; - keyPacket.decrypt(privateKeyPacket); - break; - } - } - - } else { - throw new Error('No key or password specified.'); - } - - if (keyPacket) { - return { - data: keyPacket.sessionKey, - algorithm: keyPacket.sessionKeyAlgorithm - }; - } + }); }; /** @@ -184,7 +210,7 @@ Message.prototype.decryptSessionKey = function(privateKey, password) { */ Message.prototype.getLiteralData = function() { var literal = this.packets.findPacket(enums.packet.literal); - return literal && literal.data || null; + return (literal && literal.data) || null; }; /** @@ -193,7 +219,7 @@ Message.prototype.getLiteralData = function() { */ Message.prototype.getFilename = function() { var literal = this.packets.findPacket(enums.packet.literal); - return literal && literal.getFilename() || null; + return (literal && literal.getFilename()) || null; }; /** @@ -218,7 +244,7 @@ Message.prototype.getText = function() { */ Message.prototype.encrypt = function(keys, passwords, sessionKey) { let symAlgo, msg, symEncryptedPacket; - return Promise.resolve().then(() => { + return Promise.resolve().then(async () => { if (sessionKey) { if (!util.isUint8Array(sessionKey.data) || !util.isString(sessionKey.algorithm)) { throw new Error('Invalid session key for encryption.'); @@ -226,7 +252,7 @@ Message.prototype.encrypt = function(keys, passwords, sessionKey) { symAlgo = sessionKey.algorithm; sessionKey = sessionKey.data; } else if (keys && keys.length) { - symAlgo = enums.read(enums.symmetric, keyModule.getPreferredSymAlgo(keys)); + symAlgo = enums.read(enums.symmetric, getPreferredSymAlgo(keys)); } else if (passwords && passwords.length) { symAlgo = enums.read(enums.symmetric, config.encryption_cipher); } else { @@ -237,7 +263,7 @@ Message.prototype.encrypt = function(keys, passwords, sessionKey) { sessionKey = crypto.generateSessionKey(symAlgo); } - msg = encryptSessionKey(sessionKey, symAlgo, keys, passwords); + msg = await encryptSessionKey(sessionKey, symAlgo, keys, passwords); if (config.aead_protect) { symEncryptedPacket = new packet.SymEncryptedAEADProtected(); @@ -272,38 +298,66 @@ Message.prototype.encrypt = function(keys, passwords, sessionKey) { * @return {Message} new message with encrypted content */ export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords) { - var packetlist = new packet.List(); + var results, packetlist = new packet.List(); - if (publicKeys) { - publicKeys.forEach(function(key) { - var encryptionKeyPacket = key.getEncryptionKeyPacket(); - if (encryptionKeyPacket) { + return Promise.resolve().then(async () => { + if (publicKeys) { + results = await Promise.all(publicKeys.map(async function(key) { + await key.verifyPrimaryUser(); + var encryptionKeyPacket = key.getEncryptionKeyPacket(); + if (!encryptionKeyPacket) { + throw new Error('Could not find valid key packet for encryption in key ' + key.primaryKey.getKeyId().toHex()); + } var pkESKeyPacket = new packet.PublicKeyEncryptedSessionKey(); pkESKeyPacket.publicKeyId = encryptionKeyPacket.getKeyId(); pkESKeyPacket.publicKeyAlgorithm = encryptionKeyPacket.algorithm; pkESKeyPacket.sessionKey = sessionKey; pkESKeyPacket.sessionKeyAlgorithm = symAlgo; - pkESKeyPacket.encrypt(encryptionKeyPacket); + await pkESKeyPacket.encrypt(encryptionKeyPacket); delete pkESKeyPacket.sessionKey; // delete plaintext session key after encryption - packetlist.push(pkESKeyPacket); - } else { - throw new Error('Could not find valid key packet for encryption in key ' + key.primaryKey.getKeyId().toHex()); - } - }); - } + return pkESKeyPacket; + })); + packetlist.concat(results); + } - if (passwords) { - passwords.forEach(function(password) { - var symEncryptedSessionKeyPacket = new packet.SymEncryptedSessionKey(); - symEncryptedSessionKeyPacket.sessionKey = sessionKey; - symEncryptedSessionKeyPacket.sessionKeyAlgorithm = symAlgo; - symEncryptedSessionKeyPacket.encrypt(password); - delete symEncryptedSessionKeyPacket.sessionKey; // delete plaintext session key after encryption - packetlist.push(symEncryptedSessionKeyPacket); - }); - } + if (passwords) { - return new Message(packetlist); + 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, symAlgo, password) { + + var symEncryptedSessionKeyPacket = new packet.SymEncryptedSessionKey(); + symEncryptedSessionKeyPacket.sessionKey = sessionKey; + symEncryptedSessionKeyPacket.sessionKeyAlgorithm = symAlgo; + await symEncryptedSessionKeyPacket.encrypt(password); + + if (config.password_collision_check) { + var results = await Promise.all(passwords.map(pwd => testDecrypt(symEncryptedSessionKeyPacket, pwd))); + if (results.reduce(sum) !== 1) { + return encryptPassword(sessionKey, symAlgo, password); + } + } + + delete symEncryptedSessionKeyPacket.sessionKey; // delete plaintext session key after encryption + return symEncryptedSessionKeyPacket; + }; + + results = await Promise.all(passwords.map(pwd => encryptPassword(sessionKey, symAlgo, pwd))); + packetlist.concat(results); + } + }).then(() => { + return new Message(packetlist); + }); } /** @@ -312,7 +366,7 @@ export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords) { * @param {Signature} signature (optional) any existing detached signature to add to the message * @return {module:message~Message} new message with signed content */ -Message.prototype.sign = function(privateKeys=[], signature=null) { +Message.prototype.sign = async function(privateKeys=[], signature=null) { var packetlist = new packet.List(); @@ -321,61 +375,67 @@ Message.prototype.sign = function(privateKeys=[], signature=null) { throw new Error('No literal data packet to sign.'); } + var i; var literalFormat = enums.write(enums.literal, literalDataPacket.format); var signatureType = literalFormat === enums.literal.binary ? - enums.signature.binary : enums.signature.text; - var i, signingKeyPacket, existingSigPacketlist, onePassSig; + enums.signature.binary : enums.signature.text; if (signature) { - existingSigPacketlist = signature.packets.filterByTag(enums.packet.signature); - if (existingSigPacketlist.length) { - for (i = existingSigPacketlist.length - 1; i >= 0; i--) { - var sigPacket = existingSigPacketlist[i]; - onePassSig = new packet.OnePassSignature(); - onePassSig.type = signatureType; - onePassSig.hashAlgorithm = config.prefer_hash_algorithm; - onePassSig.publicKeyAlgorithm = sigPacket.publicKeyAlgorithm; - onePassSig.signingKeyId = sigPacket.issuerKeyId; - if (!privateKeys.length && i === 0) { - onePassSig.flags = 1; - } - packetlist.push(onePassSig); + var existingSigPacketlist = signature.packets.filterByTag(enums.packet.signature); + for (i = existingSigPacketlist.length - 1; i >= 0; i--) { + var signaturePacket = existingSigPacketlist[i]; + var onePassSig = new packet.OnePassSignature(); + onePassSig.type = signatureType; + onePassSig.hashAlgorithm = signaturePacket.hashAlgorithm; + onePassSig.publicKeyAlgorithm = signaturePacket.publicKeyAlgorithm; + onePassSig.signingKeyId = signaturePacket.issuerKeyId; + if (!privateKeys.length && i === 0) { + onePassSig.flags = 1; } + packetlist.push(onePassSig); } } - for (i = 0; i < privateKeys.length; i++) { - if (privateKeys[i].isPublic()) { + + await Promise.all(Array.from(privateKeys).reverse().map(async function (privateKey, i) { + if (privateKey.isPublic()) { throw new Error('Need private key for signing'); } + await privateKey.verifyPrimaryUser(); + var signingKeyPacket = privateKey.getSigningKeyPacket(); + if (!signingKeyPacket) { + throw new Error('Could not find valid key packet for signing in key ' + + privateKey.primaryKey.getKeyId().toHex()); + } onePassSig = new packet.OnePassSignature(); onePassSig.type = signatureType; - //TODO get preferred hashg algo from key signature - onePassSig.hashAlgorithm = config.prefer_hash_algorithm; - signingKeyPacket = privateKeys[i].getSigningKeyPacket(); - if (!signingKeyPacket) { - throw new Error('Could not find valid key packet for signing in key ' + privateKeys[i].primaryKey.getKeyId().toHex()); - } + //TODO get preferred hash algo from key signature + onePassSig.hashAlgorithm = getPreferredHashAlgo(privateKey); onePassSig.publicKeyAlgorithm = signingKeyPacket.algorithm; onePassSig.signingKeyId = signingKeyPacket.getKeyId(); if (i === privateKeys.length - 1) { onePassSig.flags = 1; } - packetlist.push(onePassSig); - } + return onePassSig; + })).then(onePassSignatureList => { + onePassSignatureList.forEach(onePassSig => packetlist.push(onePassSig)); + }); packetlist.push(literalDataPacket); - for (i = privateKeys.length - 1; i >= 0; i--) { + await Promise.all(privateKeys.map(async function(privateKey) { var signaturePacket = new packet.Signature(); - signaturePacket.signatureType = signatureType; - signaturePacket.hashAlgorithm = config.prefer_hash_algorithm; - signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; + var signingKeyPacket = privateKey.getSigningKeyPacket(); if (!signingKeyPacket.isDecrypted) { throw new Error('Private key is not decrypted.'); } - signaturePacket.sign(signingKeyPacket, literalDataPacket); - packetlist.push(signaturePacket); - } + signaturePacket.signatureType = signatureType; + signaturePacket.hashAlgorithm = getPreferredHashAlgo(privateKey); + signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; + await signaturePacket.sign(signingKeyPacket, literalDataPacket); + return signaturePacket; + })).then(signatureList => { + signatureList.forEach(signaturePacket => packetlist.push(signaturePacket)); + }); if (signature) { packetlist.concat(existingSigPacketlist); @@ -390,7 +450,7 @@ Message.prototype.sign = function(privateKeys=[], signature=null) { * @param {Signature} signature (optional) any existing detached signature * @return {module:signature~Signature} new detached signature of message content */ -Message.prototype.signDetached = function(privateKeys=[], signature=null) { +Message.prototype.signDetached = async function(privateKeys=[], signature=null) { var packetlist = new packet.List(); @@ -401,26 +461,30 @@ Message.prototype.signDetached = function(privateKeys=[], signature=null) { var literalFormat = enums.write(enums.literal, literalDataPacket.format); var signatureType = literalFormat === enums.literal.binary ? - enums.signature.binary : enums.signature.text; + enums.signature.binary : enums.signature.text; - for (var i = 0; i < privateKeys.length; i++) { - var signingKeyPacket = privateKeys[i].getSigningKeyPacket(); + await Promise.all(privateKeys.map(async function(privateKey) { var signaturePacket = new packet.Signature(); - signaturePacket.signatureType = signatureType; - signaturePacket.hashAlgorithm = config.prefer_hash_algorithm; - signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; + await privateKey.verifyPrimaryUser(); + var signingKeyPacket = privateKey.getSigningKeyPacket(); if (!signingKeyPacket.isDecrypted) { throw new Error('Private key is not decrypted.'); } - signaturePacket.sign(signingKeyPacket, literalDataPacket); - packetlist.push(signaturePacket); - } + signaturePacket.signatureType = signatureType; + signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; + signaturePacket.hashAlgorithm = getPreferredHashAlgo(privateKey); + await signaturePacket.sign(signingKeyPacket, literalDataPacket); + return signaturePacket; + })).then(signatureList => { + signatureList.forEach(signaturePacket => packetlist.push(signaturePacket)); + }); + if (signature) { var existingSigPacketlist = signature.packets.filterByTag(enums.packet.signature); packetlist.concat(existingSigPacketlist); } - return new sigModule.Signature(packetlist); + return new Signature(packetlist); }; @@ -462,34 +526,30 @@ Message.prototype.verifyDetached = function(signature, keys) { * @param {Array} keys array of keys to verify signatures * @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature */ -function createVerificationObjects(signatureList, literalDataList, keys) { - var result = []; - for (var i = 0; i < signatureList.length; i++) { +async function createVerificationObjects(signatureList, literalDataList, keys) { + return Promise.all(signatureList.map(async function(signature) { var keyPacket = null; - for (var j = 0; j < keys.length; j++) { - keyPacket = keys[j].getSigningKeyPacket(signatureList[i].issuerKeyId, config.verify_expired_keys); - if (keyPacket) { - break; + await Promise.all(keys.map(async function(key) { + await key.verifyPrimaryUser(); + // Look for the unique key packet that matches issuerKeyId of signature + var result = key.getSigningKeyPacket(signature.issuerKeyId, config.verify_expired_keys); + if (result) { + keyPacket = result; } - } + })); - var verifiedSig = {}; - if (keyPacket) { - //found a key packet that matches keyId of signature - verifiedSig.keyid = signatureList[i].issuerKeyId; - verifiedSig.valid = signatureList[i].verify(keyPacket, literalDataList[0]); - } else { - verifiedSig.keyid = signatureList[i].issuerKeyId; - verifiedSig.valid = null; - } + // Look for the unique key packet that matches issuerKeyId of signature + var verifiedSig = { + keyid: signature.issuerKeyId, + valid: keyPacket ? await signature.verify(keyPacket, literalDataList[0]) : null + }; var packetlist = new packet.List(); - packetlist.push(signatureList[i]); - verifiedSig.signature = new sigModule.Signature(packetlist); + packetlist.push(signature); + verifiedSig.signature = new Signature(packetlist); - result.push(verifiedSig); - } - return result; + return verifiedSig; + })); } /** diff --git a/src/openpgp.js b/src/openpgp.js index ba4d27af..fe653f4b 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -21,6 +21,8 @@ * @requires key * @requires config * @requires util + * @requires polyfills + * @requires worker/async_proxy * @module openpgp */ @@ -32,15 +34,17 @@ 'use strict'; -import * as messageLib from './message.js'; -import * as cleartext from './cleartext.js'; -import * as key from './key.js'; -import config from './config/config.js'; +import * as messageLib from './message'; +import { CleartextMessage } from './cleartext'; +import { generate, reformat } from './key'; +import config from './config/config'; import util from './util'; -import AsyncProxy from './worker/async_proxy.js'; -import es6Promise from 'es6-promise'; -es6Promise.polyfill(); // load ES6 Promises polyfill +import AsyncProxy from './worker/async_proxy'; +// Old browser polyfills +if (typeof window !== 'undefined') { + require('./polyfills'); +} ////////////////////////// // // @@ -57,7 +61,7 @@ let asyncProxy; // instance of the asyncproxy * @param {Object} worker alternative to path parameter: web worker initialized with 'openpgp.worker.js' */ export function initWorker({ path='openpgp.worker.js', worker } = {}) { - if (worker || typeof window !== 'undefined' && window.Worker) { + if (worker || (typeof window !== 'undefined' && window.Worker)) { asyncProxy = new AsyncProxy({ path, worker, config }); return true; } @@ -87,28 +91,31 @@ export function destroyWorker() { /** - * Generates a new OpenPGP key pair. Currently only supports RSA keys. Primary and subkey will be of same type. + * 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} numBits (optional) number of bits for the key creation. (should be 2048 or 4096) + * @param {Number} numBits (optional) number of bits for RSA keys: 2048 or 4096. + * @param {String} curve (optional) elliptic curve for ECC keys: curve25519, p256, p384, p521, or secp256k1 * @param {Boolean} unlocked (optional) If the returned secret part of the generated key is unlocked * @param {Number} keyExpirationTime (optional) The number of seconds after the key creation time that the key expires * @return {Promise} The generated key object in the form: * { key:Key, privateKeyArmored:String, publicKeyArmored:String } * @static */ -export function generateKey({ userIds=[], passphrase, numBits=2048, unlocked=false, keyExpirationTime=0 } = {}) { - const options = formatUserIds({ userIds, passphrase, numBits, unlocked, keyExpirationTime }); + +export function generateKey({ userIds=[], passphrase, numBits=2048, unlocked=false, keyExpirationTime=0, curve="" } = {}) { + userIds = formatUserIds(userIds); + const options = {userIds, passphrase, numBits, unlocked, keyExpirationTime, curve}; if (!util.getWebCryptoAll() && asyncProxy) { // use web worker if web crypto apis are not supported return asyncProxy.delegate('generateKey', options); } - return key.generate(options).then(newKey => ({ + return generate(options).then(key => ({ - key: newKey, - privateKeyArmored: newKey.armor(), - publicKeyArmored: newKey.toPublic().armor() + key: key, + privateKeyArmored: key.armor(), + publicKeyArmored: key.toPublic().armor() })).catch(onError.bind(null, 'Error generating keypair')); } @@ -124,17 +131,19 @@ export function generateKey({ userIds=[], passphrase, numBits=2048, unlocked=fal * @static */ export function reformatKey({ privateKey, userIds=[], passphrase="", unlocked=false, keyExpirationTime=0 } = {}) { - const options = formatUserIds({ privateKey, userIds, passphrase, unlocked, keyExpirationTime }); + userIds = formatUserIds(userIds); + + const options = {privateKey, userIds, passphrase, unlocked, keyExpirationTime}; if (asyncProxy) { return asyncProxy.delegate('reformatKey', options); } - return key.reformat(options).then(newKey => ({ + return reformat(options).then(key => ({ - key: newKey, - privateKeyArmored: newKey.armor(), - publicKeyArmored: newKey.toPublic().armor() + key: key, + privateKeyArmored: key.armor(), + publicKeyArmored: key.toPublic().armor() })).catch(onError.bind(null, 'Error reformatting keypair')); } @@ -150,16 +159,15 @@ export function decryptKey({ privateKey, passphrase }) { return asyncProxy.delegate('decryptKey', { privateKey, passphrase }); } - return execute(() => { + return Promise.resolve().then(async function() { + + await privateKey.decrypt(passphrase); - if (!privateKey.decrypt(passphrase)) { - throw new Error('Invalid passphrase'); - } return { key: privateKey }; - }, 'Error decrypting private key'); + }).catch(onError.bind(null, 'Error decrypting private key')); } @@ -195,7 +203,7 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, sessionKey, return asyncProxy.delegate('encrypt', { data, publicKeys, privateKeys, passwords, sessionKey, filename, armor, detached, signature, returnSessionKey }); } var result = {}; - return Promise.resolve().then(() => { + return Promise.resolve().then(async function() { let message = createMessage(data, filename); if (!privateKeys) { @@ -203,14 +211,14 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, sessionKey, } if (privateKeys.length || signature) { // sign the message only if private keys or signature is specified if (detached) { - var detachedSignature = message.signDetached(privateKeys, signature); + var detachedSignature = await message.signDetached(privateKeys, signature); if (armor) { result.signature = detachedSignature.armor(); } else { result.signature = detachedSignature; } } else { - message = message.sign(privateKeys, signature); + message = await message.sign(privateKeys, signature); } } return message.encrypt(publicKeys, passwords, sessionKey); @@ -225,6 +233,7 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, sessionKey, result.sessionKey = encrypted.sessionKey; } return result; + }).catch(onError.bind(null, 'Error encrypting message')); } @@ -249,7 +258,7 @@ export function decrypt({ message, privateKey, publicKeys, sessionKey, password, return asyncProxy.delegate('decrypt', { message, privateKey, publicKeys, sessionKey, password, format, signature }); } - return message.decrypt(privateKey, sessionKey, password).then(message => { + return message.decrypt(privateKey, sessionKey, password).then(async function(message) { const result = parseMessage(message, format); @@ -258,9 +267,9 @@ export function decrypt({ message, privateKey, publicKeys, sessionKey, password, } if (signature) { //detached signature - result.signatures = message.verifyDetached(signature, publicKeys); + result.signatures = await message.verifyDetached(signature, publicKeys); } else { - result.signatures = message.verify(publicKeys); + result.signatures = await message.verify(publicKeys); } return result; @@ -296,34 +305,33 @@ export function sign({ data, privateKeys, armor=true, detached=false}) { } var result = {}; - return execute(() => { + return Promise.resolve().then(async function() { var message; if (util.isString(data)) { - message = new cleartext.CleartextMessage(data); + message = new CleartextMessage(data); } else { message = messageLib.fromBinary(data); } if (detached) { - var signature = message.signDetached(privateKeys); + var signature = await message.signDetached(privateKeys); if (armor) { result.signature = signature.armor(); } else { result.signature = signature; } } else { - message = message.sign(privateKeys); + message = await message.sign(privateKeys); if (armor) { result.data = message.armor(); } else { result.message = message; } } - return result; - }, 'Error signing cleartext message'); + }).catch(onError.bind(null, 'Error signing cleartext message')); } /** @@ -332,7 +340,7 @@ export function sign({ data, privateKeys, armor=true, detached=false}) { * @param {CleartextMessage} message cleartext message object with signatures * @param {Signature} signature (optional) detached signature for verification * @return {Promise} cleartext with status of verified signatures in the form of: - * { data:String, signatures: [{ keyid:String, valid:Boolean }] } + * { data:String, signatures: [{ keyid:String, valid:Boolean }] } * @static */ export function verify({ message, publicKeys, signature=null }) { @@ -343,22 +351,24 @@ export function verify({ message, publicKeys, signature=null }) { return asyncProxy.delegate('verify', { message, publicKeys, signature }); } - var result = {}; - return execute(() => { - if (cleartext.CleartextMessage.prototype.isPrototypeOf(message)) { + return Promise.resolve().then(async function() { + + var result = {}; + if (CleartextMessage.prototype.isPrototypeOf(message)) { result.data = message.getText(); } else { result.data = message.getLiteralData(); } + if (signature) { //detached signature - result.signatures = message.verifyDetached(signature, publicKeys); + result.signatures = await message.verifyDetached(signature, publicKeys); } else { - result.signatures = message.verify(publicKeys); + result.signatures = await message.verify(publicKeys); } return result; - }, 'Error verifying cleartext signed message'); + }).catch(onError.bind(null, 'Error verifying cleartext signed message')); } @@ -386,11 +396,11 @@ export function encryptSessionKey({ data, algorithm, publicKeys, passwords }) { return asyncProxy.delegate('encryptSessionKey', { data, algorithm, publicKeys, passwords }); } - return execute(() => ({ + return Promise.resolve().then(async function() { - message: messageLib.encryptSessionKey(data, algorithm, publicKeys, passwords) + return { message: await messageLib.encryptSessionKey(data, algorithm, publicKeys, passwords) }; - }), 'Error encrypting session key'); + }).catch(onError.bind(null, 'Error encrypting session key')); } /** @@ -399,19 +409,23 @@ export function encryptSessionKey({ data, algorithm, publicKeys, passwords }) { * @param {Message} message a message object containing the encrypted session key packets * @param {Key} privateKey (optional) private key with decrypted secret key data * @param {String} password (optional) a single password to decrypt the session key - * @return {Promise} decrypted session key and algorithm in object form: + * @return {Promise} Array of decrypted session key, algorithm pairs in form: * { data:Uint8Array, algorithm:String } * or 'undefined' if no key packets found * @static */ -export function decryptSessionKey({ message, privateKey, password }) { +export function decryptSessionKeys({ message, privateKey, password }) { checkMessage(message); if (asyncProxy) { // use web worker if available - return asyncProxy.delegate('decryptSessionKey', { message, privateKey, password }); + return asyncProxy.delegate('decryptSessionKeys', { message, privateKey, password }); } - return execute(() => message.decryptSessionKey(privateKey, password), 'Error decrypting session key'); + return Promise.resolve().then(async function() { + + return message.decryptSessionKeys(privateKey, password); + + }).catch(onError.bind(null, 'Error decrypting session keys')); } @@ -446,7 +460,7 @@ function checkMessage(message) { } } function checkCleartextOrMessage(message) { - if (!cleartext.CleartextMessage.prototype.isPrototypeOf(message) && !messageLib.Message.prototype.isPrototypeOf(message)) { + if (!CleartextMessage.prototype.isPrototypeOf(message) && !messageLib.Message.prototype.isPrototypeOf(message)) { throw new Error('Parameter [message] needs to be of type Message or CleartextMessage'); } } @@ -454,12 +468,12 @@ function checkCleartextOrMessage(message) { /** * Format user ids for internal use. */ -function formatUserIds(options) { - if (!options.userIds) { - return options; +function formatUserIds(userIds) { + if (!userIds) { + return userIds; } - options.userIds = toArray(options.userIds); // normalize to array - options.userIds = options.userIds.map(id => { + userIds = toArray(userIds); // normalize to array + userIds = userIds.map(id => { if (util.isString(id) && !util.isUserId(id)) { throw new Error('Invalid user id format'); } @@ -478,7 +492,7 @@ function formatUserIds(options) { } return id.name + '<' + id.email + '>'; }); - return options; + return userIds; } /** @@ -533,20 +547,6 @@ function parseMessage(message, format) { } } -/** - * Command pattern that wraps synchronous code into a promise. - * @param {function} cmd The synchronous function with a return value - * to be wrapped in a promise - * @param {String} message A human readable error Message - * @return {Promise} The promise wrapped around cmd - */ -function execute(cmd, message) { - // wrap the sync cmd in a promise - const promise = new Promise(resolve => resolve(cmd())); - // handler error globally - return promise.catch(onError.bind(null, message)); -} - /** * Global error handler that logs the stack trace and rethrows a high lvl error message. * @param {String} message A human readable high level error Message diff --git a/src/packet/clone.js b/src/packet/clone.js index 1d4484cb..8c7bff7d 100644 --- a/src/packet/clone.js +++ b/src/packet/clone.js @@ -23,12 +23,13 @@ 'use strict'; -import * as key from '../key.js'; -import * as message from '../message.js'; -import * as cleartext from '../cleartext.js'; -import * as signature from '../signature.js' -import Packetlist from './packetlist.js'; -import type_keyid from '../type/keyid.js'; +import { Key } from '../key'; +import { Message } from '../message'; +import { CleartextMessage } from '../cleartext'; +import { Signature } from '../signature' +import Packetlist from './packetlist'; +import type_keyid from '../type/keyid'; +import util from '../util'; ////////////////////////////// @@ -58,13 +59,13 @@ export function clonePackets(options) { } if (options.message) { //could be either a Message or CleartextMessage object - if (options.message instanceof message.Message) { + if (options.message instanceof Message) { options.message = options.message.packets; - } else if (options.message instanceof cleartext.CleartextMessage) { + } else if (options.message instanceof CleartextMessage) { options.message.signature = options.message.signature.packets; } } - if (options.signature && (options.signature instanceof signature.Signature)) { + if (options.signature && (options.signature instanceof Signature)) { options.signature = options.signature.packets; } if (options.signatures) { @@ -120,31 +121,31 @@ export function parseClonedPackets(options, method) { function packetlistCloneToKey(clone) { const packetlist = Packetlist.fromStructuredClone(clone); - return new key.Key(packetlist); + return new Key(packetlist); } function packetlistCloneToMessage(clone) { const packetlist = Packetlist.fromStructuredClone(clone); - return new message.Message(packetlist); + return new Message(packetlist); } function packetlistCloneToCleartextMessage(clone) { var packetlist = Packetlist.fromStructuredClone(clone.signature); - return new cleartext.CleartextMessage(clone.text, new signature.Signature(packetlist)); + return new CleartextMessage(clone.text, new Signature(packetlist)); } //verification objects function packetlistCloneToSignatures(clone) { clone.keyid = type_keyid.fromClone(clone.keyid); - clone.signature = new signature.Signature(clone.signature); + clone.signature = new Signature(clone.signature); return clone; } function packetlistCloneToSignature(clone) { - if (typeof clone === "string") { + if (util.isString(clone)) { //signature is armored return clone; } var packetlist = Packetlist.fromStructuredClone(clone); - return new signature.Signature(packetlist); + return new Signature(packetlist); } diff --git a/src/packet/compressed.js b/src/packet/compressed.js index 619a87da..86e9e9d0 100644 --- a/src/packet/compressed.js +++ b/src/packet/compressed.js @@ -79,7 +79,6 @@ Compressed.prototype.read = function (bytes) { }; - /** * Return the compressed packet. * @return {String} binary compressed packet diff --git a/src/packet/packetlist.js b/src/packet/packetlist.js index 01d5c0cd..77ba0baf 100644 --- a/src/packet/packetlist.js +++ b/src/packet/packetlist.js @@ -1,3 +1,4 @@ +/* eslint-disable callback-return */ /** * This class represents a list of openpgp packets. * Take care when iterating over it - the packets themselves @@ -45,7 +46,10 @@ Packetlist.prototype.read = function (bytes) { pushed = true; packet.read(parsed.packet); } catch(e) { - if (!config.tolerant || parsed.tag == enums.packet.symmetricallyEncrypted || parsed.tag == enums.packet.literal || parsed.tag == enums.packet.compressed) { + if (!config.tolerant || + parsed.tag === enums.packet.symmetricallyEncrypted || + parsed.tag === enums.packet.literal || + parsed.tag === enums.packet.compressed) { throw e; } if (pushed) { @@ -127,7 +131,7 @@ Packetlist.prototype.filterByTag = function () { var filtered = new Packetlist(); var that = this; - function handle(packetType) {return that[i].tag === packetType;} + function handle(packetType) { return that[i].tag === packetType; } for (var i = 0; i < this.length; i++) { if (args.some(handle)) { filtered.push(this[i]); @@ -142,10 +146,38 @@ Packetlist.prototype.filterByTag = function () { */ Packetlist.prototype.forEach = function (callback) { for (var i = 0; i < this.length; i++) { - callback(this[i]); + callback(this[i], i, this); } }; +/** +* Returns an array containing return values of callback +* on each element +*/ +Packetlist.prototype.map = function (callback) { + var packetArray = []; + + for (var i = 0; i < this.length; i++) { + packetArray.push(callback(this[i], i, this)); + } + + return packetArray; +}; + +/** +* Executes the callback function once for each element +* until it finds one where callback returns a truthy value +*/ +Packetlist.prototype.some = async function (callback) { + for (var i = 0; i < this.length; i++) { + // eslint-disable-next-line no-await-in-loop + if (await callback(this[i], i, this)) { + return true; + } + } + return false; +}; + /** * Traverses packet tree and returns first matching packet * @param {module:enums.packet} type The packet type @@ -177,7 +209,7 @@ Packetlist.prototype.indexOfTag = function () { var tagIndex = []; var that = this; - function handle(packetType) {return that[i].tag === packetType;} + function handle(packetType) { return that[i].tag === packetType; } for (var i = 0; i < this.length; i++) { if (args.some(handle)) { tagIndex.push(i); @@ -228,4 +260,4 @@ Packetlist.fromStructuredClone = function(packetlistClone) { } } return packetlist; -}; \ No newline at end of file +}; diff --git a/src/packet/public_key.js b/src/packet/public_key.js index 6542998b..a01b41be 100644 --- a/src/packet/public_key.js +++ b/src/packet/public_key.js @@ -24,19 +24,17 @@ * major versions. Consequently, this section is complex. * @requires crypto * @requires enums - * @requires type/keyid - * @requires type/mpi * @requires util + * @requires type/keyid * @module packet/public_key */ 'use strict'; -import util from '../util.js'; -import type_mpi from '../type/mpi.js'; -import type_keyid from '../type/keyid.js'; -import enums from '../enums.js'; import crypto from '../crypto'; +import enums from '../enums'; +import util from '../util'; +import type_keyid from '../type/keyid'; /** * @constructor @@ -47,12 +45,8 @@ export default function PublicKey() { /** Key creation date. * @type {Date} */ this.created = new Date(); - /** A list of multiprecision integers - * @type {module:type/mpi} */ - this.mpi = []; - /** Public key algorithm - * @type {module:enums.publicKey} */ - this.algorithm = 'rsa_sign'; + /* Algorithm specific params */ + this.params = []; // time in days (V3 only) this.expirationTimeV3 = 0; /** @@ -93,19 +87,15 @@ PublicKey.prototype.read = function (bytes) { // - A one-octet number denoting the public-key algorithm of this key. this.algorithm = enums.read(enums.publicKey, bytes[pos++]); - var mpicount = crypto.getPublicMpiCount(this.algorithm); - this.mpi = []; + var types = crypto.getPubKeyParamTypes(this.algorithm); + this.params = crypto.constructParams(types); - var bmpi = bytes.subarray(pos, bytes.length); + var b = bytes.subarray(pos, bytes.length); var p = 0; - for (var i = 0; i < mpicount && p < bmpi.length; i++) { - - this.mpi[i] = new type_mpi(); - - p += this.mpi[i].read(bmpi.subarray(p, bmpi.length)); - - if (p > bmpi.length) { + for (var i = 0; i < types.length && p < b.length; i++) { + p += this.params[i].read(b.subarray(p, b.length)); + if (p > b.length) { throw new Error('Error reading MPI @:' + p); } } @@ -138,10 +128,10 @@ PublicKey.prototype.write = function () { } arr.push(new Uint8Array([enums.write(enums.publicKey, this.algorithm)])); - var mpicount = crypto.getPublicMpiCount(this.algorithm); + var paramCount = crypto.getPubKeyParamTypes(this.algorithm).length; - for (var i = 0; i < mpicount; i++) { - arr.push(this.mpi[i].write()); + for (var i = 0; i < paramCount; i++) { + arr.push(this.params[i].write()); } return util.concatUint8Array(arr); @@ -174,7 +164,7 @@ PublicKey.prototype.getKeyId = function () { if (this.version === 4) { this.keyid.read(util.str2Uint8Array(util.hex2bin(this.getFingerprint()).substr(12, 8))); } else if (this.version === 3) { - var arr = this.mpi[0].write(); + var arr = this.params[0].write(); this.keyid.read(arr.subarray(arr.length - 8, arr.length)); } return this.keyid; @@ -193,9 +183,9 @@ PublicKey.prototype.getFingerprint = function () { toHash = this.writeOld(); this.fingerprint = util.Uint8Array2str(crypto.hash.sha1(toHash)); } else if (this.version === 3) { - var mpicount = crypto.getPublicMpiCount(this.algorithm); - for (var i = 0; i < mpicount; i++) { - toHash += this.mpi[i].toBytes(); + var paramCount = crypto.getPubKeyParamTypes(this.algorithm).length; + for (var i = 0; i < paramCount; i++) { + toHash += this.params[i].toBytes(); } this.fingerprint = util.Uint8Array2str(crypto.hash.md5(util.str2Uint8Array(toHash))); } @@ -208,15 +198,17 @@ PublicKey.prototype.getFingerprint = function () { * @return {int} Number of bits */ PublicKey.prototype.getBitSize = function () { - return this.mpi[0].byteLength() * 8; + return this.params[0].byteLength() * 8; }; /** * Fix custom types after cloning */ PublicKey.prototype.postCloneTypeFix = function() { - for (var i = 0; i < this.mpi.length; i++) { - this.mpi[i] = type_mpi.fromClone(this.mpi[i]); + const types = crypto.getPubKeyParamTypes(this.algorithm); + for (var 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); diff --git a/src/packet/public_key_encrypted_session_key.js b/src/packet/public_key_encrypted_session_key.js index fcdd71d0..2b4d5c58 100644 --- a/src/packet/public_key_encrypted_session_key.js +++ b/src/packet/public_key_encrypted_session_key.js @@ -31,6 +31,7 @@ * decrypt the message. * @requires crypto * @requires enums + * @requires type/ecdh_symkey * @requires type/keyid * @requires type/mpi * @requires util @@ -41,6 +42,7 @@ import type_keyid from '../type/keyid.js'; import util from '../util.js'; +import type_ecdh_symkey from '../type/ecdh_symkey.js'; import type_mpi from '../type/mpi.js'; import enums from '../enums.js'; import crypto from '../crypto'; @@ -53,10 +55,7 @@ export default function PublicKeyEncryptedSessionKey() { this.version = 3; this.publicKeyId = new type_keyid(); - this.publicKeyAlgorithm = 'rsa_encrypt'; - this.sessionKey = null; - this.sessionKeyAlgorithm = 'aes256'; /** @type {Array} */ this.encrypted = []; @@ -79,26 +78,11 @@ PublicKeyEncryptedSessionKey.prototype.read = function (bytes) { var i = 10; - var integerCount = (function(algo) { - switch (algo) { - case 'rsa_encrypt': - case 'rsa_encrypt_sign': - return 1; + var types = crypto.getEncSessionKeyParamTypes(this.publicKeyAlgorithm); + this.encrypted = crypto.constructParams(types); - case 'elgamal': - return 2; - - default: - throw new Error("Invalid algorithm."); - } - })(this.publicKeyAlgorithm); - - this.encrypted = []; - - for (var j = 0; j < integerCount; j++) { - var mpi = new type_mpi(); - i += mpi.read(bytes.subarray(i, bytes.length)); - this.encrypted.push(mpi); + for (var j = 0; j < types.length; j++) { + i += this.encrypted[j].read(bytes.subarray(i, bytes.length)); } }; @@ -118,7 +102,7 @@ PublicKeyEncryptedSessionKey.prototype.write = function () { return util.concatUint8Array(arr); }; -PublicKeyEncryptedSessionKey.prototype.encrypt = function (key) { +PublicKeyEncryptedSessionKey.prototype.encrypt = async function (key) { var data = String.fromCharCode( enums.write(enums.symmetric, this.sessionKeyAlgorithm)); @@ -126,15 +110,18 @@ PublicKeyEncryptedSessionKey.prototype.encrypt = function (key) { var checksum = util.calc_checksum(this.sessionKey); data += util.Uint8Array2str(util.writeNumber(checksum, 2)); - var mpi = new type_mpi(); - mpi.fromBytes(crypto.pkcs1.eme.encode( - data, - key.mpi[0].byteLength())); + var toEncrypt; + if (this.publicKeyAlgorithm === 'ecdh') { + toEncrypt = new type_mpi(crypto.pkcs5.encode(data)); + } else { + toEncrypt = new type_mpi(crypto.pkcs1.eme.encode(data, key.params[0].byteLength())); + } - this.encrypted = crypto.publicKeyEncrypt( + this.encrypted = await crypto.publicKeyEncrypt( this.publicKeyAlgorithm, - key.mpi, - mpi); + key.params, + toEncrypt, + key.fingerprint); }; /** @@ -142,18 +129,25 @@ PublicKeyEncryptedSessionKey.prototype.encrypt = function (key) { * packets (tag 1) * * @param {module:packet/secret_key} key - * Private key with secMPIs unlocked + * Private key with secret params unlocked * @return {String} The unencrypted session key */ -PublicKeyEncryptedSessionKey.prototype.decrypt = function (key) { - var result = crypto.publicKeyDecrypt( +PublicKeyEncryptedSessionKey.prototype.decrypt = async function (key) { + var result = (await crypto.publicKeyDecrypt( this.publicKeyAlgorithm, - key.mpi, - this.encrypted).toBytes(); + key.params, + this.encrypted, + key.fingerprint)).toBytes(); - var checksum = util.readNumber(util.str2Uint8Array(result.substr(result.length - 2))); - - var decoded = crypto.pkcs1.eme.decode(result); + var checksum; + var decoded; + if (this.publicKeyAlgorithm === 'ecdh') { + decoded = crypto.pkcs5.decode(result); + checksum = util.readNumber(util.str2Uint8Array(decoded.substr(decoded.length - 2))); + } else { + decoded = crypto.pkcs1.eme.decode(result); + checksum = util.readNumber(util.str2Uint8Array(result.substr(result.length - 2))); + } key = util.str2Uint8Array(decoded.substring(1, decoded.length - 2)); @@ -161,8 +155,7 @@ PublicKeyEncryptedSessionKey.prototype.decrypt = function (key) { throw new Error('Checksum mismatch'); } else { this.sessionKey = key; - this.sessionKeyAlgorithm = - enums.read(enums.symmetric, decoded.charCodeAt(0)); + this.sessionKeyAlgorithm = enums.read(enums.symmetric, decoded.charCodeAt(0)); } }; @@ -171,7 +164,8 @@ PublicKeyEncryptedSessionKey.prototype.decrypt = function (key) { */ PublicKeyEncryptedSessionKey.prototype.postCloneTypeFix = function() { this.publicKeyId = type_keyid.fromClone(this.publicKeyId); + var types = crypto.getEncSessionKeyParamTypes(this.publicKeyAlgorithm); for (var i = 0; i < this.encrypted.length; i++) { - this.encrypted[i] = type_mpi.fromClone(this.encrypted[i]); + this.encrypted[i] = types[i].fromClone(this.encrypted[i]); } }; diff --git a/src/packet/secret_key.js b/src/packet/secret_key.js index e4f88e41..af5eafce 100644 --- a/src/packet/secret_key.js +++ b/src/packet/secret_key.js @@ -25,7 +25,7 @@ * @requires crypto * @requires enums * @requires packet/public_key - * @requires type/mpi + * @requires type/keyid * @requires type/s2k * @requires util * @module packet/secret_key @@ -37,8 +37,8 @@ import publicKey from './public_key.js'; import enums from '../enums.js'; import util from '../util.js'; import crypto from '../crypto'; -import type_mpi from '../type/mpi.js'; import type_s2k from '../type/s2k.js'; +import type_keyid from '../type/keyid.js'; /** * @constructor @@ -76,38 +76,38 @@ function get_hash_fn(hash) { // Helper function -function parse_cleartext_mpi(hash_algorithm, cleartext, algorithm) { +function parse_cleartext_params(hash_algorithm, cleartext, algorithm) { var hashlen = get_hash_len(hash_algorithm), hashfn = get_hash_fn(hash_algorithm); var hashtext = util.Uint8Array2str(cleartext.subarray(cleartext.length - hashlen, cleartext.length)); cleartext = cleartext.subarray(0, cleartext.length - hashlen); - var hash = util.Uint8Array2str(hashfn(cleartext)); if (hash !== hashtext) { return new Error("Hash mismatch."); } - var mpis = crypto.getPrivateMpiCount(algorithm); + var types = crypto.getPrivKeyParamTypes(algorithm); + var params = crypto.constructParams(types); + var p = 0; - var j = 0; - var mpi = []; - - for (var i = 0; i < mpis && j < cleartext.length; i++) { - mpi[i] = new type_mpi(); - j += mpi[i].read(cleartext.subarray(j, cleartext.length)); + for (var 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); + } } - return mpi; + return params; } -function write_cleartext_mpi(hash_algorithm, algorithm, mpi) { +function write_cleartext_params(hash_algorithm, algorithm, params) { var arr = []; - var discard = crypto.getPublicMpiCount(algorithm); + var numPublicParams = crypto.getPubKeyParamTypes(algorithm).length; - for (var i = discard; i < mpi.length; i++) { - arr.push(mpi[i].write()); + for (var i = numPublicParams; i < params.length; i++) { + arr.push(params[i].write()); } var bytes = util.concatUint8Array(arr); @@ -143,11 +143,11 @@ SecretKey.prototype.read = function (bytes) { // - Plain or encrypted multiprecision integers comprising the secret // key data. These algorithm-specific fields are as described // below. - var parsedMPI = parse_cleartext_mpi('mod', bytes.subarray(1, bytes.length), this.algorithm); - if (parsedMPI instanceof Error) { - throw parsedMPI; + var privParams = parse_cleartext_params('mod', bytes.subarray(1, bytes.length), this.algorithm); + if (privParams instanceof Error) { + throw privParams; } - this.mpi = this.mpi.concat(parsedMPI); + this.params = this.params.concat(privParams); this.isDecrypted = true; } @@ -161,7 +161,7 @@ SecretKey.prototype.write = function () { if (!this.encrypted) { arr.push(new Uint8Array([0])); - arr.push(write_cleartext_mpi('mod', this.algorithm, this.mpi)); + arr.push(write_cleartext_params('mod', this.algorithm, this.params)); } else { arr.push(this.encrypted); } @@ -170,8 +170,6 @@ SecretKey.prototype.write = function () { }; - - /** Encrypt the payload. By default, we use aes256 and iterated, salted string * to key specifier. If the key is in a decrypted state (isDecrypted === true) * and the passphrase is empty or undefined, the key will be set as not encrypted. @@ -188,12 +186,12 @@ SecretKey.prototype.encrypt = function (passphrase) { var s2k = new type_s2k(), symmetric = 'aes256', - cleartext = write_cleartext_mpi('sha1', this.algorithm, this.mpi), + cleartext = write_cleartext_params('sha1', this.algorithm, this.params), key = produceEncryptionKey(s2k, passphrase, symmetric), blockLen = crypto.cipher[symmetric].blockSize, iv = crypto.random.getRandomBytes(blockLen); - var arr = [ new Uint8Array([254, enums.write(enums.symmetric, symmetric)]) ]; + var arr = [new Uint8Array([254, enums.write(enums.symmetric, symmetric)])]; arr.push(s2k.write()); arr.push(iv); arr.push(crypto.cfb.normalEncrypt(symmetric, key, cleartext, iv)); @@ -207,13 +205,13 @@ function produceEncryptionKey(s2k, passphrase, algorithm) { } /** - * Decrypts the private key MPIs which are needed to use the key. + * Decrypts the private key params which are needed to use the key. * @link module:packet/secret_key.isDecrypted should be * false otherwise a call to this function is not needed * * @param {String} str_passphrase The passphrase for this private key * as string - * @return {Boolean} True if the passphrase was correct or MPI already + * @return {Boolean} True if the passphrase was correct or param already * decrypted; false if not */ SecretKey.prototype.decrypt = function (passphrase) { @@ -263,32 +261,46 @@ SecretKey.prototype.decrypt = function (passphrase) { 'sha1' : 'mod'; - var parsedMPI = parse_cleartext_mpi(hash, cleartext, this.algorithm); - if (parsedMPI instanceof Error) { + var privParams = parse_cleartext_params(hash, cleartext, this.algorithm); + if (privParams instanceof Error) { return false; } - this.mpi = this.mpi.concat(parsedMPI); + this.params = this.params.concat(privParams); this.isDecrypted = true; this.encrypted = null; return true; }; -SecretKey.prototype.generate = function (bits) { - var self = this; +SecretKey.prototype.generate = function (bits, curve) { + var that = this; - return crypto.generateMpi(self.algorithm, bits).then(function(mpi) { - self.mpi = mpi; - self.isDecrypted = true; + return crypto.generateParams(that.algorithm, bits, curve).then(function(params) { + that.params = params; + that.isDecrypted = true; }); }; /** - * Clear private MPIs, return to initial state + * Clear private params, return to initial state */ -SecretKey.prototype.clearPrivateMPIs = function () { +SecretKey.prototype.clearPrivateParams = function () { if (!this.encrypted) { - throw new Error('If secret key is not encrypted, clearing private MPIs is irreversible.'); + throw new Error('If secret key is not encrypted, clearing private params is irreversible.'); } - this.mpi = this.mpi.slice(0, crypto.getPublicMpiCount(this.algorithm)); + this.params = this.params.slice(0, crypto.getPubKeyParamTypes(this.algorithm).length); this.isDecrypted = false; }; + +/** + * Fix custom types after cloning + */ + SecretKey.prototype.postCloneTypeFix = function() { + const types = crypto.getPubKeyParamTypes(this.algorithm).concat(crypto.getPrivKeyParamTypes(this.algorithm)); + for (var 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); + } +}; diff --git a/src/packet/signature.js b/src/packet/signature.js index 25e545c9..3826bf4a 100644 --- a/src/packet/signature.js +++ b/src/packet/signature.js @@ -97,6 +97,28 @@ export default function Signature() { Signature.prototype.read = function (bytes) { var i = 0; this.version = bytes[i++]; + + function subpackets(bytes) { + // Two-octet scalar octet count for following subpacket data. + var subpacket_length = util.readNumber( + bytes.subarray(0, 2)); + + var i = 2; + + // subpacket data set (zero or more subpackets) + while (i < 2 + subpacket_length) { + + var len = packet.readSimpleLength(bytes.subarray(i, bytes.length)); + i += len.offset; + + this.read_sub_packet(bytes.subarray(i, i + len.len)); + + i += len.len; + } + + return i; + } + // switch on version (3 and 4) switch (this.version) { case 3: @@ -133,27 +155,6 @@ Signature.prototype.read = function (bytes) { this.publicKeyAlgorithm = bytes[i++]; this.hashAlgorithm = bytes[i++]; - function subpackets(bytes) { - // Two-octet scalar octet count for following subpacket data. - var subpacket_length = util.readNumber( - bytes.subarray(0, 2)); - - var i = 2; - - // subpacket data set (zero or more subpackets) - while (i < 2 + subpacket_length) { - - var len = packet.readSimpleLength(bytes.subarray(i, bytes.length)); - i += len.offset; - - this.read_sub_packet(bytes.subarray(i, i + len.len)); - - i += len.len; - } - - return i; - } - // hashed subpackets i += subpackets.call(this, bytes.subarray(i, bytes.length), true); @@ -210,7 +211,7 @@ Signature.prototype.write = function () { * @param {module:packet/secret_key} key private key used to sign the message. * @param {Object} data Contains packets to be signed. */ -Signature.prototype.sign = function (key, data) { +Signature.prototype.sign = async function (key, data) { var signatureType = enums.write(enums.signature, this.signatureType), publicKeyAlgorithm = enums.write(enums.publicKey, this.publicKeyAlgorithm), hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm); @@ -242,8 +243,8 @@ Signature.prototype.sign = function (key, data) { this.signedHashValue = hash.subarray(0, 2); - this.signature = crypto.signature.sign(hashAlgorithm, - publicKeyAlgorithm, key.mpi, toHash); + this.signature = await crypto.signature.sign(hashAlgorithm, + publicKeyAlgorithm, key.params, toHash); }; /** @@ -614,7 +615,7 @@ Signature.prototype.calculateTrailer = function () { * module:packet/secret_subkey|module:packet/secret_key} key the public key to verify the signature * @return {boolean} True if message is verified, else false. */ -Signature.prototype.verify = function (key, data) { +Signature.prototype.verify = async function (key, data) { var signatureType = enums.write(enums.signature, this.signatureType), publicKeyAlgorithm = enums.write(enums.publicKey, this.publicKeyAlgorithm), hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm); @@ -629,22 +630,27 @@ Signature.prototype.verify = function (key, data) { if (publicKeyAlgorithm > 0 && publicKeyAlgorithm < 4) { mpicount = 1; } - // Algorithm-Specific Fields for DSA signatures: + // Algorithm-Specific Fields for DSA, ECDSA, and EdDSA signatures: // - MPI of DSA value r. // - MPI of DSA value s. - else if (publicKeyAlgorithm === 17) { + else if (publicKeyAlgorithm === enums.publicKey.dsa || + publicKeyAlgorithm === enums.publicKey.ecdsa || + publicKeyAlgorithm === enums.publicKey.eddsa) { mpicount = 2; } + // EdDSA signature parameters are encoded in litte-endian format + // https://tools.ietf.org/html/rfc8032#section-5.1.2 + var endian = publicKeyAlgorithm === enums.publicKey.eddsa ? 'le' : 'be'; var mpi = [], i = 0; for (var j = 0; j < mpicount; j++) { mpi[j] = new type_mpi(); - i += mpi[j].read(this.signature.subarray(i, this.signature.length)); + i += mpi[j].read(this.signature.subarray(i, this.signature.length), endian); } - this.verified = crypto.signature.verify(publicKeyAlgorithm, - hashAlgorithm, mpi, key.mpi, + this.verified = await crypto.signature.verify(publicKeyAlgorithm, + hashAlgorithm, mpi, key.params, util.concatUint8Array([bytes, this.signatureData, trailer])); return this.verified; diff --git a/src/packet/sym_encrypted_aead_protected.js b/src/packet/sym_encrypted_aead_protected.js index 41f44aec..36284b13 100644 --- a/src/packet/sym_encrypted_aead_protected.js +++ b/src/packet/sym_encrypted_aead_protected.js @@ -37,7 +37,7 @@ export default function SymEncryptedAEADProtected() { this.version = VERSION; this.iv = null; this.encrypted = null; - this.packets = null; + this.packets = null; } /** @@ -85,4 +85,4 @@ SymEncryptedAEADProtected.prototype.encrypt = function (sessionKeyAlgorithm, key return crypto.gcm.encrypt(sessionKeyAlgorithm, this.packets.write(), key, this.iv).then(encrypted => { this.encrypted = encrypted; }); -}; \ No newline at end of file +}; diff --git a/src/packet/sym_encrypted_integrity_protected.js b/src/packet/sym_encrypted_integrity_protected.js index 24fbd842..605f4dd2 100644 --- a/src/packet/sym_encrypted_integrity_protected.js +++ b/src/packet/sym_encrypted_integrity_protected.js @@ -34,10 +34,11 @@ 'use strict'; +import asmCrypto from 'asmcrypto-lite'; import util from '../util.js'; import crypto from '../crypto'; import enums from '../enums.js'; -import asmCrypto from 'asmcrypto-lite'; + const nodeCrypto = util.getNodeCrypto(); const Buffer = util.getNodeBuffer(); @@ -132,7 +133,7 @@ SymEncryptedIntegrityProtected.prototype.decrypt = function (sessionKeyAlgorithm this.packets.read(decrypted.subarray(0, decrypted.length - 22)); } - return Promise.resolve(); + return true; }; @@ -176,4 +177,4 @@ function nodeDecrypt(algo, ct, key) { const decipherObj = new nodeCrypto.createDecipheriv('aes-' + algo.substr(3,3) + '-cfb', key, iv); const pt = decipherObj.update(ct); return new Uint8Array(pt); -} \ No newline at end of file +} diff --git a/src/packet/sym_encrypted_session_key.js b/src/packet/sym_encrypted_session_key.js index 045d24bb..f6be141f 100644 --- a/src/packet/sym_encrypted_session_key.js +++ b/src/packet/sym_encrypted_session_key.js @@ -122,9 +122,7 @@ SymEncryptedSessionKey.prototype.decrypt = function(passphrase) { var decrypted = crypto.cfb.normalDecrypt( algo, key, this.encrypted, null); - this.sessionKeyAlgorithm = enums.read(enums.symmetric, - decrypted[0]); - + this.sessionKeyAlgorithm = enums.read(enums.symmetric, decrypted[0]); this.sessionKey = decrypted.subarray(1,decrypted.length); } }; @@ -139,8 +137,7 @@ SymEncryptedSessionKey.prototype.encrypt = function(passphrase) { var length = crypto.cipher[algo].keySize; var key = this.s2k.produce_key(passphrase, length); - var algo_enum = new Uint8Array([ - enums.write(enums.symmetric, this.sessionKeyAlgorithm)]); + var algo_enum = new Uint8Array([enums.write(enums.symmetric, this.sessionKeyAlgorithm)]); var private_key; if(this.sessionKey === null) { @@ -148,8 +145,7 @@ SymEncryptedSessionKey.prototype.encrypt = function(passphrase) { } private_key = util.concatUint8Array([algo_enum, this.sessionKey]); - this.encrypted = crypto.cfb.normalEncrypt( - algo, key, private_key, null); + this.encrypted = crypto.cfb.normalEncrypt(algo, key, private_key, null); }; /** diff --git a/src/packet/symmetrically_encrypted.js b/src/packet/symmetrically_encrypted.js index 04a0b8a1..13f69280 100644 --- a/src/packet/symmetrically_encrypted.js +++ b/src/packet/symmetrically_encrypted.js @@ -42,7 +42,7 @@ export default function SymmetricallyEncrypted() { this.encrypted = null; /** Decrypted packets contained within. * @type {module:packet/packetlist} */ - this.packets = null; + this.packets = null; this.ignore_mdc_error = config.ignore_mdc_error; } diff --git a/src/polyfills.js b/src/polyfills.js new file mode 100644 index 00000000..3c46bb4d --- /dev/null +++ b/src/polyfills.js @@ -0,0 +1,29 @@ +/* eslint-disable import/no-extraneous-dependencies */ +// Old browser polyfills +// All are listed as dev dependencies because Node does not need them +// and for browser babel will take care of it + +if (typeof window.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.from === 'undefined') { + require('core-js/fn/array/from'); +} +if (typeof Promise === 'undefined') { + 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'); +} diff --git a/src/type/ecdh_symkey.js b/src/type/ecdh_symkey.js new file mode 100644 index 00000000..e49cd155 --- /dev/null +++ b/src/type/ecdh_symkey.js @@ -0,0 +1,69 @@ +// OpenPGP.js - An OpenPGP implementation in javascript +// Copyright (C) 2015-2016 Decentral +// +// 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 + +/** + * Encoded symmetric key for ECDH
+ *
+ * @requires util + * @module type/ecdh_symkey + */ + +'use strict'; + +import util from '../util'; + +module.exports = ECDHSymmetricKey; + +/** + * @constructor + */ +function ECDHSymmetricKey(data) { + if (typeof data === 'undefined') { + data = new Uint8Array([]); + } else if (util.isString(data)) { + data = util.str2Uint8Array(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 + * @return {Number} Number of read bytes + */ +ECDHSymmetricKey.prototype.read = function (input) { + if (input.length >= 1) + { + var 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'); +}; + +/** + * Write an ECDHSymmetricKey as an Uint8Array + * @return {Uint8Array} An array containing the value + */ +ECDHSymmetricKey.prototype.write = function () { + return util.concatUint8Array([new Uint8Array([this.data.length]), this.data]); +}; diff --git a/src/type/kdf_params.js b/src/type/kdf_params.js new file mode 100644 index 00000000..8a33a026 --- /dev/null +++ b/src/type/kdf_params.js @@ -0,0 +1,70 @@ +// OpenPGP.js - An OpenPGP implementation in javascript +// Copyright (C) 2015-2016 Decentral +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 3.0 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +/** + * Implementation of type KDF parameters RFC 6637
+ *
+ * @requires enums + * @module type/kdf_params + */ + +'use strict'; + +import enums from '../enums.js'; + +module.exports = KDFParams; + +/** + * @constructor + * @param {enums.hash} hash Hash algorithm + * @param {enums.symmetric} cipher Symmetric algorithm + */ +function KDFParams(data) { + if (data && data.length === 2) { + this.hash = data[0]; + this.cipher = data[1]; + } else { + this.hash = enums.hash.sha1; + this.cipher = enums.symmetric.aes128; + } +} + +/** + * Read KDFParams from an Uint8Array + * @param {Uint8Array} input Where to read the KDFParams from + * @return {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 + * @return {Uint8Array} Array with the KDFParams value + */ +KDFParams.prototype.write = function () { + return new Uint8Array([3, 1, this.hash, this.cipher]); +}; + +KDFParams.fromClone = function (clone) { + return new KDFParams(clone.hash, clone.cipher); +}; diff --git a/src/type/mpi.js b/src/type/mpi.js index 08b1b4c5..8c043966 100644 --- a/src/type/mpi.js +++ b/src/type/mpi.js @@ -36,25 +36,32 @@ 'use strict'; -import BigInteger from '../crypto/public_key/jsbn.js'; -import util from '../util.js'; +import BigInteger from '../crypto/public_key/jsbn'; +import util from '../util'; /** * @constructor */ -export default function MPI() { +export default function MPI(data) { /** An implementation dependent integer */ - this.data = null; + if (data instanceof BigInteger) { + this.fromBigInteger(data); + } else if (util.isString(data)) { + this.fromBytes(data); + } else { + this.data = null; + } } /** * Parsing function for a mpi ({@link http://tools.ietf.org/html/rfc4880#section3.2|RFC 4880 3.2}). * @param {String} input Payload of mpi data + * @param {String} endian Endianness of the payload; 'be' for big-endian and 'le' for little-endian * @return {Integer} Length of data read */ -MPI.prototype.read = function (bytes) { +MPI.prototype.read = function (bytes, endian='be') { - if(typeof bytes === 'string' || String.prototype.isPrototypeOf(bytes)) { + if(util.isString(bytes)) { bytes = util.str2Uint8Array(bytes); } @@ -71,8 +78,11 @@ MPI.prototype.read = function (bytes) { // TODO: Verification of this size method! This size calculation as // specified above is not applicable in JavaScript var bytelen = Math.ceil(bits / 8); - - var raw = util.Uint8Array2str(bytes.subarray(2, 2 + bytelen)); + var payload = bytes.subarray(2, 2 + bytelen); + if (endian === 'le') { + payload = new Uint8Array(payload).reverse(); + } + var raw = util.Uint8Array2str(payload); this.fromBytes(raw); return 2 + bytelen; diff --git a/src/type/oid.js b/src/type/oid.js new file mode 100644 index 00000000..be633321 --- /dev/null +++ b/src/type/oid.js @@ -0,0 +1,82 @@ +// OpenPGP.js - An OpenPGP implementation in javascript +// Copyright (C) 2015-2016 Decentral +// +// 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 + +/** + * Wrapper to an OID value
+ *
+ * An object identifier type from {@link https://tools.ietf.org/html/rfc6637#section-11|RFC6637, section 11}. + * @requires util + * @module type/oid + */ + +'use strict'; + +import util from '../util.js'; + +module.exports = OID; + +/** + * @constructor + */ +function OID(oid) { + if (typeof oid === 'undefined') { + oid = ''; + } else if (util.isArray(oid)) { + oid = util.bin2str(oid); + } else if (util.isUint8Array(oid)) { + oid = util.Uint8Array2str(oid); + } + this.oid = oid; +} + +/** + * Method to read an OID object + * @param {Uint8Array} input Where to read the OID from + * @return {Number} Number of read bytes + */ +OID.prototype.read = function (input) { + if (input.length >= 1) { + var length = input[0]; + if (input.length >= 1+length) { + this.oid = util.Uint8Array2str(input.subarray(1, 1+length)); + return 1+this.oid.length; + } + } + throw new Error('Invalid oid'); +}; + +/** + * Serialize an OID object + * @return {Uint8Array} Array with the serialized value the OID + */ +OID.prototype.write = function () { + return util.str2Uint8Array( + String.fromCharCode(this.oid.length)+this.oid); +}; + +/** + * Serialize an OID object as a hex string + * @return {string} String with the hex value of the OID + */ +OID.prototype.toHex = function() { + return util.hexstrdump(this.oid); +}; + +OID.fromClone = function (clone) { + var oid = new OID(clone.oid); + return oid; +}; diff --git a/src/type/s2k.js b/src/type/s2k.js index 6667cefa..8cc1c75a 100644 --- a/src/type/s2k.js +++ b/src/type/s2k.js @@ -156,15 +156,11 @@ S2K.prototype.produce_key = function (passphrase, numBytes) { util.concatUint8Array([prefix, s2k.salt, passphrase])); case 'iterated': - var isp = [], - count = s2k.get_count(), - data = util.concatUint8Array([s2k.salt,passphrase]); + var count = s2k.get_count(), + data = util.concatUint8Array([s2k.salt,passphrase]), + isp = new Array(Math.ceil(count / data.length)); - while (isp.length * data.length < count) { - isp.push(data); - } - - isp = util.concatUint8Array(isp); + isp = util.concatUint8Array(isp.fill(data)); if (isp.length > count) { isp = isp.subarray(0, count); diff --git a/src/util.js b/src/util.js index f2d8bdef..89afff54 100644 --- a/src/util.js +++ b/src/util.js @@ -43,7 +43,7 @@ export default { if (!this.isString(data)) { return false; } - const re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + const re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+([a-zA-Z]{2,}|xn--[a-zA-Z\-0-9]+)))$/; return re.test(data); }, @@ -169,6 +169,15 @@ export default { return str; }, + + hex2Uint8Array: function (hex) { + var result = new Uint8Array(hex.length/2); + for (var k=0; k>> 16; + if (t !== 0) { + x = t; + r += 16; + } + t = x >> 8; + if (t !== 0) { + x = t; + r += 8; + } + t = x >> 4; + if (t !== 0) { + x = t; + r += 4; + } + t = x >> 2; + if (t !== 0) { + x = t; + r += 2; + } + t = x >> 1; + if (t !== 0) { + x = t; + r += 1; + } + return r; + }, + + /** + * Convert a Uint8Array to an MPI array. + * @function module:util.Uint8Array2MPI + * @param {Uint8Array} bin An array of (binary) integers to convert + * @return {Array} MPI-formatted array + */ + Uint8Array2MPI: function (bin) { + var size = (bin.length - 1) * 8 + this.nbits(bin[0]); + return [(size & 0xFF00) >> 8, size & 0xFF].concat(Array.from(bin)); + }, + /** * Concat Uint8arrays * @function module:util.concatUint8Array @@ -486,22 +537,7 @@ export default { }, /** - * Wraps a generic synchronous function in an ES6 Promise. - * @param {Function} fn The function to be wrapped - * @return {Function} The function wrapped in a Promise - */ - promisify: function(fn) { - return function() { - var args = arguments; - return new Promise(function(resolve) { - var result = fn.apply(null, args); - resolve(result); - }); - }; - }, - - /** - * Converts an IE11 web crypro api result to a promise. + * Converts an IE11 web crypto api result to a promise. * This is required since IE11 implements an old version of the * Web Crypto specification that does not use promises. * @param {Object} cryptoOp The return value of an IE11 web cryptro api call diff --git a/src/worker/async_proxy.js b/src/worker/async_proxy.js index 6a780206..99d56554 100644 --- a/src/worker/async_proxy.js +++ b/src/worker/async_proxy.js @@ -90,7 +90,7 @@ AsyncProxy.prototype.onMessage = function(event) { */ AsyncProxy.prototype.seedRandom = function(size) { const buf = this.getRandomBuffer(size); - this.worker.postMessage({ event:'seed-random', buf }, util.getTransferables.call(util, buf)); + this.worker.postMessage({ event:'seed-random', buf }, util.getTransferables(buf)); }; /** @@ -125,7 +125,7 @@ AsyncProxy.prototype.delegate = function(method, options) { return new Promise((resolve, reject) => { // clone packets (for web worker structured cloning algorithm) - this.worker.postMessage({ id:id, event:method, options:packet.clone.clonePackets(options) }, util.getTransferables.call(util, options)); + this.worker.postMessage({ id:id, event:method, options:packet.clone.clonePackets(options) }, util.getTransferables(options)); // remember to handle parsing cloned packets from worker this.tasks[id] = { resolve: data => resolve(packet.clone.parseClonedPackets(data, method)), reject }; diff --git a/src/worker/worker.js b/src/worker/worker.js index ff1e357b..a7850b4a 100644 --- a/src/worker/worker.js +++ b/src/worker/worker.js @@ -15,7 +15,7 @@ // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -/* globals self: true */ +/* eslint-disable no-restricted-globals */ self.window = {}; // to make UMD bundles work @@ -98,5 +98,5 @@ function response(event) { if (openpgp.crypto.random.randomBuffer.size < MIN_SIZE_RANDOM_BUFFER) { self.postMessage({event: 'request-seed'}); } - self.postMessage(event, openpgp.util.getTransferables.call(openpgp.util, event.data)); -} \ No newline at end of file + self.postMessage(event, openpgp.util.getTransferables(event.data)); +} diff --git a/test/crypto/aes_kw.js b/test/crypto/aes_kw.js new file mode 100644 index 00000000..9500711d --- /dev/null +++ b/test/crypto/aes_kw.js @@ -0,0 +1,59 @@ +'use strict'; + +var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); + +var expect = require('chai').expect; + +describe('AES Key Wrap and Unwrap', function () { + var test_vectors = [ + [ + "128 bits of Key Data with a 128-bit KEK", + "000102030405060708090A0B0C0D0E0F", + "00112233445566778899AABBCCDDEEFF", + "1FA68B0A8112B447 AEF34BD8FB5A7B82 9D3E862371D2CFE5" + ], + [ + "128 bits of Key Data with a 192-bit KEK", + "000102030405060708090A0B0C0D0E0F1011121314151617", + "00112233445566778899AABBCCDDEEFF", + "96778B25AE6CA435 F92B5B97C050AED2 468AB8A17AD84E5D" + ], + [ + "128 bits of Key Data with a 256-bit KEK", + "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "00112233445566778899AABBCCDDEEFF", + "64E8C3F9CE0F5BA2 63E9777905818A2A 93C8191E7D6E8AE7" + ], + [ + "192 bits of Key Data with a 192-bit KEK", + "000102030405060708090A0B0C0D0E0F1011121314151617", + "00112233445566778899AABBCCDDEEFF0001020304050607", + "031D33264E15D332 68F24EC260743EDC E1C6C7DDEE725A93 6BA814915C6762D2" + ], + [ + "192 bits of Key Data with a 256-bit KEK", + "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "00112233445566778899AABBCCDDEEFF0001020304050607", + "A8F9BC1612C68B3F F6E6F4FBE30E71E4 769C8B80A32CB895 8CD5D17D6B254DA1" + ], + [ + "256 bits of Key Data with a 256-bit KEK", + "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + "00112233445566778899AABBCCDDEEFF000102030405060708090A0B0C0D0E0F", + "28C9F404C4B810F4 CBCCB35CFB87F826 3F5786E2D80ED326 CBC7F0E71A99F43B FB988B9B7A02DD21" + ] + ]; + + test_vectors.forEach(function(test) { + it(test[0], function(done) { + var kek = openpgp.util.hex2Uint8Array(test[1]); + var input = test[2].replace(/\s/g, ""); + var input_bin = openpgp.util.hex2bin(input); + var output = test[3].replace(/\s/g, ""); + var output_bin = openpgp.util.hex2bin(output); + expect(openpgp.util.hexidump(openpgp.crypto.aes_kw.wrap(kek, input_bin)).toUpperCase()).to.equal(output); + expect(openpgp.util.hexidump(openpgp.crypto.aes_kw.unwrap(kek, output_bin)).toUpperCase()).to.equal(input); + done(); + }); + }); +}); diff --git a/test/crypto/cipher/des.js b/test/crypto/cipher/des.js index 044a0696..62f2122d 100644 --- a/test/crypto/cipher/des.js +++ b/test/crypto/cipher/des.js @@ -6,7 +6,8 @@ var util = openpgp.util, chai = require('chai'), expect = chai.expect; -describe('TripleDES (EDE) cipher test with test vectors from http://csrc.nist.gov/publications/nistpubs/800-20/800-20.pdf', function() { +describe('TripleDES (EDE) cipher test with test vectors from NIST SP 800-20', function() { + // see http://csrc.nist.gov/publications/nistpubs/800-20/800-20.pdf var 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]); var testvectors = [[[0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00],[0x95,0xF8,0xA5,0xE5,0xDD,0x31,0xD9,0x00]], [[0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00],[0xDD,0x7F,0x12,0x1C,0xA5,0x01,0x56,0x19]], diff --git a/test/crypto/crypto.js b/test/crypto/crypto.js index c4dda83f..ee1e852f 100644 --- a/test/crypto/crypto.js +++ b/test/crypto/crypto.js @@ -2,8 +2,9 @@ var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); -var chai = require('chai'), - expect = chai.expect; +var chai = require('chai'); +chai.use(require('chai-as-promised')); +var expect = chai.expect; describe('API functional testing', function() { var util = openpgp.util; @@ -227,29 +228,39 @@ describe('API functional testing', function() { var data = util.str2Uint8Array("foobar"); describe('Sign and verify', function () { - it('RSA', function (done) { + it('RSA', function () { //Originally we passed public and secret MPI separately, now they are joined. Is this what we want to do long term? // RSA - var RSAsignedData = openpgp.crypto.signature.sign(2, 1, RSApubMPIs.concat(RSAsecMPIs), data); - var RSAsignedDataMPI = new openpgp.MPI(); - RSAsignedDataMPI.read(RSAsignedData); - var success = openpgp.crypto.signature.verify(1, 2, [RSAsignedDataMPI], RSApubMPIs, data); - expect(success).to.be.true; - done(); + return openpgp.crypto.signature.sign( + 2, 1, RSApubMPIs.concat(RSAsecMPIs), data + ).then(RSAsignedData => { + var RSAsignedDataMPI = new openpgp.MPI(); + RSAsignedDataMPI.read(RSAsignedData); + return openpgp.crypto.signature.verify( + 1, 2, [RSAsignedDataMPI], RSApubMPIs, data + ).then(success => { + return expect(success).to.be.true; + }); + }); }); - it('DSA', function (done) { + it('DSA', function () { // DSA - var DSAsignedData = util.Uint8Array2str(openpgp.crypto.signature.sign(2, 17, DSApubMPIs.concat(DSAsecMPIs), data)); - - var 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)); - var success = openpgp.crypto.signature.verify(17, 2, DSAmsgMPIs, DSApubMPIs, data); - expect(success).to.be.true; - done(); + return openpgp.crypto.signature.sign( + 2, 17, DSApubMPIs.concat(DSAsecMPIs), data + ).then(DSAsignedData => { + var DSAsignedData = util.Uint8Array2str(DSAsignedData); + var 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 openpgp.crypto.signature.verify( + 17, 2, DSAmsgMPIs, DSApubMPIs, data + ).then(success => { + return expect(success).to.be.true; + }); + }); }); }); @@ -357,28 +368,38 @@ describe('API functional testing', function() { var symmKey = util.Uint8Array2str(openpgp.crypto.generateSessionKey('aes256')); var RSAUnencryptedData = new openpgp.MPI(); RSAUnencryptedData.fromBytes(openpgp.crypto.pkcs1.eme.encode(symmKey, RSApubMPIs[0].byteLength())); - var RSAEncryptedData = openpgp.crypto.publicKeyEncrypt("rsa_encrypt_sign", RSApubMPIs, RSAUnencryptedData); + openpgp.crypto.publicKeyEncrypt( + "rsa_encrypt_sign", RSApubMPIs, RSAUnencryptedData + ).then(RSAEncryptedData => { - var data = openpgp.crypto.publicKeyDecrypt("rsa_encrypt_sign", RSApubMPIs.concat(RSAsecMPIs), RSAEncryptedData).write(); - data = util.Uint8Array2str(data.subarray(2, data.length)); + openpgp.crypto.publicKeyDecrypt("rsa_encrypt_sign", RSApubMPIs.concat(RSAsecMPIs), RSAEncryptedData).then(data => { + data = data.write(); + data = util.Uint8Array2str(data.subarray(2, data.length)); - var result = openpgp.crypto.pkcs1.eme.decode(data, RSApubMPIs[0].byteLength()); - expect(result).to.equal(symmKey); - done(); + var result = openpgp.crypto.pkcs1.eme.decode(data, RSApubMPIs[0].byteLength()); + expect(result).to.equal(symmKey); + done(); + }); + }); }); it('Asymmetric using Elgamal with eme_pkcs1 padding', function (done) { var symmKey = util.Uint8Array2str(openpgp.crypto.generateSessionKey('aes256')); var ElgamalUnencryptedData = new openpgp.MPI(); ElgamalUnencryptedData.fromBytes(openpgp.crypto.pkcs1.eme.encode(symmKey, ElgamalpubMPIs[0].byteLength())); - var ElgamalEncryptedData = openpgp.crypto.publicKeyEncrypt("elgamal", ElgamalpubMPIs, ElgamalUnencryptedData); + openpgp.crypto.publicKeyEncrypt( + "elgamal", ElgamalpubMPIs, ElgamalUnencryptedData + ).then(ElgamalEncryptedData => { - var data = openpgp.crypto.publicKeyDecrypt("elgamal", ElgamalpubMPIs.concat(ElgamalsecMPIs), ElgamalEncryptedData).write(); - data = util.Uint8Array2str(data.subarray(2, data.length)); + var data = openpgp.crypto.publicKeyDecrypt("elgamal", ElgamalpubMPIs.concat(ElgamalsecMPIs), ElgamalEncryptedData).then(data => { + data = data.write(); + data = util.Uint8Array2str(data.subarray(2, data.length)); - var result = openpgp.crypto.pkcs1.eme.decode(data, ElgamalpubMPIs[0].byteLength()); - expect(result).to.equal(symmKey); - done(); + var result = openpgp.crypto.pkcs1.eme.decode(data, ElgamalpubMPIs[0].byteLength()); + expect(result).to.equal(symmKey); + done(); + }); + }); }); }); }); diff --git a/test/crypto/elliptic.js b/test/crypto/elliptic.js new file mode 100644 index 00000000..2c9ed482 --- /dev/null +++ b/test/crypto/elliptic.js @@ -0,0 +1,354 @@ +'use strict'; + +var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); + +var chai = require('chai'); +chai.use(require('chai-as-promised')); +var expect = chai.expect; + +var bin2bi = function (bytes) { + var mpi = new openpgp.MPI(); + bytes = openpgp.util.bin2str(bytes); + mpi.fromBytes(bytes); + return mpi.toBigInteger(); +}; + +describe('Elliptic Curve Cryptography', function () { + var elliptic_curves = openpgp.crypto.publicKey.elliptic; + var key_data = { + p256: { + priv: new Uint8Array([ + 0x2B, 0x48, 0x2B, 0xE9, 0x88, 0x74, 0xE9, 0x49, + 0x1F, 0x89, 0xCC, 0xFF, 0x0A, 0x26, 0x05, 0xA2, + 0x3C, 0x2A, 0x35, 0x25, 0x26, 0x11, 0xD7, 0xEA, + 0xA1, 0xED, 0x29, 0x95, 0xB5, 0xE1, 0x5F, 0x1D]), + pub: new Uint8Array([0x04, + 0x80, 0x2C, 0x40, 0x76, 0x31, 0x20, 0xB6, 0x9B, + 0x48, 0x3B, 0x05, 0xEB, 0x6C, 0x1E, 0x3F, 0x49, + 0x84, 0xF7, 0xD2, 0xAD, 0x16, 0xA1, 0x6F, 0x62, + 0xFD, 0xCA, 0xEC, 0xB4, 0xA0, 0xBD, 0x4C, 0x1A, + 0x6F, 0xAA, 0xE7, 0xFD, 0xC4, 0x7D, 0x89, 0xCC, + 0x06, 0xCA, 0xFE, 0xAE, 0xCD, 0x0E, 0x9E, 0x62, + 0x57, 0xA4, 0xC3, 0xE7, 0x5E, 0x69, 0x10, 0xEE, + 0x67, 0xC2, 0x09, 0xF9, 0xEF, 0xE7, 0x9E, 0x56]) + }, + p384: { + priv: new Uint8Array([ + 0xB5, 0x38, 0xDA, 0xF3, 0x77, 0x58, 0x3F, 0x94, + 0x5B, 0xC2, 0xCA, 0xC6, 0xA9, 0xFC, 0xAA, 0x3F, + 0x97, 0xB0, 0x54, 0x26, 0x10, 0xB4, 0xEC, 0x2A, + 0xA7, 0xC1, 0xA3, 0x4B, 0xC0, 0xBD, 0xFE, 0x3E, + 0xF1, 0xBE, 0x76, 0xCB, 0xE8, 0xAB, 0x3B, 0xBD, + 0xB6, 0x84, 0xC7, 0x8B, 0x91, 0x2F, 0x76, 0x8B]), + pub: new Uint8Array([0x04, + 0x44, 0x83, 0xA0, 0x3E, 0x5B, 0x0A, 0x0D, 0x9B, + 0xA0, 0x06, 0xDF, 0x38, 0xC7, 0x64, 0xCD, 0x62, + 0x7D, 0x5E, 0x3D, 0x3B, 0x50, 0xF5, 0x06, 0xC7, + 0xF7, 0x9B, 0xF0, 0xDE, 0xB1, 0x0C, 0x64, 0x74, + 0x0D, 0x03, 0x67, 0x24, 0xA0, 0xFF, 0xD1, 0x3D, + 0x03, 0x96, 0x48, 0xE7, 0x73, 0x5E, 0xF1, 0xC0, + 0x62, 0xCC, 0x33, 0x5A, 0x2A, 0x66, 0xA7, 0xAB, + 0xCA, 0x77, 0x52, 0xB8, 0xCD, 0xB5, 0x91, 0x16, + 0xAF, 0x42, 0xBB, 0x79, 0x0A, 0x59, 0x51, 0x68, + 0x8E, 0xEA, 0x32, 0x7D, 0x4A, 0x4A, 0xBB, 0x26, + 0x13, 0xFB, 0x95, 0xC0, 0xB1, 0xA4, 0x54, 0xCA, + 0xFA, 0x85, 0x8A, 0x4B, 0x58, 0x7C, 0x61, 0x39]) + }, + p521: { + priv: new Uint8Array([ + 0x00, 0xBB, 0x35, 0x27, 0xBC, 0xD6, 0x7E, 0x35, + 0xD5, 0xC5, 0x99, 0xC9, 0xB4, 0x6C, 0xEE, 0xDE, + 0x79, 0x2D, 0x77, 0xBD, 0x0A, 0x08, 0x9A, 0xC2, + 0x21, 0xF8, 0x35, 0x1C, 0x49, 0x5C, 0x40, 0x11, + 0xAC, 0x95, 0x2A, 0xEE, 0x91, 0x3A, 0x60, 0x5A, + 0x25, 0x5A, 0x95, 0x38, 0xDC, 0xEB, 0x59, 0x8E, + 0x33, 0xAD, 0xC0, 0x0B, 0x56, 0xB1, 0x06, 0x8C, + 0x57, 0x48, 0xA3, 0x73, 0xDB, 0xE0, 0x19, 0x50, + 0x2E, 0x79]), + pub: new Uint8Array([0x04, + 0x01, 0x0D, 0xD5, 0xCA, 0xD8, 0xB0, 0xEF, 0x9F, + 0x2B, 0x7E, 0x58, 0x99, 0xDE, 0x05, 0xF6, 0xF6, + 0x64, 0x6B, 0xCD, 0x59, 0x2E, 0x39, 0xB8, 0x82, + 0xB3, 0x13, 0xE6, 0x7D, 0x50, 0x85, 0xC3, 0xFA, + 0x93, 0xA5, 0x3F, 0x92, 0x85, 0x42, 0x36, 0xC0, + 0x83, 0xC9, 0xA4, 0x38, 0xB3, 0xD1, 0x99, 0xDA, + 0xE1, 0x02, 0x37, 0x7A, 0x3A, 0xC2, 0xB4, 0x55, + 0xEC, 0x1C, 0x0F, 0x00, 0x97, 0xFC, 0x75, 0x93, + 0xFE, 0x87, 0x00, 0x7D, 0xBE, 0x1A, 0xF5, 0xF9, + 0x57, 0x5C, 0xF2, 0x50, 0x2D, 0x14, 0x32, 0xEE, + 0x9B, 0xBE, 0xB3, 0x0E, 0x12, 0x2F, 0xF8, 0x85, + 0x11, 0x1A, 0x4F, 0x88, 0x50, 0xA4, 0xDB, 0x37, + 0xA6, 0x53, 0x5C, 0xB7, 0x87, 0xA6, 0x06, 0x21, + 0x15, 0xCC, 0x12, 0xC0, 0x1C, 0x83, 0x6F, 0x7B, + 0x5A, 0x8A, 0x36, 0x4E, 0x46, 0x9E, 0x54, 0x3F, + 0xE2, 0xF7, 0xED, 0x63, 0xC9, 0x92, 0xA4, 0x38, + 0x2B, 0x9C, 0xE2, 0xB7]) + }, + secp256k1: { + priv: new Uint8Array([ + 0x9E, 0xB0, 0x30, 0xD6, 0xE1, 0xCE, 0xAA, 0x0B, + 0x7B, 0x8F, 0xDE, 0x5D, 0x91, 0x4D, 0xDC, 0xA0, + 0xAD, 0x05, 0xAB, 0x8F, 0x87, 0x9B, 0x57, 0x48, + 0xAE, 0x8A, 0xE0, 0xF9, 0x39, 0xBD, 0x24, 0x00]), + pub: new Uint8Array([0x04, + 0xA8, 0x02, 0x35, 0x2C, 0xB7, 0x24, 0x95, 0x51, + 0x0A, 0x65, 0x26, 0x7D, 0xDF, 0xEA, 0x64, 0xB3, + 0xA8, 0xE1, 0x4F, 0xDD, 0x12, 0x84, 0x7E, 0x59, + 0xDB, 0x81, 0x0F, 0x89, 0xED, 0xFB, 0x29, 0xFB, + 0x07, 0x60, 0x29, 0x7D, 0x39, 0x8F, 0xB8, 0x68, + 0xF0, 0xFD, 0xA6, 0x67, 0x83, 0x55, 0x75, 0x7D, + 0xB8, 0xFD, 0x0B, 0xDF, 0x76, 0xCE, 0xBC, 0x95, + 0x4B, 0x92, 0x26, 0xFC, 0xAA, 0x7A, 0x7C, 0x3F]) + } + }; + var signature_data = { + priv: new Uint8Array([ + 0x14, 0x2B, 0xE2, 0xB7, 0x4D, 0xBD, 0x1B, 0x22, + 0x4D, 0xDF, 0x96, 0xA4, 0xED, 0x8E, 0x5B, 0xF9, + 0xBD, 0xD3, 0xFE, 0xAE, 0x3F, 0xB2, 0xCF, 0xEE, + 0xA7, 0xDB, 0xD0, 0x58, 0xA7, 0x47, 0xF8, 0x7C]), + pub: new Uint8Array([0x04, + 0xD3, 0x36, 0x11, 0xF9, 0xF9, 0xAB, 0x39, 0x23, + 0x15, 0xB9, 0x71, 0x7B, 0x2A, 0x0B, 0xA6, 0x6D, + 0x39, 0x6D, 0x64, 0x87, 0x22, 0x9A, 0xA3, 0x0A, + 0x55, 0x27, 0x14, 0x2E, 0x1C, 0x61, 0xA2, 0x8A, + 0xDA, 0x4E, 0x8F, 0xCE, 0x04, 0xBE, 0xE2, 0xC3, + 0x82, 0x0B, 0x21, 0x4C, 0xBC, 0xED, 0x0E, 0xE2, + 0xF1, 0x14, 0x33, 0x9A, 0x86, 0x5F, 0xC6, 0xF9, + 0x8E, 0x95, 0x24, 0x10, 0x1F, 0x0F, 0x13, 0xE4]), + message: new Uint8Array([ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F]), + signature: { + r: new Uint8Array([ + 0xF1, 0x78, 0x1C, 0xA5, 0x13, 0x21, 0x0C, 0xBA, + 0x6F, 0x18, 0x5D, 0xB3, 0x01, 0xE2, 0x17, 0x1B, + 0x67, 0x65, 0x7F, 0xC6, 0x1F, 0x50, 0x12, 0xFB, + 0x2F, 0xD3, 0xA4, 0x29, 0xE3, 0xC2, 0x44, 0x9F]), + s: new Uint8Array([ + 0x7F, 0x08, 0x69, 0x6D, 0xBB, 0x1B, 0x9B, 0xF2, + 0x62, 0x1C, 0xCA, 0x80, 0xC6, 0x15, 0xB2, 0xAE, + 0x60, 0x50, 0xD1, 0xA7, 0x1B, 0x32, 0xF3, 0xB1, + 0x01, 0x0B, 0xDF, 0xC6, 0xAB, 0xF0, 0xEB, 0x01]) + } + }; + describe('Basic Operations', function () { + it('Creating curve with name', function (done) { + var names = ['p256', 'p384', 'p521', 'secp256k1', 'curve25519']; + names.forEach(function (name) { + expect(elliptic_curves.get(name)).to.exist; + }); + done(); + }); + it('Creating curve from oid', function (done) { + var oids = ['2A8648CE3D030107', '2B81040022', '2B81040023', '2B8104000A']; + oids.forEach(function (oid) { + expect(elliptic_curves.get(openpgp.util.hex2bin(oid))).to.exist; + }); + done(); + }); + it('Creating KeyPair', function () { + var names = ['p256', 'p384', 'p521', 'secp256k1', 'curve25519']; + return Promise.all(names.map(function (name) { + var curve = elliptic_curves.get(name); + return curve.genKeyPair().then(keyPair => { + expect(keyPair).to.exist; + }); + })); + }); + it('Creating KeyPair from data', function (done) { + for (var name in key_data) { + var pair = key_data[name]; + var curve = elliptic_curves.get(name); + expect(curve).to.exist; + var keyPair = curve.keyFromPrivate(pair.priv); + expect(keyPair).to.exist; + var pub = keyPair.getPublic(); + expect(pub).to.exist; + expect(openpgp.util.hexidump(pub)).to.equal(openpgp.util.hexidump(pair.pub)); + } + done(); + }); + it('Signature verification', function (done) { + var curve = elliptic_curves.get('p256'); + var key = curve.keyFromPublic(signature_data.pub); + expect(key.verify(signature_data.message, signature_data.signature, 8)).to.eventually.be.true; + done(); + }); + it('Invalid signature', function (done) { + var curve = elliptic_curves.get('p256'); + var key = curve.keyFromPublic(key_data.p256.pub); + expect(key.verify(signature_data.message, signature_data.signature, 8)).to.eventually.be.false; + done(); + }); + it('Signature generation', function () { + var curve = elliptic_curves.get('p256'); + var key = curve.keyFromPrivate(key_data.p256.priv); + return key.sign(signature_data.message, 8).then(signature => { + key = curve.keyFromPublic(key_data.p256.pub); + expect(key.verify(signature_data.message, signature, 8)).to.eventually.be.true; + }); + }); + it('Shared secret generation', function (done) { + var curve = elliptic_curves.get('p256'); + var key1 = curve.keyFromPrivate(key_data.p256.priv); + var key2 = curve.keyFromPublic(signature_data.pub); + var shared1 = openpgp.util.hexidump(key1.derive(key2)); + key1 = curve.keyFromPublic(key_data.p256.pub); + key2 = curve.keyFromPrivate(signature_data.priv); + var shared2 = openpgp.util.hexidump(key2.derive(key1)); + expect(shared1).to.equal(shared2); + done(); + }); + }); + describe('ECDSA signature', function () { + var verify_signature = function (oid, hash, r, s, message, pub) { + if (openpgp.util.isString(message)) { + message = openpgp.util.str2Uint8Array(message); + } else if (!openpgp.util.isUint8Array(message)) { + message = new Uint8Array(message); + } + var ecdsa = elliptic_curves.ecdsa; + return ecdsa.verify( + oid, + hash, + {r: bin2bi(r), s: bin2bi(s)}, + message, + bin2bi(pub) + ); + }; + var secp256k1_dummy_value = new Uint8Array([ + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); + var secp256k1_dummy_point = new Uint8Array([0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); + var secp256k1_invalid_point = new Uint8Array([0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); + it('Invalid curve oid', function () { + return Promise.all([ + expect(verify_signature( + 'invalid oid', 8, [], [], [], [] + )).to.be.rejectedWith(Error, /Not valid curve/), + expect(verify_signature( + "\x00", 8, [], [], [], [] + )).to.be.rejectedWith(Error, /Not valid curve/) + ]); + }); + it('Invalid public key', function () { + return Promise.all([ + expect(verify_signature( + 'secp256k1', 8, [], [], [], [] + )).to.be.rejectedWith(Error, /Unknown point format/), + expect(verify_signature( + 'secp256k1', 8, [], [], [], secp256k1_invalid_point + )).to.be.rejectedWith(Error, /Unknown point format/) + ]); + }); + it('Invalid signature', function (done) { + expect(verify_signature( + 'secp256k1', 8, [], [], [], secp256k1_dummy_point + )).to.eventually.be.false.notify(done); + }); + + var p384_message = new Uint8Array([ + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF]); + var p384_r = new Uint8Array([ + 0x9D, 0x07, 0xCA, 0xA5, 0x9F, 0xBE, 0xB8, 0x76, + 0xA9, 0xB9, 0x66, 0x0F, 0xA0, 0x64, 0x70, 0x5D, + 0xE6, 0x37, 0x40, 0x43, 0xD0, 0x8E, 0x40, 0xA8, + 0x8B, 0x37, 0x83, 0xE7, 0xBC, 0x1C, 0x4C, 0x86, + 0xCB, 0x3C, 0xD5, 0x9B, 0x68, 0xF0, 0x65, 0xEB, + 0x3A, 0xB6, 0xD6, 0xA6, 0xCF, 0x85, 0x3D, 0xA9]); + var p384_s = new Uint8Array([ + 0x32, 0x85, 0x78, 0xCC, 0xEA, 0xC5, 0x22, 0x83, + 0x10, 0x73, 0x1C, 0xCF, 0x10, 0x8A, 0x52, 0x11, + 0x8E, 0x49, 0x9E, 0xCF, 0x7E, 0x17, 0x18, 0xC3, + 0x11, 0x11, 0xBC, 0x0F, 0x6D, 0x98, 0xE2, 0x16, + 0x68, 0x58, 0x23, 0x1D, 0x11, 0xEF, 0x3D, 0x21, + 0x30, 0x75, 0x24, 0x39, 0x48, 0x89, 0x03, 0xDC]); + it('Valid signature', function (done) { + expect(verify_signature('p384', 8, p384_r, p384_s, p384_message, key_data.p384.pub)) + .to.eventually.be.true.notify(done); + }); + it('Sign and verify message', function () { + var curve = elliptic_curves.get('p521'); + return curve.genKeyPair().then(keyPair => { + var keyPublic = bin2bi(keyPair.getPublic()); + var keyPrivate = bin2bi(keyPair.getPrivate()); + var oid = curve.oid; + var message = p384_message; + return elliptic_curves.ecdsa.sign(oid, 10, message, keyPrivate).then(signature => { + expect(elliptic_curves.ecdsa.verify(oid, 10, signature, message, keyPublic)) + .to.eventually.be.true; + }); + }); + }); + }); + describe('ECDH key exchange', function () { + var decrypt_message = function (oid, hash, cipher, priv, ephemeral, data, fingerprint) { + if (openpgp.util.isString(data)) { + data = openpgp.util.str2Uint8Array(data); + } else { + data = new Uint8Array(data); + } + return Promise.resolve().then(() => { + var ecdh = elliptic_curves.ecdh; + return ecdh.decrypt( + oid, + cipher, + hash, + bin2bi(ephemeral), + data, + bin2bi(priv), + fingerprint + ); + }); + }; + var secp256k1_value = new Uint8Array([ + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); + var secp256k1_point = new Uint8Array([0x04, + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); + var secp256k1_data = new Uint8Array([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); + + it('Invalid curve oid', function (done) { + expect(decrypt_message( + '', 2, 7, [], [], [], '' + )).to.be.rejectedWith(Error, /Not valid curve/).notify(done); + }); + it('Invalid ephemeral key', function (done) { + expect(decrypt_message( + 'secp256k1', 2, 7, [], [], [], '' + )).to.be.rejectedWith(Error, /Unknown point format/).notify(done);; + }); + it('Invalid key data integrity', function (done) { + expect(decrypt_message( + 'secp256k1', 2, 7, secp256k1_value, secp256k1_point, secp256k1_data, '' + )).to.be.rejectedWith(Error, /Key Data Integrity failed/).notify(done); + }); + }); +}); diff --git a/test/crypto/index.js b/test/crypto/index.js index f04a73d0..7ab5c758 100644 --- a/test/crypto/index.js +++ b/test/crypto/index.js @@ -3,4 +3,7 @@ describe('Crypto', function () { require('./hash'); require('./random.js'); require('./crypto.js'); + require('./elliptic.js'); + require('./pkcs5.js'); + require('./aes_kw.js'); }); diff --git a/test/crypto/pkcs5.js b/test/crypto/pkcs5.js new file mode 100644 index 00000000..2e078ffa --- /dev/null +++ b/test/crypto/pkcs5.js @@ -0,0 +1,41 @@ +'use strict'; + +var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); + +var expect = require('chai').expect; + +describe('PKCS5 padding', function() { + function repeat(pattern, count) { + var result = ''; + for (var k=0; k 8, 8..15 -> 16 + var l = Math.ceil((s.length+1)/8)*8; + var 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 (var k=1; k<=8; ++k) { + var s = repeat(' ', 8-k); + var r = s + repeat(String.fromCharCode(k), k); + var 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/general/ecc_nist.js b/test/general/ecc_nist.js new file mode 100644 index 00000000..27149763 --- /dev/null +++ b/test/general/ecc_nist.js @@ -0,0 +1,232 @@ +'use strict'; + +var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); + +var chai = require('chai'); +chai.use(require('chai-as-promised')); +var expect = chai.expect; + +describe('Elliptic Curve Cryptography', function () { + var data = { + romeo: { + id: 'c2b12389b401a43d', + pass: 'juliet', + pub: [ + '-----BEGIN PGP PUBLIC KEY BLOCK-----', + 'Version: OpenPGP.js 1.3+secp256k1', + 'Comment: http://openpgpjs.org', + '', + 'xk8EVjET2xMFK4EEAAoCAwS/zT2gefLhEnISXN3rvdV3eD6MVrPwxNMAR+LM', + 'ZzFO1gdtZbf7XQSZP02CYQe3YFrNQYYuJ4CGkTvOVJSV+yrAzS5Sb21lbyBN', + 'b250YWd1ZSAoc2VjcDI1NmsxKSA8cm9tZW9AZXhhbXBsZS5uZXQ+wnIEEBMI', + 'ACQFAlYxE9sFCwkIBwMJEMKxI4m0AaQ9AxUICgMWAgECGwMCHgEAAOjHAQDM', + 'y6EJPFayCgI4ZSmZlSue3xFShj9y6hZTLZqPJquspQD+MMT00a2Cicnbhrd1', + '8SQUIYRQ//I7oXVoxZN5MA4rmOHOUwRWMRPbEgUrgQQACgIDBLPZgGC257Ra', + 'Z9Bg3ij9OgSoJGwqIu03SfQMTnR2crHkAHqLaUImz/lwhsL/V499zXZ2gEmf', + 'oKCacroXNDM85xUDAQgHwmEEGBMIABMFAlYxE9sJEMKxI4m0AaQ9AhsMAADk', + 'gwEA4B3lysFe/3+KE/PgCSZkUfx7n7xlKqMiqrX+VNyPej8BAMQJgtMVdslQ', + 'HLr5fhoGnRots3JSC0j20UQQOKVOXaW3', + '=VpL9', + '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'), + priv: [ + '-----BEGIN PGP PRIVATE KEY BLOCK-----', + 'Version: OpenPGP.js 1.3+secp256k1', + 'Comment: http://openpgpjs.org', + '', + 'xaIEVjET2xMFK4EEAAoCAwS/zT2gefLhEnISXN3rvdV3eD6MVrPwxNMAR+LM', + 'ZzFO1gdtZbf7XQSZP02CYQe3YFrNQYYuJ4CGkTvOVJSV+yrA/gkDCILD3FP2', + 'D6eRYNWhI+QTFWAGDw+pIhtXQ/p0zZgK6HSk68Fox0tH6TlGtPmtULkPExs0', + 'cnIdAVSMHI+SnZ9lIeAykAcFoqJYIO5p870XbjzNLlJvbWVvIE1vbnRhZ3Vl', + 'IChzZWNwMjU2azEpIDxyb21lb0BleGFtcGxlLm5ldD7CcgQQEwgAJAUCVjET', + '2wULCQgHAwkQwrEjibQBpD0DFQgKAxYCAQIbAwIeAQAA6McBAMzLoQk8VrIK', + 'AjhlKZmVK57fEVKGP3LqFlMtmo8mq6ylAP4wxPTRrYKJyduGt3XxJBQhhFD/', + '8juhdWjFk3kwDiuY4cemBFYxE9sSBSuBBAAKAgMEs9mAYLbntFpn0GDeKP06', + 'BKgkbCoi7TdJ9AxOdHZyseQAeotpQibP+XCGwv9Xj33NdnaASZ+goJpyuhc0', + 'MzznFQMBCAf+CQMIqp5StLTK+lBgqmaJ8/64E+8+OJVOgzk8EoRp8bS9IEac', + 'VYu2i8ARjAF3sqwGZ5hxxsniORcjQUghf+n+NwEm9LUWfbAGUlT4YfSIq5pV', + 'rsJhBBgTCAATBQJWMRPbCRDCsSOJtAGkPQIbDAAA5IMBAOAd5crBXv9/ihPz', + '4AkmZFH8e5+8ZSqjIqq1/lTcj3o/AQDECYLTFXbJUBy6+X4aBp0aLbNyUgtI', + '9tFEEDilTl2ltw==', + '=C3TW', + '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'), + message: 'Shall I hear more, or shall I speak at this?' + }, + juliet: { + id: '64116021959bdfe0', + pass: 'romeo', + pub: [ + '-----BEGIN PGP PUBLIC KEY BLOCK-----', + 'Version: OpenPGP.js 1.3+secp256k1', + 'Comment: http://openpgpjs.org', + '', + 'xk8EVjEUUBMFK4EEAAoCAwQRNz0sbftAv3SSE0fm7vE0pD96NDA3YtGdObaj', + 'D0DNUMBL1eoLl5/qdJUc/16xbZLkL2saMsbqtPn/iuahz6bkzS9KdWxpZXQg', + 'Q2FwdWxldCAoc2VjcDI1NmsxKSA8anVsaWV0QGV4YW1wbGUubmV0PsJyBBAT', + 'CAAkBQJWMRRRBQsJCAcDCRBkEWAhlZvf4AMVCAoDFgIBAhsDAh4BAAAr1wEA', + '+39TqKy/tks7dPlEYw+IYkFCW99a60kiSCjLBPxEgNUA/3HeLDP/XbrgklUs', + 'DFOy20aHE7M6i/cFXLLxDJmN6BF3zlMEVjEUUBIFK4EEAAoCAwTQ02rHHP/d', + 'kR4W7y5BY4kRtoNc/HxUloOpxA8svfmxwOoP5stCS/lInD8K+7nSEiPr84z9', + 'EQ47LMjiT1zK2mHZAwEIB8JhBBgTCAATBQJWMRRRCRBkEWAhlZvf4AIbDAAA', + '7FoA/1Y4xDYO49u21I7aqjPyTygLoObdLMAtK6xht+DDc0YKAQDNp2wv0HOJ', + '+0kjoUNu6PRIll/jMgTVAXn0Mov6HqJ95A==', + '=ISmy', + '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'), + priv: [ + '-----BEGIN PGP PRIVATE KEY BLOCK-----', + 'Version: OpenPGP.js 1.3+secp256k1', + 'Comment: http://openpgpjs.org', + '', + 'xaIEVjEUUBMFK4EEAAoCAwQRNz0sbftAv3SSE0fm7vE0pD96NDA3YtGdObaj', + 'D0DNUMBL1eoLl5/qdJUc/16xbZLkL2saMsbqtPn/iuahz6bk/gkDCD9EH0El', + '7o9qYIbX56Ri3VlfCbpQgy1cVx9RETKI4guW9vUu6SeY2NhXASvfK+zgpLzO', + 'j+hv2a+re549UKBdFbPEcyPUQKo2YJ1AfdAfZcDNL0p1bGlldCBDYXB1bGV0', + 'IChzZWNwMjU2azEpIDxqdWxpZXRAZXhhbXBsZS5uZXQ+wnIEEBMIACQFAlYx', + 'FFEFCwkIBwMJEGQRYCGVm9/gAxUICgMWAgECGwMCHgEAACvXAQD7f1OorL+2', + 'Szt0+URjD4hiQUJb31rrSSJIKMsE/ESA1QD/cd4sM/9duuCSVSwMU7LbRocT', + 'szqL9wVcsvEMmY3oEXfHpgRWMRRQEgUrgQQACgIDBNDTascc/92RHhbvLkFj', + 'iRG2g1z8fFSWg6nEDyy9+bHA6g/my0JL+UicPwr7udISI+vzjP0RDjssyOJP', + 'XMraYdkDAQgH/gkDCA4aIC5h7thWYEM9KvwVEN4/rAYOWVNzUN2K7l25M+NZ', + '1/mEAjEgEW9yPufKtF3hILeNdPBwh6Gcw/0gOJ/9yJwKk7tqwyS/gKF1+VDm', + 'X0LCYQQYEwgAEwUCVjEUUQkQZBFgIZWb3+ACGwwAAOxaAP9WOMQ2DuPbttSO', + '2qoz8k8oC6Dm3SzALSusYbfgw3NGCgEAzadsL9BziftJI6FDbuj0SJZf4zIE', + '1QF59DKL+h6ifeQ=', + '=QvXN', + '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'), + message: 'O Romeo, Romeo! Wherefore art thou Romeo?', + message_signed: [ + '-----BEGIN PGP SIGNED MESSAGE-----', + 'Hash: SHA256', + '', + 'O Romeo, Romeo! Wherefore art thou Romeo?', + '-----BEGIN PGP SIGNATURE-----', + 'Version: GnuPG v2', + 'Comment: GnuPG v2.1+libgcrypt-1.7', + '', + 'iF4EARMIAAYFAlYxF8oACgkQZBFgIZWb3+BfTwD/b1yKtFnKrRjELuD6/gOH9/er', + '6yc7nzn1FBYFzMz8aFIA/3FlcIvR+eLvRTVmfiEatB6IU6JviBnzxR1gA/SOdyS2', + '=GCiR', + '-----END PGP SIGNATURE-----'].join('\n'), + message_encrypted: [ + '-----BEGIN PGP MESSAGE-----', + 'Version: GnuPG v2', + 'Comment: GnuPG v2.1+libgcrypt-1.7', + '', + 'hH4DDYFqRW5CSpsSAgMERfIYgKzriOCHTTQnWhM4VZ6cLjrjJbOaW1VuCfeN03d+', + 'yzhW1Sm1BYYdqxPE0rvjvGfD8VmMB6etaHQsrDQflzA+vGeVa9Mn/wyKq4+j13ur', + 'NOoUhDKX27+LEBNfho6bbEN72J7z3E5/+wVr+wEt3bLSwBcBvuNNkvGCpE19/AmL', + 'GP2lmjE6O9VfiW0o8sxfa+hPEq2A+6DxvMhxi2YPS0f9MMPqn5NFx2PCIGdC0+xY', + 'f0BXl1atBO1z6UXTC9aHH7UULKdynr4nUEkDa3DJW/feCSC6rQxTikn/Gf4341qQ', + 'aiwv66jhgJSdB+2+JrHfh6Znvv2fhl3SQl8K0CiG8Q0QubWdlQwNaNSOmgH7v3T8', + 'j5FhrMbD3Z+TPlrNjJqidAV28XwSBFvhw8Jf5WpaewOxVlxLjUHnnkUGHyvfdEr/', + 'DP/V1yLuBUZuRg==', + '=GEAB', + '-----END PGP MESSAGE-----'].join('\n') + } + }; + function load_pub_key(name) { + if (data[name].pub_key) { + return data[name].pub_key; + } + var pub = 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].primaryKey.getKeyId().toHex()).to.equal(data[name].id); + data[name].pub_key = pub.keys[0]; + return data[name].pub_key; + } + function load_priv_key(name) { + if (data[name].priv_key) { + return data[name].priv_key; + } + var pk = openpgp.key.readArmored(data[name].priv); + expect(pk).to.exist; + expect(pk.err).to.not.exist; + expect(pk.keys).to.have.length(1); + expect(pk.keys[0].primaryKey.getKeyId().toHex()).to.equal(data[name].id); + expect(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', function (done) { + load_pub_key('romeo'); + load_pub_key('juliet'); + done(); + }); + it('Load private key', function (done) { + load_priv_key('romeo'); + load_priv_key('juliet'); + done(); + }); + it('Verify clear signed message', function () { + var pub = load_pub_key('juliet'); + var msg = openpgp.cleartext.readArmored(data.juliet.message_signed); + return openpgp.verify({publicKeys: [pub], message: msg}).then(function(result) { + expect(result).to.exist; + expect(result.data.trim()).to.equal(data.juliet.message); + expect(result.signatures).to.have.length(1); + expect(result.signatures[0].valid).to.be.true; + }); + }); + it('Sign message', function () { + var romeo = load_priv_key('romeo'); + return openpgp.sign({privateKeys: [romeo], data: data.romeo.message + "\n"}).then(function (signed) { + var romeo = load_pub_key('romeo'); + var msg = openpgp.cleartext.readArmored(signed.data); + return openpgp.verify({publicKeys: [romeo], message: msg}).then(function (result) { + expect(result).to.exist; + expect(result.data.trim()).to.equal(data.romeo.message); + expect(result.signatures).to.have.length(1); + expect(result.signatures[0].valid).to.be.true; + }); + }); + }); + it('Decrypt and verify message', function () { + var juliet = load_pub_key('juliet'); + var romeo = load_priv_key('romeo'); + var msg = openpgp.message.readArmored(data.juliet.message_encrypted); + return openpgp.decrypt( + {privateKey: romeo, publicKeys: [juliet], message: msg} + ).then(function (result) { + expect(result).to.exist; + // trim required because https://github.com/openpgpjs/openpgpjs/issues/311 + expect(result.data.trim()).to.equal(data.juliet.message); + expect(result.signatures).to.have.length(1); + expect(result.signatures[0].valid).to.be.true; + }); + }); + it('Encrypt and sign message', function () { + var romeo = load_priv_key('romeo'); + var juliet = load_pub_key('juliet'); + expect(romeo.decrypt(data['romeo'].pass)).to.be.true; + return openpgp.encrypt( + {publicKeys: [juliet], privateKeys: [romeo], data: data.romeo.message + "\n"} + ).then(function (encrypted) { + var message = openpgp.message.readArmored(encrypted.data); + var romeo = load_pub_key('romeo'); + var juliet = load_priv_key('juliet'); + return openpgp.decrypt( + {privateKey: juliet, publicKeys: [romeo], message: message} + ).then(function (result) { + expect(result).to.exist; + expect(result.data.trim()).to.equal(data.romeo.message); + expect(result.signatures).to.have.length(1); + expect(result.signatures[0].valid).to.be.true; + }); + }); + }); + it('Generate key', function () { + var options = { + userIds: {name: "Hamlet (secp256k1)", email: "hamlet@example.net"}, + curve: "secp256k1", + passphrase: "ophelia" + }; + return openpgp.generateKey(options).then(function (key) { + expect(key).to.exist; + expect(key.key).to.exist; + expect(key.key.primaryKey).to.exist; + expect(key.privateKeyArmored).to.exist; + expect(key.publicKeyArmored).to.exist; + }); + }); +}); diff --git a/test/general/index.js b/test/general/index.js index 61de4d72..0e9b6ff2 100644 --- a/test/general/index.js +++ b/test/general/index.js @@ -7,5 +7,8 @@ describe('General', function () { require('./key.js'); require('./openpgp.js'); require('./hkp.js'); + require('./oid.js'); + require('./ecc_nist.js'); + require('./x25519.js'); }); diff --git a/test/general/key.js b/test/general/key.js index 10a8dde3..47974849 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -2,8 +2,9 @@ var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); -var chai = require('chai'), - expect = chai.expect; +var chai = require('chai'); +chai.use(require('chai-as-promised')); +var expect = chai.expect; describe('Key', function() { var twoKeys = @@ -582,7 +583,7 @@ describe('Key', function() { '=yzZh', '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'); - it('Parsing armored text with RSA key and ECC subkey in tolerant mode', function(done) { + it('Parsing armored text with RSA key and ECC subkey', function(done) { openpgp.config.tolerant = true; var pubKeys = openpgp.key.readArmored(rsa_ecc_pub); expect(pubKeys).to.exist; @@ -592,15 +593,6 @@ describe('Key', function() { done(); }); - it('Parsing armored text with RSA key and ECC subkey in non-tolerant mode', function(done) { - openpgp.config.tolerant = false; - var pubKeys = openpgp.key.readArmored(rsa_ecc_pub); - expect(pubKeys).to.exist; - expect(pubKeys.err).to.exist; - done(); - }); - - var multi_uid_key = ['-----BEGIN PGP PUBLIC KEY BLOCK-----', 'Version: GnuPG v1', @@ -727,9 +719,9 @@ describe('Key', function() { expect(pubKey.subKeys).to.exist; expect(pubKey.subKeys).to.have.length(2); - var status = pubKey.subKeys[0].verify(pubKey.primaryKey); - expect(status).to.equal(openpgp.enums.keyStatus.revoked); - done(); + expect(pubKey.subKeys[0].verify( + pubKey.primaryKey + )).to.eventually.equal(openpgp.enums.keyStatus.revoked).notify(done); }); it('Evaluate key flags to find valid encryption key packet', function() { @@ -750,7 +742,9 @@ describe('Key', function() { var pubKey = openpgp.key.readArmored(twoKeys).keys[1]; expect(pubKey).to.exist; expect(pubKey).to.be.an.instanceof(openpgp.key.Key); - expect(pubKey.getExpirationTime().toISOString()).to.be.equal('2018-11-26T10:58:29.000Z'); + return pubKey.verifyPrimaryUser().then(() => { + expect(pubKey.getExpirationTime().toISOString()).to.be.equal('2018-11-26T10:58:29.000Z'); + }); }); it('Method getExpirationTime V4 SubKey', function() { @@ -760,73 +754,96 @@ describe('Key', function() { expect(pubKey.subKeys[0].getExpirationTime().toISOString()).to.be.equal('2018-11-26T10:58:29.000Z'); }); - it('update() - throw error if fingerprints not equal', function() { + it('update() - throw error if fingerprints not equal', function(done) { var keys = openpgp.key.readArmored(twoKeys).keys; - expect(keys[0].update.bind(keys[0], keys[1])).to.throw('Key update method: fingerprints of keys not equal'); + expect(keys[0].update.bind( + keys[0], keys[1] + )()).to.be.rejectedWith('Key update method: fingerprints of keys not equal').notify(done); }); - it('update() - merge revocation signature', function() { + it('update() - merge revocation signature', function(done) { var source = openpgp.key.readArmored(pub_revoked).keys[0]; var dest = openpgp.key.readArmored(pub_revoked).keys[0]; expect(source.revocationSignature).to.exist; dest.revocationSignature = null; - dest.update(source); - expect(dest.revocationSignature).to.exist.and.be.an.instanceof(openpgp.packet.Signature); + dest.update(source).then(() => { + expect(dest.revocationSignature).to.exist.and.be.an.instanceof(openpgp.packet.Signature); + done(); + }); }); - it('update() - merge user', function() { + it('update() - merge user', function(done) { var source = openpgp.key.readArmored(pub_sig_test).keys[0]; var dest = openpgp.key.readArmored(pub_sig_test).keys[0]; expect(source.users[1]).to.exist; dest.users.pop(); - dest.update(source); - expect(dest.users[1]).to.exist; - expect(dest.users[1].userId).to.equal(source.users[1].userId); + dest.update(source).then(() => { + expect(dest.users[1]).to.exist; + expect(dest.users[1].userId).to.equal(source.users[1].userId); + done(); + }); }); - it('update() - merge user - other and revocation certification', function() { + it('update() - merge user - other and revocation certification', function(done) { var source = openpgp.key.readArmored(pub_sig_test).keys[0]; var dest = openpgp.key.readArmored(pub_sig_test).keys[0]; expect(source.users[1].otherCertifications).to.exist; expect(source.users[1].revocationCertifications).to.exist; dest.users[1].otherCertifications = null; dest.users[1].revocationCertifications.pop(); - dest.update(source); - expect(dest.users[1].otherCertifications).to.exist.and.to.have.length(1); - expect(dest.users[1].otherCertifications[0].signature).to.equal(source.users[1].otherCertifications[0].signature); - expect(dest.users[1].revocationCertifications).to.exist.and.to.have.length(2); - expect(dest.users[1].revocationCertifications[1].signature).to.equal(source.users[1].revocationCertifications[1].signature); + dest.update(source).then(() => { + expect(dest.users[1].otherCertifications).to.exist.and.to.have.length(1); + expect(dest.users[1].otherCertifications[0].signature).to.equal(source.users[1].otherCertifications[0].signature); + expect(dest.users[1].revocationCertifications).to.exist.and.to.have.length(2); + expect(dest.users[1].revocationCertifications[1].signature).to.equal(source.users[1].revocationCertifications[1].signature); + done(); + }); }); - it('update() - merge subkey', function() { + it('update() - merge subkey', function(done) { var source = openpgp.key.readArmored(pub_sig_test).keys[0]; var dest = openpgp.key.readArmored(pub_sig_test).keys[0]; expect(source.subKeys[1]).to.exist; dest.subKeys.pop(); - dest.update(source); - expect(dest.subKeys[1]).to.exist; - expect(dest.subKeys[1].subKey.getKeyId().toHex()).to.equal(source.subKeys[1].subKey.getKeyId().toHex()); + dest.update(source).then(() => { + expect(dest.subKeys[1]).to.exist; + expect( + dest.subKeys[1].subKey.getKeyId().toHex() + ).to.equal(source.subKeys[1].subKey.getKeyId().toHex()); + done(); + }); }); - it('update() - merge subkey - revocation signature', function() { + it('update() - merge subkey - revocation signature', function(done) { var source = openpgp.key.readArmored(pub_sig_test).keys[0]; var dest = openpgp.key.readArmored(pub_sig_test).keys[0]; expect(source.subKeys[0].revocationSignature).to.exist; dest.subKeys[0].revocationSignature = null; - dest.update(source); - expect(dest.subKeys[0].revocationSignature).to.exist; - expect(dest.subKeys[0].revocationSignature.signature).to.equal(dest.subKeys[0].revocationSignature.signature); + dest.update(source).then(() => { + expect(dest.subKeys[0].revocationSignature).to.exist; + expect(dest.subKeys[0].revocationSignature.signature).to.equal(dest.subKeys[0].revocationSignature.signature); + done(); + }); }); it('update() - merge private key into public key', function() { var source = openpgp.key.readArmored(priv_key_rsa).keys[0]; var dest = openpgp.key.readArmored(twoKeys).keys[0]; expect(dest.isPublic()).to.be.true; - dest.update(source); - expect(dest.isPrivate()).to.be.true; - expect(source.verifyPrimaryKey()).to.equal(dest.verifyPrimaryKey()); - expect(source.users[0].verify(source.primaryKey)).to.equal(dest.users[0].verify(dest.primaryKey)); - expect(source.subKeys[0].verify(source.primaryKey)).to.equal(dest.subKeys[0].verify(dest.primaryKey)); + 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); + }), + dest.subKeys[0].verify(dest.primaryKey).then(result => { + expect(source.subKeys[0].verify(source.primaryKey)).to.eventually.equal(result); + }) + ]); + }); }); it('update() - merge private key into public key - no subkeys', function() { @@ -835,54 +852,72 @@ describe('Key', function() { source.subKeys = null; dest.subKeys = null; expect(dest.isPublic()).to.be.true; - dest.update(source); - expect(dest.isPrivate()).to.be.true; - expect(source.verifyPrimaryKey()).to.equal(dest.verifyPrimaryKey()); - expect(source.users[0].verify(source.primaryKey)).to.equal(dest.users[0].verify(dest.primaryKey)); + 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', function() { + it('update() - merge private key into public key - mismatch throws error', function(done) { var source = openpgp.key.readArmored(priv_key_rsa).keys[0]; var dest = openpgp.key.readArmored(twoKeys).keys[0]; source.subKeys = null; expect(dest.subKeys).to.exist; expect(dest.isPublic()).to.be.true; - expect(dest.update.bind(dest, source)).to.throw('Cannot update public key with private key if subkey mismatch'); + expect(dest.update.bind(dest, source)()) + .to.be.rejectedWith('Cannot update public key with private key if subkey mismatch').notify(done); }); - it('update() - merge subkey binding signatures', function() { + it('update() - merge subkey binding signatures', function(done) { var source = openpgp.key.readArmored(pgp_desktop_pub).keys[0]; var dest = openpgp.key.readArmored(pgp_desktop_priv).keys[0]; expect(source.subKeys[0].bindingSignatures[0]).to.exist; - expect(source.subKeys[0].verify(source.primaryKey)).to.equal(openpgp.enums.keyStatus.valid); + expect(source.subKeys[0].verify(source.primaryKey)) + .to.eventually.equal(openpgp.enums.keyStatus.valid); expect(dest.subKeys[0].bindingSignatures[0]).to.not.exist; - dest.update(source); - expect(dest.subKeys[0].bindingSignatures[0]).to.exist; - expect(dest.subKeys[0].verify(source.primaryKey)).to.equal(openpgp.enums.keyStatus.valid); + dest.update(source).then(() => { + expect(dest.subKeys[0].bindingSignatures[0]).to.exist; + expect(dest.subKeys[0].verify(source.primaryKey)) + .to.eventually.equal(openpgp.enums.keyStatus.valid); + done(); + }); }); it('getPreferredSymAlgo() - one key - AES256', function() { var key1 = openpgp.key.readArmored(twoKeys).keys[0]; - var prefAlgo = openpgp.key.getPreferredSymAlgo([key1]); - expect(prefAlgo).to.equal(openpgp.enums.symmetric.aes256); + return key1.verifyPrimaryUser().then(() => { + var prefAlgo = openpgp.key.getPreferredSymAlgo([key1]); + expect(prefAlgo).to.equal(openpgp.enums.symmetric.aes256); + }); }); it('getPreferredSymAlgo() - two key - AES128', function() { var keys = openpgp.key.readArmored(twoKeys).keys; var key1 = keys[0]; var key2 = keys[1]; - key2.getPrimaryUser().selfCertificate.preferredSymmetricAlgorithms = [6,7,3]; - var prefAlgo = openpgp.key.getPreferredSymAlgo([key1, key2]); - expect(prefAlgo).to.equal(openpgp.enums.symmetric.aes128); + return Promise.all([key1.verifyPrimaryUser(), key2.verifyPrimaryUser()]).then(() => { + key2.getPrimaryUser().selfCertificate.preferredSymmetricAlgorithms = [6,7,3]; + var prefAlgo = openpgp.key.getPreferredSymAlgo([key1, key2]); + expect(prefAlgo).to.equal(openpgp.enums.symmetric.aes128); + }); }); it('getPreferredSymAlgo() - two key - one without pref', function() { var keys = openpgp.key.readArmored(twoKeys).keys; var key1 = keys[0]; var key2 = keys[1]; - key2.getPrimaryUser().selfCertificate.preferredSymmetricAlgorithms = null; - var prefAlgo = openpgp.key.getPreferredSymAlgo([key1, key2]); - expect(prefAlgo).to.equal(openpgp.config.encryption_cipher); + return Promise.all([key1.verifyPrimaryUser(), key2.verifyPrimaryUser()]).then(() => { + key2.getPrimaryUser().selfCertificate.preferredSymmetricAlgorithms = null; + var prefAlgo = openpgp.key.getPreferredSymAlgo([key1, key2]); + expect(prefAlgo).to.equal(openpgp.config.encryption_cipher); + }); }); it('Preferences of generated key', function() { @@ -917,10 +952,12 @@ describe('Key', function() { it('getPrimaryUser()', function() { var key = openpgp.key.readArmored(pub_sig_test).keys[0]; - var primUser = key.getPrimaryUser(); - expect(primUser).to.exist; - expect(primUser.user.userId.userid).to.equal('Signature Test '); - expect(primUser.selfCertificate).to.be.an.instanceof(openpgp.packet.Signature); + return key.verifyPrimaryUser().then(() => { + var primUser = key.getPrimaryUser(); + expect(primUser).to.exist; + expect(primUser.user.userId.userid).to.equal('Signature Test '); + expect(primUser.selfCertificate).to.be.an.instanceof(openpgp.packet.Signature); + }); }); it('Generated key is not unlocked by default', function() { @@ -993,92 +1030,110 @@ describe('Key', function() { return openpgp.generateKey(opt).then(function(key) { key = key.key; - var expiration = key.getExpirationTime(); - expect(expiration).to.exist; + return key.verifyPrimaryUser().then(() => { + var expiration = key.getExpirationTime(); + expect(expiration).to.exist; - var actual_delta = (new Date(expiration) - new Date()) / 1000; - expect(Math.abs(actual_delta - expect_delta)).to.be.below(60); + var actual_delta = (new Date(expiration) - new Date()) / 1000; + expect(Math.abs(actual_delta - expect_delta)).to.be.below(60); - var subKeyExpiration = key.subKeys[0].getExpirationTime(); - expect(subKeyExpiration).to.exist; + var subKeyExpiration = key.subKeys[0].getExpirationTime(); + expect(subKeyExpiration).to.exist; - var actual_subKeyDelta = (new Date(subKeyExpiration) - new Date()) / 1000; - expect(Math.abs(actual_subKeyDelta - expect_delta)).to.be.below(60); + var actual_subKeyDelta = (new Date(subKeyExpiration) - new Date()) / 1000; + expect(Math.abs(actual_subKeyDelta - expect_delta)).to.be.below(60); + }); }); }); - it('Sign and verify key - primary user', function(done) { + it('Sign and verify key - primary user', function() { var key = openpgp.key.readArmored(pub_sig_test).keys[0]; var privateKey = openpgp.key.readArmored(priv_key_rsa).keys[0]; privateKey.decrypt('hello world'); - key = key.signPrimaryUser([privateKey]); - var signatures = key.verifyPrimaryUser([privateKey]); - expect(signatures.length).to.equal(2); - expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[0].valid).to.be.null; - expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[1].valid).to.be.true; - done(); + return key.signPrimaryUser([privateKey]).then(key => { + return Promise.all( + [key.verifyPrimaryUser([privateKey]), privateKey.verifyPrimaryUser()] + ).then(results => { + var signatures = results[0]; + expect(signatures.length).to.equal(2); + expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[0].valid).to.be.null; + expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[1].valid).to.be.true; + }); + }); }); - it('Sign key and verify with wrong key - primary user', function(done) { + it('Sign key and verify with wrong key - primary user', function() { var key = openpgp.key.readArmored(pub_sig_test).keys[0]; var privateKey = openpgp.key.readArmored(priv_key_rsa).keys[0]; var wrongKey = openpgp.key.readArmored(wrong_key).keys[0]; privateKey.decrypt('hello world'); - key = key.signPrimaryUser([privateKey]); - var signatures = key.verifyPrimaryUser([wrongKey]); - expect(signatures.length).to.equal(2); - expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[0].valid).to.be.null; - expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[1].valid).to.be.null; - done(); + return key.signPrimaryUser([privateKey]).then(key => { + return Promise.all( + [key.verifyPrimaryUser([wrongKey]), privateKey.verifyPrimaryUser()] + ).then(results => { + var signatures = results[0]; + expect(signatures.length).to.equal(2); + expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[0].valid).to.be.null; + expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[1].valid).to.be.null; + }); + }); }); - it('Sign and verify key - all users', function(done) { + it('Sign and verify key - all users', function() { var key = openpgp.key.readArmored(multi_uid_key).keys[0]; var privateKey = openpgp.key.readArmored(priv_key_rsa).keys[0]; privateKey.decrypt('hello world'); - key = key.signAllUsers([privateKey]); - var signatures = key.verifyAllUsers([privateKey]); - expect(signatures.length).to.equal(4); - expect(signatures[0].userid).to.equal(key.users[0].userId.userid); - expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[0].valid).to.be.null; - expect(signatures[1].userid).to.equal(key.users[0].userId.userid); - expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[1].valid).to.be.true; - expect(signatures[2].userid).to.equal(key.users[1].userId.userid); - expect(signatures[2].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[2].valid).to.be.null; - expect(signatures[3].userid).to.equal(key.users[1].userId.userid); - expect(signatures[3].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[3].valid).to.be.true; - done(); + return key.signAllUsers([privateKey]).then(key => { + return Promise.all( + [key.verifyAllUsers([privateKey]), key.verifyPrimaryUser(), privateKey.verifyPrimaryUser()] + ).then(results => { + var signatures = results[0]; + expect(signatures.length).to.equal(4); + expect(signatures[0].userid).to.equal(key.users[0].userId.userid); + expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[0].valid).to.be.null; + expect(signatures[1].userid).to.equal(key.users[0].userId.userid); + expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[1].valid).to.be.true; + expect(signatures[2].userid).to.equal(key.users[1].userId.userid); + expect(signatures[2].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[2].valid).to.be.null; + expect(signatures[3].userid).to.equal(key.users[1].userId.userid); + expect(signatures[3].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[3].valid).to.be.true; + }); + }); }); - it('Sign key and verify with wrong key - all users', function(done) { + it('Sign key and verify with wrong key - all users', function() { var key = openpgp.key.readArmored(multi_uid_key).keys[0]; var privateKey = openpgp.key.readArmored(priv_key_rsa).keys[0]; var wrongKey = openpgp.key.readArmored(wrong_key).keys[0]; privateKey.decrypt('hello world'); - key = key.signAllUsers([privateKey]); - var signatures = key.verifyAllUsers([wrongKey]); - expect(signatures.length).to.equal(4); - expect(signatures[0].userid).to.equal(key.users[0].userId.userid); - expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[0].valid).to.be.null; - expect(signatures[1].userid).to.equal(key.users[0].userId.userid); - expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[1].valid).to.be.null; - expect(signatures[2].userid).to.equal(key.users[1].userId.userid); - expect(signatures[2].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[2].valid).to.be.null; - expect(signatures[3].userid).to.equal(key.users[1].userId.userid); - expect(signatures[3].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); - expect(signatures[3].valid).to.be.null; - done(); + return key.signAllUsers([privateKey]).then(key => { + return Promise.all( + [key.verifyAllUsers([wrongKey]), key.verifyPrimaryUser(), privateKey.verifyPrimaryUser()] + ).then(results => { + var signatures = results[0]; + expect(signatures.length).to.equal(4); + expect(signatures[0].userid).to.equal(key.users[0].userId.userid); + expect(signatures[0].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[0].valid).to.be.null; + expect(signatures[1].userid).to.equal(key.users[0].userId.userid); + expect(signatures[1].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[1].valid).to.be.null; + expect(signatures[2].userid).to.equal(key.users[1].userId.userid); + expect(signatures[2].keyid.toHex()).to.equal(key.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[2].valid).to.be.null; + expect(signatures[3].userid).to.equal(key.users[1].userId.userid); + expect(signatures[3].keyid.toHex()).to.equal(privateKey.getSigningKeyPacket().getKeyId().toHex()); + expect(signatures[3].valid).to.be.null; + }); + }); }); it('Reformat key without passphrase', function() { var userId1 = 'test1 '; diff --git a/test/general/oid.js b/test/general/oid.js new file mode 100644 index 00000000..b4f4e68a --- /dev/null +++ b/test/general/oid.js @@ -0,0 +1,38 @@ +'use strict'; + +var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); + +var expect = require('chai').expect; + +describe('Oid tests', function() { + var OID = openpgp.OID; + var p256_oid = new Uint8Array([0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07]); + var p384_oid = new Uint8Array([0x2B, 0x81, 0x04, 0x00, 0x22]); + var p521_oid = new Uint8Array([0x2B, 0x81, 0x04, 0x00, 0x23]); + it('Constructing', function() { + var oids = [p256_oid, p384_oid, p521_oid]; + oids.forEach(function (data) { + var oid = new OID(data); + expect(oid).to.exist; + expect(oid.oid).to.exist; + expect(oid.oid).to.have.length(data.length); + expect(oid.oid).to.equal(openpgp.util.Uint8Array2str(data)); + }); + }); + it('Reading and writing', function() { + var oids = [p256_oid, p384_oid, p521_oid]; + oids.forEach(function (data) { + data = openpgp.util.concatUint8Array([new Uint8Array([data.length]), data]); + var 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.oid).to.equal(openpgp.util.Uint8Array2str(data.subarray(1))); + var result = oid.write(); + expect(result).to.exist; + expect(result).to.have.length(data.length); + expect(result[0]).to.equal(data.length-1); + expect(openpgp.util.Uint8Array2str(result.subarray(1))).to.equal(openpgp.util.Uint8Array2str(data.subarray(1))); + }); + }); +}); diff --git a/test/general/openpgp.js b/test/general/openpgp.js index 07977350..1c373927 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -5,8 +5,9 @@ var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); var sinon = require('sinon'), - chai = require('chai'), - expect = chai.expect; + chai = require('chai'); +chai.use(require('chai-as-promised')); +var expect = chai.expect; var pub_key = ['-----BEGIN PGP PUBLIC KEY BLOCK-----', @@ -163,6 +164,18 @@ var plaintext = 'short message\nnext line\n한국어/조선말'; var password1 = 'I am a password'; var password2 = 'I am another password'; +var twoPasswordGPGFail = ['-----BEGIN PGP MESSAGE-----', +'Version: OpenPGP.js v3.0.0', +'Comment: https://openpgpjs.org', +'', +'wy4ECQMIWjj3WEfWxGpgrfb3vXu0TS9L8UNTBvNZFIjltGjMVkLFD+/afgs5', +'aXt0wy4ECQMIrFo3TFN5xqtgtB+AaAjBcWJrA4bvIPBpJ38PbMWeF0JQgrqg', +'j3uehxXy0mUB5i7B61g0ho+YplyFGM0s9XayJCnu40tWmr5LqqsRxuwrhJKR', +'migslOF/l6Y9F0F9xGIZWGhxp3ugQPjVKjj8fOH7ap14mLm60C8q8AOxiSmL', +'ubsd/hL7FPZatUYAAZVA0a6hmQ==', +'=cHCV', +'-----END PGP MESSAGE-----'].join('\n'); + describe('OpenPGP.js public api tests', function() { describe('initWorker, getWorker, destroyWorker - unit tests', function() { @@ -304,7 +317,8 @@ describe('OpenPGP.js public api tests', function() { passphrase: 'secret', numBits: 2048, unlocked: true, - keyExpirationTime: 0 + keyExpirationTime: 0, + curve: "" }).calledOnce).to.be.true; expect(newKey.key).to.exist; expect(newKey.privateKeyArmored).to.exist; @@ -319,7 +333,8 @@ describe('OpenPGP.js public api tests', function() { passphrase: undefined, numBits: 2048, unlocked: false, - keyExpirationTime: 0 + keyExpirationTime: 0, + curve: "" }).calledOnce).to.be.true; expect(newKey.key).to.exist; }); @@ -402,7 +417,7 @@ describe('OpenPGP.js public api tests', function() { describe('encrypt, decrypt, sign, verify - integration tests', function() { var privateKey, publicKey, zero_copyVal, use_nativeVal, aead_protectVal; - beforeEach(function() { + beforeEach(function(done) { publicKey = openpgp.key.readArmored(pub_key); expect(publicKey.keys).to.have.length(1); expect(publicKey.err).to.not.exist; @@ -412,6 +427,7 @@ describe('OpenPGP.js public api tests', function() { zero_copyVal = openpgp.config.zero_copy; use_nativeVal = openpgp.config.use_native; aead_protectVal = openpgp.config.aead_protect; + privateKey.keys[0].verifyPrimaryUser().then(() => done()); }); afterEach(function() { @@ -509,11 +525,12 @@ describe('OpenPGP.js public api tests', function() { }); }); - describe('encryptSessionKey, decryptSessionKey', function() { + describe('encryptSessionKey, decryptSessionKeys', function() { var sk = new Uint8Array([0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01]); - beforeEach(function() { + beforeEach(function(done) { expect(privateKey.keys[0].decrypt(passphrase)).to.be.true; + privateKey.keys[0].verifyPrimaryUser().then(() => done()); }); it('should encrypt with public key', function() { @@ -522,12 +539,12 @@ describe('OpenPGP.js public api tests', function() { algorithm: 'aes128', publicKeys: publicKey.keys }).then(function(encrypted) { - return openpgp.decryptSessionKey({ + return openpgp.decryptSessionKeys({ message: encrypted.message, privateKey: privateKey.keys[0] }); }).then(function(decrypted) { - expect(decrypted.data).to.deep.equal(sk); + expect(decrypted[0].data).to.deep.equal(sk); }); }); @@ -537,30 +554,30 @@ describe('OpenPGP.js public api tests', function() { algorithm: 'aes128', passwords: password1 }).then(function(encrypted) { - return openpgp.decryptSessionKey({ + return openpgp.decryptSessionKeys({ message: encrypted.message, password: password1 }); }).then(function(decrypted) { - expect(decrypted.data).to.deep.equal(sk); + expect(decrypted[0].data).to.deep.equal(sk); }); }); - it('roundtrip workflow: encrypt, decryptSessionKey, decrypt with pgp key pair', function() { + it('roundtrip workflow: encrypt, decryptSessionKeys, decrypt with pgp key pair', function() { var msgAsciiArmored; return openpgp.encrypt({ data: plaintext, publicKeys: publicKey.keys }).then(function(encrypted) { msgAsciiArmored = encrypted.data; - return openpgp.decryptSessionKey({ + return openpgp.decryptSessionKeys({ message: openpgp.message.readArmored(msgAsciiArmored), privateKey: privateKey.keys[0] }); - }).then(function(decryptedSessionKey) { + }).then(function(decryptedSessionKeys) { return openpgp.decrypt({ - sessionKey: decryptedSessionKey, + sessionKey: decryptedSessionKeys[0], message: openpgp.message.readArmored(msgAsciiArmored) }); @@ -569,21 +586,21 @@ describe('OpenPGP.js public api tests', function() { }); }); - it('roundtrip workflow: encrypt, decryptSessionKey, decrypt with password', function() { + it('roundtrip workflow: encrypt, decryptSessionKeys, decrypt with password', function() { var msgAsciiArmored; return openpgp.encrypt({ data: plaintext, passwords: password1 }).then(function(encrypted) { msgAsciiArmored = encrypted.data; - return openpgp.decryptSessionKey({ + return openpgp.decryptSessionKeys({ message: openpgp.message.readArmored(msgAsciiArmored), password: password1 }); - }).then(function(decryptedSessionKey) { + }).then(function(decryptedSessionKeys) { return openpgp.decrypt({ - sessionKey: decryptedSessionKey, + sessionKey: decryptedSessionKeys[0], message: openpgp.message.readArmored(msgAsciiArmored) }); @@ -591,6 +608,20 @@ describe('OpenPGP.js public api tests', function() { expect(decrypted.data).to.equal(plaintext); }); }); + + it('roundtrip workflow: encrypt twice with one password, decryptSessionKeys, only one session key', function() { + return openpgp.encrypt({ + data: plaintext, + passwords: [password1, password1] + }).then(function(encrypted) { + return openpgp.decryptSessionKeys({ + message: openpgp.message.readArmored(encrypted.data), + password: password1 + }); + }).then(function(decryptedSessionKeys) { + expect(decryptedSessionKeys.length).to.equal(1); + }); + }); }); describe('AES / RSA encrypt, decrypt, sign, verify', function() { @@ -606,8 +637,9 @@ describe('OpenPGP.js public api tests', function() { '=6XMW\r\n' + '-----END PGP PUBLIC KEY BLOCK-----\r\n\r\n'; - beforeEach(function() { + beforeEach(function(done) { expect(privateKey.keys[0].decrypt(passphrase)).to.be.true; + privateKey.keys[0].verifyPrimaryUser().then(() => done()); }); it('should encrypt then decrypt', function() { @@ -827,8 +859,10 @@ describe('OpenPGP.js public api tests', function() { expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); expect(decrypted.signatures[0].signature.packets.length).to.equal(1); expect(decrypted.signatures[1].valid).to.be.true; - expect(decrypted.signatures[1].keyid.toHex()).to.equal(privKeyDE.getSigningKeyPacket().getKeyId().toHex()); - expect(decrypted.signatures[1].signature.packets.length).to.equal(1); + return privKeyDE.verifyPrimaryUser().then(() => { + expect(decrypted.signatures[1].keyid.toHex()).to.equal(privKeyDE.getSigningKeyPacket().getKeyId().toHex()); + expect(decrypted.signatures[1].signature.packets.length).to.equal(1); + }); }); }); @@ -981,6 +1015,39 @@ describe('OpenPGP.js public api tests', function() { }); }); + it('should encrypt and decrypt/verify both signatures when signed with two private keys', function() { + var privKeyDE = openpgp.key.readArmored(priv_key_de).keys[0]; + privKeyDE.decrypt(passphrase); + + var pubKeyDE = openpgp.key.readArmored(pub_key_de).keys[0]; + + var encOpt = { + data: plaintext, + publicKeys: publicKey.keys, + privateKeys: [privateKey.keys[0], privKeyDE] + }; + + var decOpt = { + privateKey: privateKey.keys[0], + publicKeys: [publicKey.keys[0], pubKeyDE] + }; + + return openpgp.encrypt(encOpt).then(function(encrypted) { + decOpt.message = openpgp.message.readArmored(encrypted.data); + return openpgp.decrypt(decOpt); + }).then(function(decrypted) { + expect(decrypted.data).to.equal(plaintext); + expect(decrypted.signatures[0].valid).to.be.true; + expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex()); + expect(decrypted.signatures[0].signature.packets.length).to.equal(1); + expect(decrypted.signatures[1].valid).to.be.true; + return privKeyDE.verifyPrimaryUser().then(() => { + expect(decrypted.signatures[1].keyid.toHex()).to.equal(privKeyDE.getSigningKeyPacket().getKeyId().toHex()); + expect(decrypted.signatures[1].signature.packets.length).to.equal(1); + }); + }); + }); + it('should sign and verify cleartext data', function() { var signOpt = { data: plaintext, @@ -1124,8 +1191,10 @@ describe('OpenPGP.js public api tests', function() { expect(encrypted.data).to.exist; expect(encrypted.data).to.equal(plaintext); expect(encrypted.signatures[0].valid).to.be.true; - expect(encrypted.signatures[0].keyid.toHex()).to.equal(privKeyDE.getSigningKeyPacket().getKeyId().toHex()); - expect(encrypted.signatures[0].signature.packets.length).to.equal(1); + return privKeyDE.verifyPrimaryUser().then(() => { + expect(encrypted.signatures[0].keyid.toHex()).to.equal(privKeyDE.getSigningKeyPacket().getKeyId().toHex()); + expect(encrypted.signatures[0].signature.packets.length).to.equal(1); + }); }); }); }); @@ -1231,6 +1300,18 @@ describe('OpenPGP.js public api tests', function() { }); }); + it('should decrypt with two passwords message which GPG fails on', function() { + + var decOpt = { + message: openpgp.message.readArmored(twoPasswordGPGFail), + password: password2 + }; + return openpgp.decrypt(decOpt).then(function(decrypted) { + expect(decrypted.data).to.equal(plaintext); + expect(decrypted.signatures.length).to.equal(0); + }); + }); + it('should encrypt and decrypt with password and not ascii armor', function() { var encOpt = { data: plaintext, diff --git a/test/general/packet.js b/test/general/packet.js index 431e1f3a..e5637348 100644 --- a/test/general/packet.js +++ b/test/general/packet.js @@ -2,6 +2,10 @@ var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); +var chai = require('chai'); +chai.use(require('chai-as-promised')); +var expect = chai.expect; + function stringify(array) { if(!Uint8Array.prototype.isPrototypeOf(array)) { throw new Error('Data must be in the form of a Uint8Array'); @@ -14,9 +18,6 @@ function stringify(array) { return result.join(''); } -var chai = require('chai'), - expect = chai.expect; - describe("Packet", function() { var armored_key = '-----BEGIN PGP PRIVATE KEY BLOCK-----\n' + @@ -191,16 +192,18 @@ describe("Packet", function() { enc.publicKeyAlgorithm = 'rsa_encrypt'; enc.sessionKeyAlgorithm = 'aes256'; enc.publicKeyId.bytes = '12345678'; - enc.encrypt({ mpi: mpi }); + enc.encrypt({ params: mpi }).then(() => { - msg.push(enc); + msg.push(enc); - msg2.read(msg.write()); + msg2.read(msg.write()); - msg2[0].decrypt({ mpi: mpi }); + msg2[0].decrypt({ params: mpi }).then(() => { - expect(stringify(msg2[0].sessionKey)).to.equal(stringify(enc.sessionKey)); - expect(msg2[0].sessionKeyAlgorithm).to.equal(enc.sessionKeyAlgorithm); + expect(stringify(msg2[0].sessionKey)).to.equal(stringify(enc.sessionKey)); + expect(msg2[0].sessionKeyAlgorithm).to.equal(enc.sessionKeyAlgorithm); + }); + }); }); }); @@ -239,12 +242,14 @@ describe("Packet", function() { enc.sessionKeyAlgorithm = 'aes256'; enc.publicKeyId.bytes = '12345678'; - enc.encrypt(key); + enc.encrypt(key).then(() => { - enc.decrypt(key); + enc.decrypt(key).then(() => { - expect(stringify(enc.sessionKey)).to.equal(stringify(secret)); - done(); + expect(stringify(enc.sessionKey)).to.equal(stringify(secret)); + done(); + }); + }); }); it('Public key encrypted packet (reading, GPG)', function(done) { @@ -302,13 +307,14 @@ describe("Packet", function() { var msg = new openpgp.packet.List(); msg.read(openpgp.armor.decode(armored_msg).data); - msg[0].decrypt(key); - msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey); + msg[0].decrypt(key).then(() => { + msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey); - var text = stringify(msg[1].packets[0].packets[0].data); + var text = stringify(msg[1].packets[0].packets[0].data); - expect(text).to.equal('Hello world!'); - done(); + expect(text).to.equal('Hello world!'); + done(); + }); }); it('Sym encrypted session key reading/writing', function(done) { @@ -332,7 +338,6 @@ describe("Packet", function() { enc.packets.push(literal); enc.encrypt(algo, key); - var msg2 = new openpgp.packet.List(); msg2.read(msg.write()); @@ -365,34 +370,31 @@ describe("Packet", function() { var msg = new openpgp.packet.List(); msg.read(openpgp.armor.decode(armored_msg).data); - msg[0].decrypt(key); - msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey); + msg[0].decrypt(key).then(() => { + msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey); - var text = stringify(msg[1].packets[0].packets[0].data); + var text = stringify(msg[1].packets[0].packets[0].data); - expect(text).to.equal('Hello world!'); - done(); + expect(text).to.equal('Hello world!'); + done(); + }); }); - it('Secret key reading with signature verification.', function(done) { + it('Secret key reading with signature verification.', function() { var key = new openpgp.packet.List(); key.read(openpgp.armor.decode(armored_key).data); - - - var verified = key[2].verify(key[0], + return Promise.all([ + expect(key[2].verify(key[0], { userid: key[1], key: key[0] - }); - - verified = verified && key[4].verify(key[0], + })).to.eventually.be.true, + expect(key[4].verify(key[0], { key: key[0], bind: key[3] - }); - - expect(verified).to.be.true; - done(); + })).to.eventually.be.true + ]); }); it('Reading a signed, encrypted message.', function(done) { @@ -419,15 +421,15 @@ describe("Packet", function() { var msg = new openpgp.packet.List(); msg.read(openpgp.armor.decode(armored_msg).data); - msg[0].decrypt(key[3]); - msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey); + msg[0].decrypt(key[3]).then(() => { + msg[1].decrypt(msg[0].sessionKeyAlgorithm, msg[0].sessionKey); - var payload = msg[1].packets[0].packets; + var payload = msg[1].packets[0].packets; - var verified = payload[2].verify(key[0], payload[1]); - - expect(verified).to.be.true; - done(); + expect(payload[2].verify( + key[0], payload[1] + )).to.eventually.be.true.notify(done); + }); }); it('Writing and encryption of a secret key packet.', function() { @@ -445,8 +447,8 @@ describe("Packet", function() { return mpi; }); - key[0].mpi = mpi; - + key[0].params = mpi; + key[0].algorithm = "rsa_sign"; key[0].encrypt('hello'); var raw = key.write(); @@ -455,7 +457,7 @@ describe("Packet", function() { key2.read(raw); key2[0].decrypt('hello'); - expect(key[0].mpi.toString()).to.equal(key2[0].mpi.toString()); + expect(key[0].params.toString()).to.equal(key2[0].params.toString()); }); }); @@ -473,7 +475,8 @@ describe("Packet", function() { return mpi; }); - key.mpi = mpi; + key.params = mpi; + key.algorithm = "rsa_sign"; var signed = new openpgp.packet.List(), literal = new openpgp.packet.Literal(), @@ -485,19 +488,18 @@ describe("Packet", function() { signature.publicKeyAlgorithm = 'rsa_sign'; signature.signatureType = 'binary'; - signature.sign(key, literal); + signature.sign(key, literal).then(() => { - signed.push(literal); - signed.push(signature); + signed.push(literal); + signed.push(signature); - var raw = signed.write(); + var raw = signed.write(); - var signed2 = new openpgp.packet.List(); - signed2.read(raw); + var signed2 = new openpgp.packet.List(); + signed2.read(raw); - var verified = signed2[1].verify(key, signed2[0]); - - expect(verified).to.be.true; + expect(signed2[1].verify(key, signed2[0])).to.eventually.be.true; + }); }); }); }); diff --git a/test/general/signature.js b/test/general/signature.js index 7c287205..1c1089bd 100644 --- a/test/general/signature.js +++ b/test/general/signature.js @@ -2,8 +2,9 @@ var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); -var chai = require('chai'), - expect = chai.expect; +var chai = require('chai'); +chai.use(require('chai-as-promised')); +var expect = chai.expect; describe("Signature", function() { var priv_key_arm1 = @@ -382,15 +383,16 @@ describe("Signature", function() { priv_key_gnupg_ext.subKeys[0].subKey.decrypt("abcd"); return msg.decrypt(priv_key_gnupg_ext).then(function(msg) { - var verified = msg.verify([pub_key]); - expect(verified).to.exist; - expect(verified).to.have.length(1); - expect(verified[0].valid).to.be.true; - expect(verified[0].signature.packets.length).to.equal(1); + return msg.verify([pub_key]).then(verified => { + expect(verified).to.exist; + expect(verified).to.have.length(1); + expect(verified[0].valid).to.be.true; + expect(verified[0].signature.packets.length).to.equal(1); + }); }); }); - it('Verify V4 signature. Hash: SHA1. PK: RSA. Signature Type: 0x00 (binary document)', function(done) { + it('Verify V4 signature. Hash: SHA1. PK: RSA. Signature Type: 0x00 (binary document)', function() { var signedArmor = [ '-----BEGIN PGP MESSAGE-----', 'Version: GnuPG v2.0.19 (GNU/Linux)', @@ -406,15 +408,15 @@ describe("Signature", function() { var sMsg = openpgp.message.readArmored(signedArmor); var pub_key = openpgp.key.readArmored(pub_key_arm2).keys[0]; - var verified = sMsg.verify([pub_key]); - expect(verified).to.exist; - expect(verified).to.have.length(1); - expect(verified[0].valid).to.be.true; - expect(verified[0].signature.packets.length).to.equal(1); - done(); + return sMsg.verify([pub_key]).then(verified => { + expect(verified).to.exist; + expect(verified).to.have.length(1); + expect(verified[0].valid).to.be.true; + expect(verified[0].signature.packets.length).to.equal(1); + }); }); - it('Verify V3 signature. Hash: MD5. PK: RSA. Signature Type: 0x01 (text document)', function(done) { + it('Verify V3 signature. Hash: MD5. PK: RSA. Signature Type: 0x01 (text document)', function() { var signedArmor = [ '-----BEGIN PGP MESSAGE-----', 'Version: GnuPG v2.0.19 (GNU/Linux)', @@ -430,12 +432,12 @@ describe("Signature", function() { var sMsg = openpgp.message.readArmored(signedArmor); var pub_key = openpgp.key.readArmored(pub_key_arm2).keys[0]; - var verified = sMsg.verify([pub_key]); - expect(verified).to.exist; - expect(verified).to.have.length(1); - expect(verified[0].valid).to.be.true; - expect(verified[0].signature.packets.length).to.equal(1); - done(); + return sMsg.verify([pub_key]).then(verified => { + expect(verified).to.exist; + expect(verified).to.have.length(1); + expect(verified[0].valid).to.be.true; + expect(verified[0].signature.packets.length).to.equal(1); + }); }); it('Verify signature of signed and encrypted message from GPG2 with openpgp.decrypt', function() { @@ -510,7 +512,7 @@ describe("Signature", function() { }); - it('Verify signed message with two one pass signatures', function(done) { + it('Verify signed message with two one pass signatures', function() { var msg_armor = [ '-----BEGIN PGP MESSAGE-----', 'Version: GnuPG v2.0.19 (GNU/Linux)', @@ -542,15 +544,14 @@ describe("Signature", function() { expect(sMsg.getText()).to.equal(plaintext); - var verifiedSig = sMsg.verify([pubKey2, pubKey3]); - - expect(verifiedSig).to.exist; - expect(verifiedSig).to.have.length(2); - expect(verifiedSig[0].valid).to.be.true; - expect(verifiedSig[1].valid).to.be.true; - expect(verifiedSig[0].signature.packets.length).to.equal(1); - expect(verifiedSig[1].signature.packets.length).to.equal(1); - done(); + return sMsg.verify([pubKey2, pubKey3]).then(verifiedSig => { + expect(verifiedSig).to.exist; + expect(verifiedSig).to.have.length(2); + expect(verifiedSig[0].valid).to.be.true; + expect(verifiedSig[1].valid).to.be.true; + expect(verifiedSig[0].signature.packets.length).to.equal(1); + expect(verifiedSig[1].signature.packets.length).to.equal(1); + }); }); it('Verify cleartext signed message with two signatures with openpgp.verify', function() { @@ -603,7 +604,7 @@ describe("Signature", function() { var plaintext = 'short message\nnext line\n한국어/조선말'; var pubKey = openpgp.key.readArmored(pub_key_arm2).keys[0]; var privKey = openpgp.key.readArmored(priv_key_arm2).keys[0]; - privKey.getSigningKeyPacket().decrypt('hello world'); + privKey.primaryKey.decrypt('hello world'); return openpgp.sign({ privateKeys:[privKey], data:plaintext }).then(function(signed) { @@ -617,16 +618,16 @@ describe("Signature", function() { expect(cleartextSig.signatures[0].valid).to.be.true; expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1); }); - }); it('Sign text with openpgp.sign and verify with openpgp.verify leads to same bytes cleartext and valid signatures - armored', function() { var plaintext = openpgp.util.str2Uint8Array('short message\nnext line\n한국어/조선말'); var pubKey = openpgp.key.readArmored(pub_key_arm2).keys[0]; var privKey = openpgp.key.readArmored(priv_key_arm2).keys[0]; - privKey.getSigningKeyPacket().decrypt('hello world'); + privKey.primaryKey.decrypt('hello world'); return openpgp.sign({ privateKeys:[privKey], data:plaintext }).then(function(signed) { + var csMsg = openpgp.message.readArmored(signed.data); return openpgp.verify({ publicKeys:[pubKey], message:csMsg }); @@ -637,16 +638,16 @@ describe("Signature", function() { expect(cleartextSig.signatures[0].valid).to.be.true; expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1); }); - }); it('Sign text with openpgp.sign and verify with openpgp.verify leads to same bytes cleartext and valid signatures - not armored', function() { var plaintext = openpgp.util.str2Uint8Array('short message\nnext line\n한국어/조선말'); var pubKey = openpgp.key.readArmored(pub_key_arm2).keys[0]; var privKey = openpgp.key.readArmored(priv_key_arm2).keys[0]; - privKey.getSigningKeyPacket().decrypt('hello world'); + privKey.primaryKey.decrypt('hello world'); return openpgp.sign({ privateKeys:[privKey], data:plaintext, armor:false }).then(function(signed) { + var csMsg = signed.message; return openpgp.verify({ publicKeys:[pubKey], message:csMsg }); @@ -657,6 +658,31 @@ describe("Signature", function() { expect(cleartextSig.signatures[0].valid).to.be.true; expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1); }); + }); + + it('Verify test with expired verification public key and verify_expired_keys set to false', function() { + openpgp.config.verify_expired_keys = false; + var pubKey = openpgp.key.readArmored(pub_expired).keys[0]; + var message = 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); + expect(verified.signatures[0].valid).to.not.be.true; + expect(verified.signatures[0].signature.packets.length).to.equal(1); + }); + + }); + + it('Verify test with expired verification public key and verify_expired_keys set to true', function() { + openpgp.config.verify_expired_keys = true; + var pubKey = openpgp.key.readArmored(pub_expired).keys[0]; + var message = 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); + expect(verified.signatures[0].valid).to.be.true; + expect(verified.signatures[0].signature.packets.length).to.equal(1); + }); }); @@ -688,20 +714,16 @@ describe("Signature", function() { it('Verify primary key revocation signature', function(done) { var pubKey = openpgp.key.readArmored(pub_revoked).keys[0]; - - var verified = pubKey.revocationSignature.verify(pubKey.primaryKey, {key: pubKey.primaryKey}); - - expect(verified).to.be.true; - done(); + expect(pubKey.revocationSignature.verify( + pubKey.primaryKey, {key: pubKey.primaryKey} + )).to.eventually.be.true.notify(done); }); it('Verify subkey revocation signature', function(done) { var pubKey = openpgp.key.readArmored(pub_revoked).keys[0]; - - var verified = pubKey.subKeys[0].revocationSignature.verify(pubKey.primaryKey, {key: pubKey.primaryKey, bind: pubKey.subKeys[0].subKey}); - - expect(verified).to.be.true; - done(); + expect(pubKey.subKeys[0].revocationSignature.verify( + pubKey.primaryKey, {key: pubKey.primaryKey, bind: pubKey.subKeys[0].subKey} + )).to.eventually.be.true.notify(done); }); it('Verify key expiration date', function(done) { @@ -715,11 +737,7 @@ describe("Signature", function() { it('Verify V3 certification signature', function(done) { var pubKey = openpgp.key.readArmored(pub_v3).keys[0]; - - var verified = pubKey.users[0].selfCertifications[0].verify(pubKey.primaryKey, {key: pubKey.primaryKey, userid: pubKey.users[0].userId}); - - expect(verified).to.be.true; - done(); + expect(pubKey.users[0].selfCertifications[0].verify(pubKey.primaryKey, {key: pubKey.primaryKey, userid: pubKey.users[0].userId})).to.eventually.be.true.notify(done); }); it('Write unhashed subpackets', function() { @@ -785,8 +803,9 @@ describe("Signature", function() { var publicKeys = openpgp.key.readArmored(publicKeyArmored).keys; var msg = openpgp.message.readSignedContent(content, detachedSig); - var result = msg.verify(publicKeys); - expect(result[0].valid).to.be.true; + return msg.verify(publicKeys).then(result => { + expect(result[0].valid).to.be.true; + }); }); it('Detached signature signing and verification', function() { @@ -799,10 +818,12 @@ describe("Signature", function() { if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys return openpgp.generateKey(opt).then(function(gen) { var generatedKey = gen.key; - var detachedSig = msg.signDetached([generatedKey, privKey2]); - var result = msg.verifyDetached(detachedSig, [generatedKey.toPublic(), pubKey2]); - expect(result[0].valid).to.be.true; - expect(result[1].valid).to.be.true; + return msg.signDetached([generatedKey, privKey2]).then(detachedSig => { + return msg.verifyDetached(detachedSig, [generatedKey.toPublic(), pubKey2]).then(result => { + expect(result[0].valid).to.be.true; + expect(result[1].valid).to.be.true; + }); + }); }); }); @@ -817,7 +838,7 @@ describe("Signature", function() { }); }); - it('Verify signed key', function(done) { + it('Verify signed key', function() { var signedArmor = [ '-----BEGIN PGP PUBLIC KEY BLOCK-----', 'Version: GnuPG v1', @@ -847,12 +868,12 @@ describe("Signature", function() { var signedKey = openpgp.key.readArmored(signedArmor).keys[0]; var signerKey = openpgp.key.readArmored(priv_key_arm1).keys[0]; - var signatures = signedKey.verifyPrimaryUser([signerKey]); - expect(signatures[0].valid).to.be.null; - expect(signatures[0].keyid.toHex()).to.equal(signedKey.primaryKey.getKeyId().toHex()); - expect(signatures[1].valid).to.be.true; - expect(signatures[1].keyid.toHex()).to.equal(signerKey.primaryKey.getKeyId().toHex()); - done(); + return signedKey.verifyPrimaryUser([signerKey]).then(signatures => { + expect(signatures[0].valid).to.be.null; + expect(signatures[0].keyid.toHex()).to.equal(signedKey.primaryKey.getKeyId().toHex()); + expect(signatures[1].valid).to.be.true; + expect(signatures[1].keyid.toHex()).to.equal(signerKey.primaryKey.getKeyId().toHex()); + }); }); }); diff --git a/test/general/util.js b/test/general/util.js index 06d75bb6..b3b6a72d 100644 --- a/test/general/util.js +++ b/test/general/util.js @@ -87,6 +87,10 @@ describe('Util unit tests', function() { var data = 'test@example.com'; expect(openpgp.util.isEmailAddress(data)).to.be.true; }); + it('should return true for valid email address', function() { + var data = 'test@xn--wgv.xn--q9jyb4c'; + expect(openpgp.util.isEmailAddress(data)).to.be.true; + }); it('should return false for invalid email address', function() { var data = 'Test User '; expect(openpgp.util.isEmailAddress(data)).to.be.false; diff --git a/test/general/x25519.js b/test/general/x25519.js new file mode 100644 index 00000000..fc43bad8 --- /dev/null +++ b/test/general/x25519.js @@ -0,0 +1,522 @@ +'use strict'; + +var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); +var elliptic = openpgp.crypto.publicKey.elliptic; + +var chai = require('chai'); +chai.use(require('chai-as-promised')); +var expect = chai.expect; + +describe('X25519 Cryptography', function () { + var data = { + light: { + id: '1ecdf026c0245830', + pass: 'sun', + pub: [ + '-----BEGIN PGP PUBLIC KEY BLOCK-----', + '', + 'mDMEWkN+5BYJKwYBBAHaRw8BAQdAIGqj23Kp273IPkgjwA7ue5MDIRAfWLYRqnFy', + 'c2AFMcC0EUxpZ2h0IDxsaWdodEBzdW4+iJAEExYIADgWIQSGS0GuVELT3Rs0woce', + 'zfAmwCRYMAUCWkN+5AIbAwULCQgHAgYVCAkKCwIEFgIDAQIeAQIXgAAKCRAezfAm', + 'wCRYMLteAQCFZcl8kBxCH86wmqpc2+KtEA8l/hsfh7jd+JWuyFuuRAD7BOix8Vo1', + 'P/hv8qUYwSn3IRXPeGXucoWVoKGgxRd+zAO4OARaQ37kEgorBgEEAZdVAQUBAQdA', + 'L1KkHCFxtK1CgvZlInT/y6OQeCfXiYzd/i452t2ZR2ADAQgHiHgEGBYIACAWIQSG', + 'S0GuVELT3Rs0wocezfAmwCRYMAUCWkN+5AIbDAAKCRAezfAmwCRYMJ71AQDmoQTg', + '36pfjrl82srS6XPRJxl3r/6lpWGaNij0VptB2wEA2V10ifOhnwILCw1qBle6On7a', + 'Ba257lrFM+cOSMaEsgo=', + '=D8HS', + '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'), + priv: [ + '-----BEGIN PGP PRIVATE KEY BLOCK-----', + '', + 'lIYEWkN+5BYJKwYBBAHaRw8BAQdAIGqj23Kp273IPkgjwA7ue5MDIRAfWLYRqnFy', + 'c2AFMcD+BwMCeaL+cNXzgI7uJQ7HBv53TAXO3y5uyJQMonkFtQtldL8YDbNP3pbd', + '3zzo9fxU12bWAJyFwBlBWJqkrxZN+0jt0ElsG3kp+V67MESJkrRhKrQRTGlnaHQg', + 'PGxpZ2h0QHN1bj6IkAQTFggAOBYhBIZLQa5UQtPdGzTChx7N8CbAJFgwBQJaQ37k', + 'AhsDBQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJEB7N8CbAJFgwu14BAIVlyXyQ', + 'HEIfzrCaqlzb4q0QDyX+Gx+HuN34la7IW65EAPsE6LHxWjU/+G/ypRjBKfchFc94', + 'Ze5yhZWgoaDFF37MA5yLBFpDfuQSCisGAQQBl1UBBQEBB0AvUqQcIXG0rUKC9mUi', + 'dP/Lo5B4J9eJjN3+Ljna3ZlHYAMBCAf+BwMCvyW2D5Yx6dbujE3yHi1XQ9MbhOY5', + 'XRFFgYIUYzzi1qmaL+8Gr9zODsUdeO60XHnMXOmqVa6/sdx32TWo5s3sgS19kRUM', + 'D+pbxS/aZnxvrYh4BBgWCAAgFiEEhktBrlRC090bNMKHHs3wJsAkWDAFAlpDfuQC', + 'GwwACgkQHs3wJsAkWDCe9QEA5qEE4N+qX465fNrK0ulz0ScZd6/+paVhmjYo9Fab', + 'QdsBANlddInzoZ8CCwsNagZXujp+2gWtue5axTPnDkjGhLIK', + '=wo91', + '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'), + message: 'Hi, Light wrote this!', + message_signed: [ + '-----BEGIN PGP SIGNED MESSAGE-----', + 'Hash: SHA256', + '', + 'Hi, Light wrote this!', + '-----BEGIN PGP SIGNATURE-----', + '', + 'iIAEARYIACgWIQSGS0GuVELT3Rs0wocezfAmwCRYMAUCWkyVkAocbGlnaHRAc3Vu', + 'AAoJEB7N8CbAJFgwdqAA/RwTsy9Nt5HEJLnokUNgHVX8wNr7Ef9wfAG1RaMgMMWs', + 'AP9KEEohpHqaj8smb1oLjYU9DgOugE40LrkujvnWNbOZBQ==', + '=T9p+', + '-----END PGP SIGNATURE-----'].join('\n') + }, + night: { + id: 'f25e5f24bb372cfa', + pass: 'moon', + pub: [ + '-----BEGIN PGP PUBLIC KEY BLOCK-----', + '', + 'mDMEWkN/RRYJKwYBBAHaRw8BAQdAM359sYg+LtcQo9G+mzMwxiu6wgY7UTVyip+V', + 'y8CWMhy0Ek5pZ2h0IDxuaWdodEBtb29uPoiQBBMWCAA4FiEEdracm9388E/nI0Df', + '8l5fJLs3LPoFAlpDf0UCGwMFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQ8l5f', + 'JLs3LPqoFAD+IkES10NVLoInYf6rMcxKY2/Nn+Dg4aYtdvphY8hY0b0A/jl34YEe', + 'cZAQvGWueGa5X2sCJvR1WZEMUWjW9cfR0TIHuDgEWkN/RRIKKwYBBAGXVQEFAQEH', + 'QCeuETdjFsEorruYHXmASKo7VNVgm29EZeA4bgbX1gsVAwEIB4h4BBgWCAAgFiEE', + 'dracm9388E/nI0Df8l5fJLs3LPoFAlpDf0UCGwwACgkQ8l5fJLs3LPojTgEApyg3', + 'Gd7R77zhC8mkSDIssegrFCoLqDgNYOSISgixUdgA/j7tIDGF45C9JC4LQsjfKY9W', + 'Td0I97hWRfub9tYo0P8K', + '=nbhM', + '-----END PGP PUBLIC KEY BLOCK-----'].join('\n'), + priv: [ + '-----BEGIN PGP PRIVATE KEY BLOCK-----', + '', + 'lIYEWkN/RRYJKwYBBAHaRw8BAQdAM359sYg+LtcQo9G+mzMwxiu6wgY7UTVyip+V', + 'y8CWMhz+BwMCxwCG2X+GJp7uQHSoj4fmvArR8d9hzyKBKDX84QsC1nCqMNRARz1v', + 'aSqXfCt4gLzR3sZh4yS0cDUB0UdDfFhh3XiG2j8zRJ3cKkXdV3GcSbQSTmlnaHQg', + 'PG5pZ2h0QG1vb24+iJAEExYIADgWIQR2tpyb3fzwT+cjQN/yXl8kuzcs+gUCWkN/', + 'RQIbAwULCQgHAgYVCAkKCwIEFgIDAQIeAQIXgAAKCRDyXl8kuzcs+qgUAP4iQRLX', + 'Q1Uugidh/qsxzEpjb82f4ODhpi12+mFjyFjRvQD+OXfhgR5xkBC8Za54ZrlfawIm', + '9HVZkQxRaNb1x9HRMgeciwRaQ39FEgorBgEEAZdVAQUBAQdAJ64RN2MWwSiuu5gd', + 'eYBIqjtU1WCbb0Rl4DhuBtfWCxUDAQgH/gcDAoeG6mA2BitC7sbt5erYFzAndJx3', + 'fOBDIo9MF2xo/JX1OrL5Z9Fro1UP+A3P+YyZQ3W/PMMVFArfnyiEoJAmQOkashgd', + 'CocKYaKUNrgbYl2IeAQYFggAIBYhBHa2nJvd/PBP5yNA3/JeXyS7Nyz6BQJaQ39F', + 'AhsMAAoJEPJeXyS7Nyz6I04BAKcoNxne0e+84QvJpEgyLLHoKxQqC6g4DWDkiEoI', + 'sVHYAP4+7SAxheOQvSQuC0LI3ymPVk3dCPe4VkX7m/bWKND/Cg==', + '=NDSU', + '-----END PGP PRIVATE KEY BLOCK-----'].join('\n'), + message: 'Oh hi, this is a private message from Light to Night!', + message_encrypted: [ + '-----BEGIN PGP MESSAGE-----', + '', + 'hF4DzfwiGcVT05ISAQdAetSWotgG0+MTEfyKvagrHAeGw0Denjph+Mu2KcpAajIw', + 'kE398hrqnc6qYFdf3p761kzvgjX0auua8L2WFlhAzGh1ULodxHVLmvxwiId4JwHq', + '0sAzAaM+Vn5hfDM5799p2DpPK8635LN0UvtlOqGIdaNfu5DgfoherMSb3zlBa4YF', + 'WJG1Fa9glfWTOlMNKKoFl4LUh1BUF4TbqUv3a0BR6GcDy6zSp4KRl3NIq22fUD/F', + 'BZWuhPRhnsvDAoBTbvlgjyuActYhtXU5srMAEh4UeVvKyU8xImDfLgJReU4500JU', + 'VjZkMXTileVhAprvE5KCCDWi6YWzV+SSpn+VhtnShAfoF870GI+DOnvFwEnhQlol', + 'JRZdfjq5haoEjWTuqSIS+O40AgmQYPIjnO5ALehFuWTHKLDFVv4EDqx7MatXZidz', + 'drpAMWGi', + '=erKa', + '-----END PGP MESSAGE-----'].join('\n') + } + }; + + function load_pub_key(name) { + if (data[name].pub_key) { + return data[name].pub_key; + } + var pub = 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].primaryKey.getKeyId().toHex()).to.equal(data[name].id); + data[name].pub_key = pub.keys[0]; + return data[name].pub_key; + } + function load_priv_key(name) { + if (data[name].priv_key) { + return data[name].priv_key; + } + var pk = openpgp.key.readArmored(data[name].priv); + expect(pk).to.exist; + expect(pk.err).to.not.exist; + expect(pk.keys).to.have.length(1); + expect(pk.keys[0].primaryKey.getKeyId().toHex()).to.equal(data[name].id); + expect(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', function (done) { + load_pub_key('light'); + load_pub_key('night'); + done(); + }); + it('Load private key', function (done) { + load_priv_key('light'); + load_priv_key('night'); + done(); + }).timeout(10000); + it('Verify clear signed message', function () { + var name = 'light'; + var pub = load_pub_key(name); + var msg = openpgp.cleartext.readArmored(data[name].message_signed); + return openpgp.verify({publicKeys: [pub], message: msg}).then(function(result) { + expect(result).to.exist; + expect(result.data.trim()).to.equal(data[name].message); + expect(result.signatures).to.have.length(1); + expect(result.signatures[0].valid).to.be.true; + }); + }); + it('Sign message', function () { + var name = 'light' + var priv = load_priv_key(name); + return openpgp.sign({privateKeys: [priv], data: data[name].message + "\n"}).then(function (signed) { + var pub = load_pub_key(name); + var msg = openpgp.cleartext.readArmored(signed.data); + return openpgp.verify({publicKeys: [pub], message: msg}).then(function (result) { + expect(result).to.exist; + expect(result.data.trim()).to.equal(data[name].message); + expect(result.signatures).to.have.length(1); + expect(result.signatures[0].valid).to.be.true; + }); + }); + }); + it('Decrypt and verify message', function () { + var light = load_pub_key('light'); + var night = load_priv_key('night'); + expect(night.decrypt(data['night'].pass)).to.be.true; + var msg = openpgp.message.readArmored(data['night'].message_encrypted); + return openpgp.decrypt( + {privateKey: night, publicKeys: [light], message: msg} + ).then(function (result) { + expect(result).to.exist; + // trim required because https://github.com/openpgpjs/openpgpjs/issues/311 + expect(result.data.trim()).to.equal(data['night'].message); + expect(result.signatures).to.have.length(1); + expect(result.signatures[0].valid).to.be.true; + }); + }); + it('Encrypt and sign message', function () { + var night = load_pub_key('night'); + var light = load_priv_key('light'); + expect(light.decrypt(data['light'].pass)).to.be.true; + openpgp.encrypt( + {publicKeys: [night], privateKeys: [light], data: data['light'].message + "\n"} + ).then(function (encrypted) { + var message = openpgp.message.readArmored(encrypted.data); + var light = load_pub_key('light'); + var night = load_priv_key('night'); + return openpgp.decrypt( + {privateKey: night, publicKeys: [light], message: message} + ).then(function (result) { + expect(result).to.exist; + expect(result.data.trim()).to.equal(data['light'].message); + expect(result.signatures).to.have.length(1); + expect(result.signatures[0].valid).to.be.true; + }); + }); + }); + + // TODO generate, export, then reimport key and validate + it('Omnibus Ed25519/Curve25519 Test', function () { + var options = { + userIds: {name: "Hi", email: "hi@hel.lo"}, + curve: "ed25519" + }; + return openpgp.generateKey(options).then(function (firstKey) { + expect(firstKey).to.exist; + expect(firstKey.privateKeyArmored).to.exist; + expect(firstKey.publicKeyArmored).to.exist; + expect(firstKey.key).to.exist; + expect(firstKey.key.primaryKey).to.exist; + expect(firstKey.key.subKeys).to.have.length(1); + expect(firstKey.key.subKeys[0].subKey).to.exist; + + var hi = firstKey.key; + var primaryKey = hi.primaryKey; + var subKey = hi.subKeys[0].subKey; + expect(primaryKey.params[0].oid).to.equal(elliptic.get('ed25519').oid); + expect(primaryKey.algorithm).to.equal('eddsa'); + expect(subKey.params[0].oid).to.equal(elliptic.get('curve25519').oid); + expect(subKey.algorithm).to.equal('ecdh'); + + // Self Certificate is valid + var user = hi.users[0] + expect(user.selfCertifications[0].verify( + primaryKey, {userid: user.userId, key: primaryKey} + )).to.eventually.be.true; + expect(user.verifyCertificate( + primaryKey, user.selfCertifications[0], [hi.toPublic()] + )).to.eventually.equal(openpgp.enums.keyStatus.valid); + + var options = { + userIds: {name: "Bye", email: "bye@good.bye"}, + curve: "curve25519" + }; + return openpgp.generateKey(options).then(function (secondKey) { + var bye = secondKey.key; + expect(bye.primaryKey.params[0].oid).to.equal(elliptic.get('ed25519').oid); + expect(bye.primaryKey.algorithm).to.equal('eddsa'); + expect(bye.subKeys[0].subKey.params[0].oid).to.equal(elliptic.get('curve25519').oid); + expect(bye.subKeys[0].subKey.algorithm).to.equal('ecdh'); + + // Self Certificate is valid + var user = bye.users[0] + expect(user.selfCertifications[0].verify( + bye.primaryKey, {userid: user.userId, key: bye.primaryKey} + )).to.eventually.be.true; + expect(user.verifyCertificate( + bye.primaryKey, user.selfCertifications[0], [bye.toPublic()] + )).to.eventually.equal(openpgp.enums.keyStatus.valid); + + return Promise.all([ + // Hi trusts Bye! + bye.toPublic().signPrimaryUser([ hi ]).then(trustedBye => { + expect(trustedBye.users[0].otherCertifications[0].verify( + primaryKey, { userid: user.userId, key: bye.toPublic().primaryKey } + )).to.eventually.be.true; + }), + // Signing message + openpgp.sign( + { data: 'Hi, this is me, Hi!', privateKeys: hi } + ).then(signed => { + var msg = openpgp.cleartext.readArmored(signed.data); + // Verifying signed message + return Promise.all([ + openpgp.verify( + { message: msg, publicKeys: hi.toPublic() } + ).then(output => expect(output.signatures[0].valid).to.be.true), + // Verifying detached signature + openpgp.verify( + { message: openpgp.message.fromText('Hi, this is me, Hi!'), + publicKeys: hi.toPublic(), + signature: openpgp.signature.readArmored(signed.data) } + ).then(output => expect(output.signatures[0].valid).to.be.true) + ]); + }), + // Encrypting and signing + openpgp.encrypt( + { data: 'Hi, Hi wrote this but only Bye can read it!', + publicKeys: [ bye.toPublic() ], + privateKeys: [ hi ] } + ).then(encrypted => { + var msg = openpgp.message.readArmored(encrypted.data) + // Decrypting and verifying + return openpgp.decrypt( + { message: msg, + privateKey: bye, + publicKeys: [ hi.toPublic() ] } + ).then(output => { + expect(output.data).to.equal('Hi, Hi wrote this but only Bye can read it!'); + expect(output.signatures[0].valid).to.be.true; + }); + }) + ]); + }); + }); + }); + + describe('Ed25519 Test Vectors from RFC8032', function () { + // https://tools.ietf.org/html/rfc8032#section-7.1 + const crypto = openpgp.crypto.signature; + const curve = openpgp.crypto.publicKey.elliptic.get('ed25519'); + const util = openpgp.util; + function testVector(vector) { + var S = curve.keyFromSecret(vector.SECRET_KEY); + var P = curve.keyFromPublic(vector.PUBLIC_KEY); + expect(S.getPublic()).to.deep.equal(P.getPublic()); + var data = util.str2Uint8Array(vector.MESSAGE); + var keyIntegers = [ + openpgp.OID.fromClone(curve), + new openpgp.MPI(util.hex2bin(vector.PUBLIC_KEY)), + new openpgp.MPI(util.hex2bin(vector.SECRET_KEY)) + ]; + var msg_MPIs = [ + new openpgp.MPI(util.Uint8Array2str(util.hex2Uint8Array(vector.SIGNATURE.R).reverse())), + new openpgp.MPI(util.Uint8Array2str(util.hex2Uint8Array(vector.SIGNATURE.S).reverse())), + ]; + return Promise.all([ + crypto.sign(undefined, 22, keyIntegers, data).then(signature => { + var len = ((signature[0] << 8| signature[1]) + 7) / 8; + expect(util.hex2Uint8Array(vector.SIGNATURE.R)).to.deep.eq(signature.slice(2, 2 + len)); + expect(util.hex2Uint8Array(vector.SIGNATURE.S)).to.deep.eq(signature.slice(4 + len)); + }), + crypto.verify(22, undefined, msg_MPIs, keyIntegers, data).then(result => { + expect(result).to.be.true; + }) + ]); + }; + + it('Signature of empty string', function () { + return testVector({ + SECRET_KEY: + ['9d61b19deffd5a60ba844af492ec2cc4', + '4449c5697b326919703bac031cae7f60'].join(''), + PUBLIC_KEY: + ['d75a980182b10ab7d54bfed3c964073a', + '0ee172f3daa62325af021a68f707511a'].join(''), + MESSAGE: '', + SIGNATURE: + { R: ['e5564300c360ac729086e2cc806e828a', + '84877f1eb8e5d974d873e06522490155'].join(''), + S: ['5fb8821590a33bacc61e39701cf9b46b', + 'd25bf5f0595bbe24655141438e7a100b'].join('') } + }); + }); + + it('Signature of single byte', function () { + return testVector({ + SECRET_KEY: + ['4ccd089b28ff96da9db6c346ec114e0f', + '5b8a319f35aba624da8cf6ed4fb8a6fb'].join(''), + PUBLIC_KEY: + ['3d4017c3e843895a92b70aa74d1b7ebc', + '9c982ccf2ec4968cc0cd55f12af4660c'].join(''), + MESSAGE: util.hex2bin('72'), + SIGNATURE: + { R: ['92a009a9f0d4cab8720e820b5f642540', + 'a2b27b5416503f8fb3762223ebdb69da'].join(''), + S: ['085ac1e43e15996e458f3613d0f11d8c', + '387b2eaeb4302aeeb00d291612bb0c00'].join('') } + }); + }); + + it('Signature of two bytes', function () { + return testVector({ + SECRET_KEY: + ['c5aa8df43f9f837bedb7442f31dcb7b1', + '66d38535076f094b85ce3a2e0b4458f7'].join(''), + PUBLIC_KEY: + ['fc51cd8e6218a1a38da47ed00230f058', + '0816ed13ba3303ac5deb911548908025'].join(''), + MESSAGE: util.hex2bin('af82'), + SIGNATURE: + { R: ['6291d657deec24024827e69c3abe01a3', + '0ce548a284743a445e3680d7db5ac3ac'].join(''), + S: ['18ff9b538d16f290ae67f760984dc659', + '4a7c15e9716ed28dc027beceea1ec40a'].join('') } + }); + }); + + it('Signature of 1023 bytes', function () { + return testVector({ + SECRET_KEY: + ['f5e5767cf153319517630f226876b86c', + '8160cc583bc013744c6bf255f5cc0ee5'].join(''), + PUBLIC_KEY: + ['278117fc144c72340f67d0f2316e8386', + 'ceffbf2b2428c9c51fef7c597f1d426e'].join(''), + MESSAGE: util.hex2bin([ + '08b8b2b733424243760fe426a4b54908', + '632110a66c2f6591eabd3345e3e4eb98', + 'fa6e264bf09efe12ee50f8f54e9f77b1', + 'e355f6c50544e23fb1433ddf73be84d8', + '79de7c0046dc4996d9e773f4bc9efe57', + '38829adb26c81b37c93a1b270b20329d', + '658675fc6ea534e0810a4432826bf58c', + '941efb65d57a338bbd2e26640f89ffbc', + '1a858efcb8550ee3a5e1998bd177e93a', + '7363c344fe6b199ee5d02e82d522c4fe', + 'ba15452f80288a821a579116ec6dad2b', + '3b310da903401aa62100ab5d1a36553e', + '06203b33890cc9b832f79ef80560ccb9', + 'a39ce767967ed628c6ad573cb116dbef', + 'efd75499da96bd68a8a97b928a8bbc10', + '3b6621fcde2beca1231d206be6cd9ec7', + 'aff6f6c94fcd7204ed3455c68c83f4a4', + '1da4af2b74ef5c53f1d8ac70bdcb7ed1', + '85ce81bd84359d44254d95629e9855a9', + '4a7c1958d1f8ada5d0532ed8a5aa3fb2', + 'd17ba70eb6248e594e1a2297acbbb39d', + '502f1a8c6eb6f1ce22b3de1a1f40cc24', + '554119a831a9aad6079cad88425de6bd', + 'e1a9187ebb6092cf67bf2b13fd65f270', + '88d78b7e883c8759d2c4f5c65adb7553', + '878ad575f9fad878e80a0c9ba63bcbcc', + '2732e69485bbc9c90bfbd62481d9089b', + 'eccf80cfe2df16a2cf65bd92dd597b07', + '07e0917af48bbb75fed413d238f5555a', + '7a569d80c3414a8d0859dc65a46128ba', + 'b27af87a71314f318c782b23ebfe808b', + '82b0ce26401d2e22f04d83d1255dc51a', + 'ddd3b75a2b1ae0784504df543af8969b', + 'e3ea7082ff7fc9888c144da2af58429e', + 'c96031dbcad3dad9af0dcbaaaf268cb8', + 'fcffead94f3c7ca495e056a9b47acdb7', + '51fb73e666c6c655ade8297297d07ad1', + 'ba5e43f1bca32301651339e22904cc8c', + '42f58c30c04aafdb038dda0847dd988d', + 'cda6f3bfd15c4b4c4525004aa06eeff8', + 'ca61783aacec57fb3d1f92b0fe2fd1a8', + '5f6724517b65e614ad6808d6f6ee34df', + 'f7310fdc82aebfd904b01e1dc54b2927', + '094b2db68d6f903b68401adebf5a7e08', + 'd78ff4ef5d63653a65040cf9bfd4aca7', + '984a74d37145986780fc0b16ac451649', + 'de6188a7dbdf191f64b5fc5e2ab47b57', + 'f7f7276cd419c17a3ca8e1b939ae49e4', + '88acba6b965610b5480109c8b17b80e1', + 'b7b750dfc7598d5d5011fd2dcc5600a3', + '2ef5b52a1ecc820e308aa342721aac09', + '43bf6686b64b2579376504ccc493d97e', + '6aed3fb0f9cd71a43dd497f01f17c0e2', + 'cb3797aa2a2f256656168e6c496afc5f', + 'b93246f6b1116398a346f1a641f3b041', + 'e989f7914f90cc2c7fff357876e506b5', + '0d334ba77c225bc307ba537152f3f161', + '0e4eafe595f6d9d90d11faa933a15ef1', + '369546868a7f3a45a96768d40fd9d034', + '12c091c6315cf4fde7cb68606937380d', + 'b2eaaa707b4c4185c32eddcdd306705e', + '4dc1ffc872eeee475a64dfac86aba41c', + '0618983f8741c5ef68d3a101e8a3b8ca', + 'c60c905c15fc910840b94c00a0b9d0'].join('')), + SIGNATURE: + { R: ['0aab4c900501b3e24d7cdf4663326a3a', + '87df5e4843b2cbdb67cbf6e460fec350'].join(''), + S: ['aa5371b1508f9f4528ecea23c436d94b', + '5e8fcd4f681e30a6ac00a9704a188a03'].join('') } + }); + }); + + it('Signature of SHA(abc)', function () { + return testVector({ + SECRET_KEY: + ['833fe62409237b9d62ec77587520911e', + '9a759cec1d19755b7da901b96dca3d42'].join(''), + PUBLIC_KEY: + ['ec172b93ad5e563bf4932c70e1245034', + 'c35467ef2efd4d64ebf819683467e2bf'].join(''), + MESSAGE: util.hex2bin([ + 'ddaf35a193617abacc417349ae204131', + '12e6fa4e89a97ea20a9eeee64b55d39a', + '2192992a274fc1a836ba3c23a3feebbd', + '454d4423643ce80e2a9ac94fa54ca49f'].join('')), + SIGNATURE: + { R: ['dc2a4459e7369633a52b1bf277839a00', + '201009a3efbf3ecb69bea2186c26b589'].join(''), + S: ['09351fc9ac90b3ecfdfbc7c66431e030', + '3dca179c138ac17ad9bef1177331a704'].join('') } + }); + }); + }); + +/* TODO how does GPG2 accept this? + it('Should handle little-endian parameters in EdDSA', function () { + var 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'); + var hi = openpgp.key.readArmored(pubKey).keys[0]; + return hi.verifyPrimaryUser().then(() => { + var results = hi.getPrimaryUser(); + expect(results).to.exist; + expect(results.user).to.exist; + var user = results.user; + expect(user.selfCertifications[0].verify( + hi.primaryKey, {userid: user.userId, key: hi.primaryKey} + )).to.eventually.be.true; + expect(user.verifyCertificate( + hi.primaryKey, user.selfCertifications[0], [hi] + )).to.eventually.equal(openpgp.enums.keyStatus.valid); + }); + }); */ +}); diff --git a/test/unittests.html b/test/unittests.html index 47f16d5d..7c3feb07 100644 --- a/test/unittests.html +++ b/test/unittests.html @@ -11,9 +11,7 @@ - -