Create lightweight build that can lazily load indutny/elliptic if needed (#956)
This PR adds four config options to configure whether and how to load indutny/elliptic: use_indutny_elliptic, external_indutny_elliptic, indutny_elliptic_path and indutny_elliptic_fetch_options. Also: - Use tweetnacl.js instead of indutny/elliptic for curve25519 key generation - Don't initialize indutny's curve25519, improving performance when using that curve - Verify NIST signatures using Web Crypto instead of indutny/elliptic when not streaming - Move KeyPair.sign/verify to ecdsa.js - Move KeyPair.derive to ecdh.js - Move keyFromPrivate and keyFromPublic to a new indutnyKey.js file
This commit is contained in:
parent
528fbfb017
commit
08b7725b8c
|
@ -88,7 +88,7 @@ module.exports = {
|
||||||
"allowKeywords": true
|
"allowKeywords": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"eol-last": "off",
|
"eol-last": ["error", "always"],
|
||||||
"eqeqeq": "error",
|
"eqeqeq": "error",
|
||||||
"for-direction": "error",
|
"for-direction": "error",
|
||||||
"func-call-spacing": "error",
|
"func-call-spacing": "error",
|
||||||
|
@ -186,7 +186,7 @@ module.exports = {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"no-multi-str": "error",
|
"no-multi-str": "error",
|
||||||
"no-multiple-empty-lines": "error",
|
"no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF":0 }],
|
||||||
"no-native-reassign": "error",
|
"no-native-reassign": "error",
|
||||||
"no-negated-condition": "off",
|
"no-negated-condition": "off",
|
||||||
"no-negated-in-lhs": "error",
|
"no-negated-in-lhs": "error",
|
||||||
|
|
|
@ -15,10 +15,14 @@ matrix:
|
||||||
env: OPENPGP_NODE_JS='10' OPENPGPJSTEST='unit'
|
env: OPENPGP_NODE_JS='10' OPENPGPJSTEST='unit'
|
||||||
- node_js: "12"
|
- node_js: "12"
|
||||||
env: OPENPGP_NODE_JS='12' OPENPGPJSTEST='unit'
|
env: OPENPGP_NODE_JS='12' OPENPGPJSTEST='unit'
|
||||||
|
- node_js: "10"
|
||||||
|
env: OPENPGP_NODE_JS='10' OPENPGPJSTEST='unit' LIGHTWEIGHT=1
|
||||||
- node_js: "9"
|
- node_js: "9"
|
||||||
env: BROWSER='"firefox_26"' OPENPGPJSTEST='browserstack' COMPAT=1
|
env: BROWSER='"firefox_26"' OPENPGPJSTEST='browserstack' COMPAT=1
|
||||||
- node_js: "9"
|
- node_js: "9"
|
||||||
env: BROWSER='"firefox_61"' OPENPGPJSTEST='browserstack'
|
env: BROWSER='"firefox_61"' OPENPGPJSTEST='browserstack'
|
||||||
|
- node_js: "10"
|
||||||
|
env: BROWSER='"chrome_68"' OPENPGPJSTEST='browserstack' LIGHTWEIGHT=1
|
||||||
- node_js: "9"
|
- node_js: "9"
|
||||||
env: BROWSER='"chrome_49"' OPENPGPJSTEST='browserstack' COMPAT=1
|
env: BROWSER='"chrome_49"' OPENPGPJSTEST='browserstack' COMPAT=1
|
||||||
- node_js: "10"
|
- node_js: "10"
|
||||||
|
|
94
Gruntfile.js
94
Gruntfile.js
|
@ -1,20 +1,12 @@
|
||||||
module.exports = function(grunt) {
|
module.exports = function(grunt) {
|
||||||
|
|
||||||
var version = grunt.option('release');
|
const version = grunt.option('release');
|
||||||
var fs = require('fs');
|
const fs = require('fs');
|
||||||
var browser_capabilities;
|
|
||||||
|
|
||||||
if (process.env.SELENIUM_BROWSER_CAPABILITIES !== undefined) {
|
|
||||||
browser_capabilities = JSON.parse(process.env.SELENIUM_BROWSER_CAPABILITIES);
|
|
||||||
}
|
|
||||||
|
|
||||||
var getSauceKey = function getSaucekey () {
|
|
||||||
return '60ffb656-2346-4b77-81f3-bc435ff4c103';
|
|
||||||
};
|
|
||||||
|
|
||||||
// Project configuration.
|
// Project configuration.
|
||||||
const dev = !!grunt.option('dev');
|
const dev = !!grunt.option('dev');
|
||||||
const compat = !!grunt.option('compat');
|
const compat = !!grunt.option('compat');
|
||||||
|
const lightweight = !!grunt.option('lightweight');
|
||||||
const plugins = compat ? [
|
const plugins = compat ? [
|
||||||
"transform-async-to-generator",
|
"transform-async-to-generator",
|
||||||
"syntax-async-functions",
|
"syntax-async-functions",
|
||||||
|
@ -50,7 +42,7 @@ module.exports = function(grunt) {
|
||||||
debug: dev,
|
debug: dev,
|
||||||
standalone: 'openpgp'
|
standalone: 'openpgp'
|
||||||
},
|
},
|
||||||
cacheFile: 'browserify-cache' + (compat ? '-compat' : '') + '.json',
|
cacheFile: 'browserify-cache' + (compat ? '-compat' : '') + (lightweight ? '-lightweight' : '') + '.json',
|
||||||
// Don't bundle these packages with openpgp.js
|
// Don't bundle these packages with openpgp.js
|
||||||
external: ['crypto', 'zlib', 'node-localstorage', 'node-fetch', 'asn1.js', 'stream', 'buffer'].concat(
|
external: ['crypto', 'zlib', 'node-localstorage', 'node-fetch', 'asn1.js', 'stream', 'buffer'].concat(
|
||||||
compat ? [] : [
|
compat ? [] : [
|
||||||
|
@ -63,8 +55,12 @@ module.exports = function(grunt) {
|
||||||
'core-js/fn/typed/uint8-array',
|
'core-js/fn/typed/uint8-array',
|
||||||
'core-js/fn/string/repeat',
|
'core-js/fn/string/repeat',
|
||||||
'core-js/fn/symbol',
|
'core-js/fn/symbol',
|
||||||
'core-js/fn/object/assign',
|
'core-js/fn/object/assign'
|
||||||
]
|
],
|
||||||
|
lightweight ? [
|
||||||
|
'elliptic',
|
||||||
|
'elliptic.min.js'
|
||||||
|
] : []
|
||||||
),
|
),
|
||||||
transform: [
|
transform: [
|
||||||
["babelify", {
|
["babelify", {
|
||||||
|
@ -131,6 +127,26 @@ module.exports = function(grunt) {
|
||||||
from: "openpgp.js",
|
from: "openpgp.js",
|
||||||
to: "openpgp.min.js"
|
to: "openpgp.min.js"
|
||||||
}]
|
}]
|
||||||
|
},
|
||||||
|
lightweight_build: {
|
||||||
|
src: ['dist/openpgp.js'],
|
||||||
|
overwrite: true,
|
||||||
|
replacements: [
|
||||||
|
{
|
||||||
|
from: "external_indutny_elliptic: false",
|
||||||
|
to: "external_indutny_elliptic: true"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
indutny_global: {
|
||||||
|
src: ['dist/elliptic.min.js'],
|
||||||
|
overwrite: true,
|
||||||
|
replacements: [
|
||||||
|
{
|
||||||
|
from: 'b.elliptic=a()',
|
||||||
|
to: 'b.openpgp.elliptic=a()'
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
terser: {
|
terser: {
|
||||||
|
@ -141,13 +157,13 @@ module.exports = function(grunt) {
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
safari10: true
|
safari10: true
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
openpgp: {
|
openpgp: {
|
||||||
options: {
|
options: {
|
||||||
text: '/*! OpenPGP.js v<%= pkg.version %> - ' +
|
text: '/*! OpenPGP.js v<%= pkg.version %> - ' +
|
||||||
'<%= grunt.template.today("yyyy-mm-dd") %> - ' +
|
'<%= grunt.template.today("yyyy-mm-dd") %> - ' +
|
||||||
'this is LGPL licensed code, see LICENSE/our website <%= pkg.homepage %> for more information. */'
|
'this is LGPL licensed code, see LICENSE/our website <%= pkg.homepage %> for more information. */'
|
||||||
},
|
},
|
||||||
|
@ -168,8 +184,11 @@ module.exports = function(grunt) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
eslint: {
|
eslint: {
|
||||||
target: ['src/**/*.js'],
|
target: ['src/**/*.js', './Gruntfile.js'],
|
||||||
options: { configFile: '.eslintrc.js' }
|
options: {
|
||||||
|
configFile: '.eslintrc.js',
|
||||||
|
fix: !!grunt.option('fix')
|
||||||
|
}
|
||||||
},
|
},
|
||||||
jsdoc: {
|
jsdoc: {
|
||||||
dist: {
|
dist: {
|
||||||
|
@ -194,7 +213,8 @@ module.exports = function(grunt) {
|
||||||
unittests: {
|
unittests: {
|
||||||
options: {
|
options: {
|
||||||
reporter: 'spec',
|
reporter: 'spec',
|
||||||
timeout: 120000
|
timeout: 120000,
|
||||||
|
grep: lightweight ? 'lightweight' : undefined
|
||||||
},
|
},
|
||||||
src: ['test/unittests.js']
|
src: ['test/unittests.js']
|
||||||
}
|
}
|
||||||
|
@ -212,9 +232,24 @@ module.exports = function(grunt) {
|
||||||
cwd: 'dist/',
|
cwd: 'dist/',
|
||||||
src: ['*.js'],
|
src: ['*.js'],
|
||||||
dest: 'dist/compat/'
|
dest: 'dist/compat/'
|
||||||
|
},
|
||||||
|
openpgp_lightweight: {
|
||||||
|
expand: true,
|
||||||
|
cwd: 'dist/',
|
||||||
|
src: ['*.js'],
|
||||||
|
dest: 'dist/lightweight/'
|
||||||
|
},
|
||||||
|
indutny_elliptic: {
|
||||||
|
expand: true,
|
||||||
|
flatten: true,
|
||||||
|
src: ['./node_modules/elliptic/dist/elliptic.min.js'],
|
||||||
|
dest: 'dist/'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clean: ['dist/'],
|
clean: {
|
||||||
|
dist: ['dist/'],
|
||||||
|
js: ['dist/*.js']
|
||||||
|
},
|
||||||
connect: {
|
connect: {
|
||||||
dev: {
|
dev: {
|
||||||
options: {
|
options: {
|
||||||
|
@ -233,7 +268,7 @@ module.exports = function(grunt) {
|
||||||
watch: {
|
watch: {
|
||||||
src: {
|
src: {
|
||||||
files: ['src/**/*.js'],
|
files: ['src/**/*.js'],
|
||||||
tasks: ['browserify:openpgp', 'browserify:worker']
|
tasks: lightweight ? ['browserify:openpgp', 'browserify:worker', 'replace:lightweight_build'] : ['browserify:openpgp', 'browserify:worker']
|
||||||
},
|
},
|
||||||
test: {
|
test: {
|
||||||
files: ['test/*.js', 'test/crypto/**/*.js', 'test/general/**/*.js', 'test/worker/**/*.js'],
|
files: ['test/*.js', 'test/crypto/**/*.js', 'test/general/**/*.js', 'test/worker/**/*.js'],
|
||||||
|
@ -279,25 +314,32 @@ module.exports = function(grunt) {
|
||||||
});
|
});
|
||||||
|
|
||||||
function patchFile(options) {
|
function patchFile(options) {
|
||||||
var path = './' + options.fileName,
|
const path = './' + options.fileName;
|
||||||
file = require(path);
|
//eslint-disable-next-line
|
||||||
|
const file = require(path);
|
||||||
|
|
||||||
if (options.version) {
|
if (options.version) {
|
||||||
file.version = options.version;
|
file.version = options.version;
|
||||||
}
|
}
|
||||||
|
//eslint-disable-next-line
|
||||||
fs.writeFileSync(path, JSON.stringify(file, null, 2) + '\n');
|
fs.writeFileSync(path, JSON.stringify(file, null, 2) + '\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build tasks
|
// Build tasks
|
||||||
grunt.registerTask('version', ['replace:openpgp']);
|
grunt.registerTask('version', ['replace:openpgp']);
|
||||||
grunt.registerTask('replace_min', ['replace:openpgp_min', 'replace:worker_min']);
|
grunt.registerTask('replace_min', ['replace:openpgp_min', 'replace:worker_min']);
|
||||||
grunt.registerTask('build', ['browserify:openpgp', 'browserify:worker', 'version', 'terser', 'header', 'replace_min']);
|
grunt.registerTask('build', function() {
|
||||||
|
if (lightweight) {
|
||||||
|
grunt.task.run(['copy:indutny_elliptic', 'browserify:openpgp', 'browserify:worker', 'replace:lightweight_build', 'replace:indutny_global', 'version', 'terser', 'header', 'replace_min']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
grunt.task.run(['browserify:openpgp', 'browserify:worker', 'version', 'terser', 'header', 'replace_min']);
|
||||||
|
}
|
||||||
|
);
|
||||||
grunt.registerTask('documentation', ['jsdoc']);
|
grunt.registerTask('documentation', ['jsdoc']);
|
||||||
grunt.registerTask('default', ['build']);
|
grunt.registerTask('default', ['build']);
|
||||||
// Test/Dev tasks
|
// Test/Dev tasks
|
||||||
grunt.registerTask('test', ['eslint', 'mochaTest']);
|
grunt.registerTask('test', ['eslint', 'mochaTest']);
|
||||||
grunt.registerTask('coverage', ['mocha_istanbul:coverage']);
|
grunt.registerTask('coverage', ['mocha_istanbul:coverage']);
|
||||||
grunt.registerTask('browsertest', ['build', 'browserify:unittests', 'copy:browsertest', 'connect:test', 'watch']);
|
grunt.registerTask('browsertest', ['build', 'browserify:unittests', 'copy:browsertest', 'connect:test', 'watch']);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
4
npm-shrinkwrap.json
generated
4
npm-shrinkwrap.json
generated
|
@ -2190,8 +2190,8 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"elliptic": {
|
"elliptic": {
|
||||||
"version": "github:openpgpjs/elliptic#6b7801573b8940a49e7b8253176ece2725841efd",
|
"version": "github:openpgpjs/elliptic#ab7d8268c60b6abeb175841c578c224ac5b2d279",
|
||||||
"from": "github:openpgpjs/elliptic#6b7801573b8940a49e7b8253176ece2725841efd",
|
"from": "github:openpgpjs/elliptic#ab7d8268c60b6abeb175841c578c224ac5b2d279",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"bn.js": "^4.4.0",
|
"bn.js": "^4.4.0",
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
"test/crypto"
|
"test/crypto"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "grunt build --compat copy:openpgp_compat && grunt build",
|
"build": "grunt build --compat copy:openpgp_compat && grunt build --lightweight copy:openpgp_lightweight clean:js && grunt build",
|
||||||
"pretest": "grunt",
|
"pretest": "grunt",
|
||||||
"test": "grunt test",
|
"test": "grunt test",
|
||||||
"lint": "eslint src"
|
"lint": "eslint src"
|
||||||
|
@ -73,7 +73,7 @@
|
||||||
"asmcrypto.js": "github:openpgpjs/asmcrypto#6e4e407b9b8ae317925a9e677cc7b4de3e447e83",
|
"asmcrypto.js": "github:openpgpjs/asmcrypto#6e4e407b9b8ae317925a9e677cc7b4de3e447e83",
|
||||||
"bn.js": "^4.11.8",
|
"bn.js": "^4.11.8",
|
||||||
"buffer": "^5.0.8",
|
"buffer": "^5.0.8",
|
||||||
"elliptic": "github:openpgpjs/elliptic#6b7801573b8940a49e7b8253176ece2725841efd",
|
"elliptic": "github:openpgpjs/elliptic#ab7d8268c60b6abeb175841c578c224ac5b2d279",
|
||||||
"hash.js": "^1.1.3",
|
"hash.js": "^1.1.3",
|
||||||
"pako": "^1.0.6",
|
"pako": "^1.0.6",
|
||||||
"seek-bzip": "github:openpgpjs/seek-bzip#6187fc025851d35c4e104a25ea15a10b9b8d6f7d",
|
"seek-bzip": "github:openpgpjs/seek-bzip#6187fc025851d35c4e104a25ea15a10b9b8d6f7d",
|
||||||
|
|
|
@ -190,5 +190,25 @@ export default {
|
||||||
* @memberof module:config
|
* @memberof module:config
|
||||||
* @property {Array} known_notations
|
* @property {Array} known_notations
|
||||||
*/
|
*/
|
||||||
known_notations: ["preferred-email-encoding@pgp.com", "pka-address@gnupg.org"]
|
known_notations: ["preferred-email-encoding@pgp.com", "pka-address@gnupg.org"],
|
||||||
|
/**
|
||||||
|
* @memberof module:config
|
||||||
|
* @property {Boolean} use_indutny_elliptic Whether to use the indutny/elliptic library. When false, certain curves will not be supported.
|
||||||
|
*/
|
||||||
|
use_indutny_elliptic: true,
|
||||||
|
/**
|
||||||
|
* @memberof module:config
|
||||||
|
* @property {Boolean} external_indutny_elliptic Whether to lazily load the indutny/elliptic library from an external path on demand.
|
||||||
|
*/
|
||||||
|
external_indutny_elliptic: false,
|
||||||
|
/**
|
||||||
|
* @memberof module:config
|
||||||
|
* @property {String} indutny_elliptic_path The path to load the indutny/elliptic library from. Only has an effect if `config.external_indutny_elliptic` is true.
|
||||||
|
*/
|
||||||
|
indutny_elliptic_path: './elliptic.min.js',
|
||||||
|
/**
|
||||||
|
* @memberof module:config
|
||||||
|
* @property {Object} indutny_elliptic_fetch_options Options object to pass to `fetch` when loading the indutny/elliptic library. Only has an effect if `config.external_indutny_elliptic` is true.
|
||||||
|
*/
|
||||||
|
indutny_elliptic_fetch_options: {}
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,22 +18,23 @@
|
||||||
/**
|
/**
|
||||||
* @fileoverview Wrapper of an instance of an Elliptic Curve
|
* @fileoverview Wrapper of an instance of an Elliptic Curve
|
||||||
* @requires bn.js
|
* @requires bn.js
|
||||||
* @requires elliptic
|
* @requires tweetnacl
|
||||||
* @requires crypto/public_key/elliptic/key
|
* @requires crypto/public_key/elliptic/key
|
||||||
* @requires crypto/random
|
* @requires crypto/random
|
||||||
* @requires enums
|
* @requires enums
|
||||||
* @requires util
|
* @requires util
|
||||||
* @requires type/oid
|
* @requires type/oid
|
||||||
|
* @requires config
|
||||||
* @module crypto/public_key/elliptic/curve
|
* @module crypto/public_key/elliptic/curve
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import BN from 'bn.js';
|
import BN from 'bn.js';
|
||||||
import { ec as EC, eddsa as EdDSA } from 'elliptic';
|
import nacl from 'tweetnacl/nacl-fast-light.js';
|
||||||
import KeyPair from './key';
|
|
||||||
import random from '../../random';
|
import random from '../../random';
|
||||||
import enums from '../../../enums';
|
import enums from '../../../enums';
|
||||||
import util from '../../../util';
|
import util from '../../../util';
|
||||||
import OID from '../../../type/oid';
|
import OID from '../../../type/oid';
|
||||||
|
import { getIndutnyCurve } from './indutnyKey';
|
||||||
|
|
||||||
const webCrypto = util.getWebCrypto();
|
const webCrypto = util.getWebCrypto();
|
||||||
const nodeCrypto = util.getNodeCrypto();
|
const nodeCrypto = util.getNodeCrypto();
|
||||||
|
@ -152,16 +153,6 @@ function Curve(oid_or_name, params) {
|
||||||
params = params || curves[this.name];
|
params = params || curves[this.name];
|
||||||
|
|
||||||
this.keyType = params.keyType;
|
this.keyType = params.keyType;
|
||||||
switch (this.keyType) {
|
|
||||||
case enums.publicKey.ecdsa:
|
|
||||||
this.curve = new EC(this.name);
|
|
||||||
break;
|
|
||||||
case enums.publicKey.eddsa:
|
|
||||||
this.curve = new EdDSA(this.name);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error('Unknown elliptic key type;');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.oid = params.oid;
|
this.oid = params.oid;
|
||||||
this.hash = params.hash;
|
this.hash = params.hash;
|
||||||
|
@ -169,49 +160,53 @@ function Curve(oid_or_name, params) {
|
||||||
this.node = params.node && curves[this.name];
|
this.node = params.node && curves[this.name];
|
||||||
this.web = params.web && curves[this.name];
|
this.web = params.web && curves[this.name];
|
||||||
this.payloadSize = params.payloadSize;
|
this.payloadSize = params.payloadSize;
|
||||||
}
|
if (this.web && util.getWebCrypto()) {
|
||||||
|
this.type = 'web';
|
||||||
Curve.prototype.keyFromPrivate = function (priv) { // Not for ed25519
|
} else if (this.node && util.getNodeCrypto()) {
|
||||||
return new KeyPair(this, { priv: priv });
|
this.type = 'node';
|
||||||
};
|
} else if (this.name === 'curve25519') {
|
||||||
|
this.type = 'curve25519';
|
||||||
Curve.prototype.keyFromPublic = function (pub) {
|
} else if (this.name === 'ed25519') {
|
||||||
const keyPair = new KeyPair(this, { pub: pub });
|
this.type = 'ed25519';
|
||||||
if (
|
|
||||||
this.keyType === enums.publicKey.ecdsa &&
|
|
||||||
keyPair.keyPair.validate().result !== true
|
|
||||||
) {
|
|
||||||
throw new Error('Invalid elliptic public key');
|
|
||||||
}
|
}
|
||||||
return keyPair;
|
}
|
||||||
};
|
|
||||||
|
|
||||||
Curve.prototype.genKeyPair = async function () {
|
Curve.prototype.genKeyPair = async function () {
|
||||||
let keyPair;
|
let keyPair;
|
||||||
if (this.web && util.getWebCrypto()) {
|
switch (this.type) {
|
||||||
// If browser doesn't support a curve, we'll catch it
|
case 'web':
|
||||||
try {
|
try {
|
||||||
keyPair = await webGenKeyPair(this.name);
|
return await webGenKeyPair(this.name);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
util.print_debug("Browser did not support signing: " + err.message);
|
util.print_debug_error("Browser did not support generating ec key " + err.message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'node':
|
||||||
|
return nodeGenKeyPair(this.name);
|
||||||
|
case 'curve25519': {
|
||||||
|
const privateKey = await random.getRandomBytes(32);
|
||||||
|
const one = new BN(1);
|
||||||
|
const mask = one.ushln(255 - 3).sub(one).ushln(3);
|
||||||
|
let secretKey = new BN(privateKey);
|
||||||
|
secretKey = secretKey.or(one.ushln(255 - 1));
|
||||||
|
secretKey = secretKey.and(mask);
|
||||||
|
secretKey = secretKey.toArrayLike(Uint8Array, 'le', 32);
|
||||||
|
keyPair = nacl.box.keyPair.fromSecretKey(secretKey);
|
||||||
|
const publicKey = util.concatUint8Array([new Uint8Array([0x40]), keyPair.publicKey]);
|
||||||
|
return { publicKey, privateKey };
|
||||||
}
|
}
|
||||||
} else if (this.node && util.getNodeCrypto()) {
|
case 'ed25519': {
|
||||||
keyPair = await nodeGenKeyPair(this.name);
|
const privateKey = await random.getRandomBytes(32);
|
||||||
}
|
const keyPair = nacl.sign.keyPair.fromSeed(privateKey);
|
||||||
|
const publicKey = util.concatUint8Array([new Uint8Array([0x40]), keyPair.publicKey]);
|
||||||
if (!keyPair || !keyPair.priv) {
|
return { publicKey, privateKey };
|
||||||
// elliptic fallback
|
|
||||||
const r = await this.curve.genKeyPair({
|
|
||||||
entropy: util.Uint8Array_to_str(await random.getRandomBytes(32))
|
|
||||||
});
|
|
||||||
const compact = this.curve.curve.type === 'edwards' || this.curve.curve.type === 'mont';
|
|
||||||
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, keyPair);
|
const indutnyCurve = await getIndutnyCurve(this.name);
|
||||||
|
keyPair = await indutnyCurve.genKeyPair({
|
||||||
|
entropy: util.Uint8Array_to_str(await random.getRandomBytes(32))
|
||||||
|
});
|
||||||
|
return { publicKey: keyPair.getPublic('array', false), privateKey: keyPair.getPrivate().toArray() };
|
||||||
};
|
};
|
||||||
|
|
||||||
async function generate(curve) {
|
async function generate(curve) {
|
||||||
|
@ -219,8 +214,8 @@ async function generate(curve) {
|
||||||
const keyPair = await curve.genKeyPair();
|
const keyPair = await curve.genKeyPair();
|
||||||
return {
|
return {
|
||||||
oid: curve.oid,
|
oid: curve.oid,
|
||||||
Q: new BN(keyPair.getPublic()),
|
Q: new BN(keyPair.publicKey),
|
||||||
d: new BN(keyPair.getPrivate()),
|
d: new BN(keyPair.privateKey),
|
||||||
hash: curve.hash,
|
hash: curve.hash,
|
||||||
cipher: curve.cipher
|
cipher: curve.cipher
|
||||||
};
|
};
|
||||||
|
@ -233,7 +228,7 @@ function getPreferredHashAlgo(oid) {
|
||||||
export default Curve;
|
export default Curve;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
curves, webCurves, nodeCurves, generate, getPreferredHashAlgo
|
curves, webCurves, nodeCurves, generate, getPreferredHashAlgo, jwkToRawPublic, rawPublicToJwk, privateToJwk
|
||||||
};
|
};
|
||||||
|
|
||||||
//////////////////////////
|
//////////////////////////
|
||||||
|
@ -251,11 +246,8 @@ async function webGenKeyPair(name) {
|
||||||
const publicKey = await webCrypto.exportKey("jwk", webCryptoKey.publicKey);
|
const publicKey = await webCrypto.exportKey("jwk", webCryptoKey.publicKey);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pub: {
|
publicKey: jwkToRawPublic(publicKey),
|
||||||
x: util.b64_to_Uint8Array(publicKey.x, true),
|
privateKey: util.b64_to_Uint8Array(privateKey.d, true)
|
||||||
y: util.b64_to_Uint8Array(publicKey.y, true)
|
|
||||||
},
|
|
||||||
priv: util.b64_to_Uint8Array(privateKey.d, true)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,9 +255,69 @@ async function nodeGenKeyPair(name) {
|
||||||
// Note: ECDSA and ECDH key generation is structurally equivalent
|
// Note: ECDSA and ECDH key generation is structurally equivalent
|
||||||
const ecdh = nodeCrypto.createECDH(nodeCurves[name]);
|
const ecdh = nodeCrypto.createECDH(nodeCurves[name]);
|
||||||
await ecdh.generateKeys();
|
await ecdh.generateKeys();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pub: ecdh.getPublicKey().toJSON().data,
|
publicKey: new Uint8Array(ecdh.getPublicKey()),
|
||||||
priv: ecdh.getPrivateKey().toJSON().data
|
privateKey: new Uint8Array(ecdh.getPrivateKey())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
// //
|
||||||
|
// Helper functions //
|
||||||
|
// //
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {JsonWebKey} jwk key for conversion
|
||||||
|
*
|
||||||
|
* @returns {Uint8Array} raw public key
|
||||||
|
*/
|
||||||
|
function jwkToRawPublic(jwk) {
|
||||||
|
const bufX = util.b64_to_Uint8Array(jwk.x);
|
||||||
|
const bufY = util.b64_to_Uint8Array(jwk.y);
|
||||||
|
const publicKey = new Uint8Array(bufX.length + bufY.length + 1);
|
||||||
|
publicKey[0] = 0x04;
|
||||||
|
publicKey.set(bufX, 1);
|
||||||
|
publicKey.set(bufY, bufX.length + 1);
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Integer} payloadSize ec payload size
|
||||||
|
* @param {String} name curve name
|
||||||
|
* @param {Uint8Array} publicKey public key
|
||||||
|
*
|
||||||
|
* @returns {JsonWebKey} public key in jwk format
|
||||||
|
*/
|
||||||
|
function rawPublicToJwk(payloadSize, name, publicKey) {
|
||||||
|
const len = payloadSize;
|
||||||
|
const bufX = publicKey.slice(1, len + 1);
|
||||||
|
const bufY = publicKey.slice(len + 1, len * 2 + 1);
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc7518.txt
|
||||||
|
const jwk = {
|
||||||
|
kty: "EC",
|
||||||
|
crv: name,
|
||||||
|
x: util.Uint8Array_to_b64(bufX, true),
|
||||||
|
y: util.Uint8Array_to_b64(bufY, true),
|
||||||
|
ext: true
|
||||||
|
};
|
||||||
|
return jwk;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Integer} payloadSize ec payload size
|
||||||
|
* @param {String} name curve name
|
||||||
|
* @param {Uint8Array} publicKey public key
|
||||||
|
* @param {Uint8Array} privateKey private key
|
||||||
|
*
|
||||||
|
* @returns {JsonWebKey} private key in jwk format
|
||||||
|
*/
|
||||||
|
function privateToJwk(payloadSize, name, publicKey, privateKey) {
|
||||||
|
const jwk = rawPublicToJwk(payloadSize, name, publicKey);
|
||||||
|
if (privateKey.length !== payloadSize) {
|
||||||
|
const start = payloadSize - privateKey.length;
|
||||||
|
privateKey = (new Uint8Array(payloadSize)).set(privateKey, start);
|
||||||
|
}
|
||||||
|
jwk.d = util.Uint8Array_to_b64(privateKey, true);
|
||||||
|
return jwk;
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
* @requires crypto/public_key/elliptic/curve
|
* @requires crypto/public_key/elliptic/curve
|
||||||
* @requires crypto/aes_kw
|
* @requires crypto/aes_kw
|
||||||
* @requires crypto/cipher
|
* @requires crypto/cipher
|
||||||
|
* @requires crypto/random
|
||||||
* @requires crypto/hash
|
* @requires crypto/hash
|
||||||
* @requires type/kdf_params
|
* @requires type/kdf_params
|
||||||
* @requires enums
|
* @requires enums
|
||||||
|
@ -31,13 +32,15 @@
|
||||||
|
|
||||||
import BN from 'bn.js';
|
import BN from 'bn.js';
|
||||||
import nacl from 'tweetnacl/nacl-fast-light.js';
|
import nacl from 'tweetnacl/nacl-fast-light.js';
|
||||||
import Curve from './curves';
|
import Curve, { jwkToRawPublic, rawPublicToJwk, privateToJwk } from './curves';
|
||||||
import aes_kw from '../../aes_kw';
|
import aes_kw from '../../aes_kw';
|
||||||
import cipher from '../../cipher';
|
import cipher from '../../cipher';
|
||||||
|
import random from '../../random';
|
||||||
import hash from '../../hash';
|
import hash from '../../hash';
|
||||||
import type_kdf_params from '../../../type/kdf_params';
|
import type_kdf_params from '../../../type/kdf_params';
|
||||||
import enums from '../../../enums';
|
import enums from '../../../enums';
|
||||||
import util from '../../../util';
|
import util from '../../../util';
|
||||||
|
import { keyFromPublic, keyFromPrivate, getIndutnyCurve } from './indutnyKey';
|
||||||
|
|
||||||
const webCrypto = util.getWebCrypto();
|
const webCrypto = util.getWebCrypto();
|
||||||
const nodeCrypto = util.getNodeCrypto();
|
const nodeCrypto = util.getNodeCrypto();
|
||||||
|
@ -87,17 +90,15 @@ async function kdf(hash_algo, X, length, param, stripLeading = false, stripTrail
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
async function genPublicEphemeralKey(curve, Q) {
|
async function genPublicEphemeralKey(curve, Q) {
|
||||||
switch (curve.name) {
|
switch (curve.type) {
|
||||||
case 'curve25519': {
|
case 'curve25519': {
|
||||||
const { secretKey: d } = nacl.box.keyPair();
|
const d = await random.getRandomBytes(32);
|
||||||
const { secretKey, sharedKey } = await genPrivateEphemeralKey(curve, Q, null, d);
|
const { secretKey, sharedKey } = await genPrivateEphemeralKey(curve, Q, null, d);
|
||||||
let { publicKey } = nacl.box.keyPair.fromSecretKey(secretKey);
|
let { publicKey } = nacl.box.keyPair.fromSecretKey(secretKey);
|
||||||
publicKey = util.concatUint8Array([new Uint8Array([0x40]), publicKey]);
|
publicKey = util.concatUint8Array([new Uint8Array([0x40]), publicKey]);
|
||||||
return { publicKey, sharedKey }; // Note: sharedKey is little-endian here, unlike below
|
return { publicKey, sharedKey }; // Note: sharedKey is little-endian here, unlike below
|
||||||
}
|
}
|
||||||
case 'p256':
|
case 'web':
|
||||||
case 'p384':
|
|
||||||
case 'p521': {
|
|
||||||
if (curve.web && util.getWebCrypto()) {
|
if (curve.web && util.getWebCrypto()) {
|
||||||
try {
|
try {
|
||||||
return await webPublicEphemeralKey(curve, Q);
|
return await webPublicEphemeralKey(curve, Q);
|
||||||
|
@ -105,10 +106,9 @@ async function genPublicEphemeralKey(curve, Q) {
|
||||||
util.print_debug_error(err);
|
util.print_debug_error(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
}
|
case 'node':
|
||||||
if (curve.node && nodeCrypto) {
|
return nodePublicEphemeralKey(curve, Q);
|
||||||
return nodePublicEphemeralKey(curve, Q);
|
|
||||||
}
|
}
|
||||||
return ellipticPublicEphemeralKey(curve, Q);
|
return ellipticPublicEphemeralKey(curve, Q);
|
||||||
}
|
}
|
||||||
|
@ -146,7 +146,7 @@ async function encrypt(oid, cipher_algo, hash_algo, m, Q, fingerprint) {
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
async function genPrivateEphemeralKey(curve, V, Q, d) {
|
async function genPrivateEphemeralKey(curve, V, Q, d) {
|
||||||
switch (curve.name) {
|
switch (curve.type) {
|
||||||
case 'curve25519': {
|
case 'curve25519': {
|
||||||
const one = new BN(1);
|
const one = new BN(1);
|
||||||
const mask = one.ushln(255 - 3).sub(one).ushln(3);
|
const mask = one.ushln(255 - 3).sub(one).ushln(3);
|
||||||
|
@ -157,9 +157,7 @@ async function genPrivateEphemeralKey(curve, V, Q, d) {
|
||||||
const sharedKey = nacl.scalarMult(secretKey, V.subarray(1));
|
const sharedKey = nacl.scalarMult(secretKey, V.subarray(1));
|
||||||
return { secretKey, sharedKey }; // Note: sharedKey is little-endian here, unlike below
|
return { secretKey, sharedKey }; // Note: sharedKey is little-endian here, unlike below
|
||||||
}
|
}
|
||||||
case 'p256':
|
case 'web':
|
||||||
case 'p384':
|
|
||||||
case 'p521': {
|
|
||||||
if (curve.web && util.getWebCrypto()) {
|
if (curve.web && util.getWebCrypto()) {
|
||||||
try {
|
try {
|
||||||
return await webPrivateEphemeralKey(curve, V, Q, d);
|
return await webPrivateEphemeralKey(curve, V, Q, d);
|
||||||
|
@ -167,10 +165,9 @@ async function genPrivateEphemeralKey(curve, V, Q, d) {
|
||||||
util.print_debug_error(err);
|
util.print_debug_error(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
}
|
case 'node':
|
||||||
if (curve.node && nodeCrypto) {
|
return nodePrivateEphemeralKey(curve, V, d);
|
||||||
return nodePrivateEphemeralKey(curve, V, d);
|
|
||||||
}
|
}
|
||||||
return ellipticPrivateEphemeralKey(curve, V, d);
|
return ellipticPrivateEphemeralKey(curve, V, d);
|
||||||
}
|
}
|
||||||
|
@ -218,7 +215,7 @@ async function decrypt(oid, cipher_algo, hash_algo, V, C, Q, d, fingerprint) {
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
async function webPrivateEphemeralKey(curve, V, Q, d) {
|
async function webPrivateEphemeralKey(curve, V, Q, d) {
|
||||||
const recipient = privateToJwk(curve.payloadSize, curve.web.web, d, Q);
|
const recipient = privateToJwk(curve.payloadSize, curve.web.web, Q, d);
|
||||||
let privateKey = webCrypto.importKey(
|
let privateKey = webCrypto.importKey(
|
||||||
"jwk",
|
"jwk",
|
||||||
recipient,
|
recipient,
|
||||||
|
@ -318,11 +315,12 @@ async function webPublicEphemeralKey(curve, Q) {
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
async function ellipticPrivateEphemeralKey(curve, V, d) {
|
async function ellipticPrivateEphemeralKey(curve, V, d) {
|
||||||
V = curve.keyFromPublic(V);
|
const indutnyCurve = await getIndutnyCurve(curve.name);
|
||||||
d = curve.keyFromPrivate(d);
|
V = keyFromPublic(indutnyCurve, V);
|
||||||
|
d = keyFromPrivate(indutnyCurve, d);
|
||||||
const secretKey = new Uint8Array(d.getPrivate());
|
const secretKey = new Uint8Array(d.getPrivate());
|
||||||
const S = d.derive(V);
|
const S = d.derive(V.getPublic());
|
||||||
const len = curve.curve.curve.p.byteLength();
|
const len = indutnyCurve.curve.p.byteLength();
|
||||||
const sharedKey = S.toArrayLike(Uint8Array, 'be', len);
|
const sharedKey = S.toArrayLike(Uint8Array, 'be', len);
|
||||||
return { secretKey, sharedKey };
|
return { secretKey, sharedKey };
|
||||||
}
|
}
|
||||||
|
@ -336,11 +334,13 @@ async function ellipticPrivateEphemeralKey(curve, V, d) {
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
async function ellipticPublicEphemeralKey(curve, Q) {
|
async function ellipticPublicEphemeralKey(curve, Q) {
|
||||||
|
const indutnyCurve = await getIndutnyCurve(curve.name);
|
||||||
const v = await curve.genKeyPair();
|
const v = await curve.genKeyPair();
|
||||||
Q = curve.keyFromPublic(Q);
|
Q = keyFromPublic(indutnyCurve, Q);
|
||||||
const publicKey = new Uint8Array(v.getPublic());
|
const V = keyFromPrivate(indutnyCurve, v.privateKey);
|
||||||
const S = v.derive(Q);
|
const publicKey = v.publicKey;
|
||||||
const len = curve.curve.curve.p.byteLength();
|
const S = V.derive(Q.getPublic());
|
||||||
|
const len = indutnyCurve.curve.p.byteLength();
|
||||||
const sharedKey = S.toArrayLike(Uint8Array, 'be', len);
|
const sharedKey = S.toArrayLike(Uint8Array, 'be', len);
|
||||||
return { publicKey, sharedKey };
|
return { publicKey, sharedKey };
|
||||||
}
|
}
|
||||||
|
@ -378,52 +378,4 @@ async function nodePublicEphemeralKey(curve, Q) {
|
||||||
return { publicKey, sharedKey };
|
return { publicKey, sharedKey };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Integer} payloadSize ec payload size
|
|
||||||
* @param {String} name curve name
|
|
||||||
* @param {Uint8Array} publicKey public key
|
|
||||||
* @returns {JsonWebKey} public key in jwk format
|
|
||||||
*/
|
|
||||||
function rawPublicToJwk(payloadSize, name, publicKey) {
|
|
||||||
const len = payloadSize;
|
|
||||||
const bufX = publicKey.slice(1, len + 1);
|
|
||||||
const bufY = publicKey.slice(len + 1, len * 2 + 1);
|
|
||||||
// https://www.rfc-editor.org/rfc/rfc7518.txt
|
|
||||||
const jwKey = {
|
|
||||||
kty: "EC",
|
|
||||||
crv: name,
|
|
||||||
x: util.Uint8Array_to_b64(bufX, true),
|
|
||||||
y: util.Uint8Array_to_b64(bufY, true),
|
|
||||||
ext: true
|
|
||||||
};
|
|
||||||
return jwKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Integer} payloadSize ec payload size
|
|
||||||
* @param {String} name curve name
|
|
||||||
* @param {Uint8Array} publicKey public key
|
|
||||||
* @param {Uint8Array} privateKey private key
|
|
||||||
* @returns {JsonWebKey} private key in jwk format
|
|
||||||
*/
|
|
||||||
function privateToJwk(payloadSize, name, privateKey, publicKey) {
|
|
||||||
const jwk = rawPublicToJwk(payloadSize, name, publicKey);
|
|
||||||
jwk.d = util.Uint8Array_to_b64(privateKey, true);
|
|
||||||
return jwk;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {JsonWebKey} jwk key for conversion
|
|
||||||
* @returns {Uint8Array} raw public key
|
|
||||||
*/
|
|
||||||
function jwkToRawPublic(jwk) {
|
|
||||||
const bufX = util.b64_to_Uint8Array(jwk.x);
|
|
||||||
const bufY = util.b64_to_Uint8Array(jwk.y);
|
|
||||||
const publicKey = new Uint8Array(bufX.length + bufY.length + 1);
|
|
||||||
publicKey[0] = 0x04;
|
|
||||||
publicKey.set(bufX, 1);
|
|
||||||
publicKey.set(bufY, bufX.length + 1);
|
|
||||||
return publicKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { encrypt, decrypt, genPublicEphemeralKey, genPrivateEphemeralKey, buildEcdhParam, kdf, webPublicEphemeralKey, webPrivateEphemeralKey, ellipticPublicEphemeralKey, ellipticPrivateEphemeralKey, nodePublicEphemeralKey, nodePrivateEphemeralKey };
|
export default { encrypt, decrypt, genPublicEphemeralKey, genPrivateEphemeralKey, buildEcdhParam, kdf, webPublicEphemeralKey, webPrivateEphemeralKey, ellipticPublicEphemeralKey, ellipticPrivateEphemeralKey, nodePublicEphemeralKey, nodePrivateEphemeralKey };
|
||||||
|
|
|
@ -17,31 +17,62 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @fileoverview Implementation of ECDSA following RFC6637 for Openpgpjs
|
* @fileoverview Implementation of ECDSA following RFC6637 for Openpgpjs
|
||||||
* @requires crypto/public_key/elliptic/curve
|
* @requires bn.js
|
||||||
|
* @requires web-stream-tools
|
||||||
|
* @requires enums
|
||||||
|
* @requires util
|
||||||
|
* @requires crypto/public_key/elliptic/curves
|
||||||
* @module crypto/public_key/elliptic/ecdsa
|
* @module crypto/public_key/elliptic/ecdsa
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Curve from './curves';
|
import BN from 'bn.js';
|
||||||
|
import stream from 'web-stream-tools';
|
||||||
|
import enums from '../../../enums';
|
||||||
|
import util from '../../../util';
|
||||||
|
import Curve, { webCurves, privateToJwk, rawPublicToJwk } from './curves';
|
||||||
|
import { getIndutnyCurve, keyFromPrivate, keyFromPublic } from './indutnyKey';
|
||||||
|
|
||||||
|
const webCrypto = util.getWebCrypto();
|
||||||
|
const nodeCrypto = util.getNodeCrypto();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign a message using the provided key
|
* Sign a message using the provided key
|
||||||
* @param {module:type/oid} oid Elliptic curve object identifier
|
* @param {module:type/oid} oid Elliptic curve object identifier
|
||||||
* @param {module:enums.hash} hash_algo Hash algorithm used to sign
|
* @param {module:enums.hash} hash_algo Hash algorithm used to sign
|
||||||
* @param {Uint8Array} m Message to sign
|
* @param {Uint8Array} message Message to sign
|
||||||
* @param {Uint8Array} d Private key used to sign the message
|
* @param {Uint8Array} publicKey Public key
|
||||||
* @param {Uint8Array} hashed The hashed message
|
* @param {Uint8Array} privateKey Private key used to sign the message
|
||||||
|
* @param {Uint8Array} hashed The hashed message
|
||||||
* @returns {{r: Uint8Array,
|
* @returns {{r: Uint8Array,
|
||||||
* s: Uint8Array}} Signature of the message
|
* s: Uint8Array}} Signature of the message
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
async function sign(oid, hash_algo, m, d, hashed) {
|
async function sign(oid, hash_algo, message, publicKey, privateKey, hashed) {
|
||||||
const curve = new Curve(oid);
|
const curve = new Curve(oid);
|
||||||
const key = curve.keyFromPrivate(d);
|
if (message && !message.locked) {
|
||||||
const signature = await key.sign(m, hash_algo, hashed);
|
message = await stream.readToEnd(message);
|
||||||
return {
|
const keyPair = { publicKey, privateKey };
|
||||||
r: signature.r.toArrayLike(Uint8Array),
|
switch (curve.type) {
|
||||||
s: signature.s.toArrayLike(Uint8Array)
|
case 'web': {
|
||||||
};
|
// If browser doesn't support a curve, we'll catch it
|
||||||
|
try {
|
||||||
|
// Need to await to make sure browser succeeds
|
||||||
|
return await webSign(curve, hash_algo, message, keyPair);
|
||||||
|
} catch (err) {
|
||||||
|
util.print_debug_error("Browser did not support signing: " + err.message);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'node': {
|
||||||
|
const signature = await nodeSign(curve, hash_algo, message, keyPair);
|
||||||
|
return {
|
||||||
|
r: signature.r.toArrayLike(Uint8Array),
|
||||||
|
s: signature.s.toArrayLike(Uint8Array)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ellipticSign(curve, hashed, privateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,16 +81,198 @@ async function sign(oid, hash_algo, m, d, hashed) {
|
||||||
* @param {module:enums.hash} hash_algo Hash algorithm used in the signature
|
* @param {module:enums.hash} hash_algo Hash algorithm used in the signature
|
||||||
* @param {{r: Uint8Array,
|
* @param {{r: Uint8Array,
|
||||||
s: Uint8Array}} signature Signature to verify
|
s: Uint8Array}} signature Signature to verify
|
||||||
* @param {Uint8Array} m Message to verify
|
* @param {Uint8Array} message Message to verify
|
||||||
* @param {Uint8Array} Q Public key used to verify the message
|
* @param {Uint8Array} publicKey Public key used to verify the message
|
||||||
* @param {Uint8Array} hashed The hashed message
|
* @param {Uint8Array} hashed The hashed message
|
||||||
* @returns {Boolean}
|
* @returns {Boolean}
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
async function verify(oid, hash_algo, signature, m, Q, hashed) {
|
async function verify(oid, hash_algo, signature, message, publicKey, hashed) {
|
||||||
const curve = new Curve(oid);
|
const curve = new Curve(oid);
|
||||||
const key = curve.keyFromPublic(Q);
|
if (message && !message.locked) {
|
||||||
return key.verify(m, signature, hash_algo, hashed);
|
message = await stream.readToEnd(message);
|
||||||
|
switch (curve.type) {
|
||||||
|
case 'web':
|
||||||
|
try {
|
||||||
|
// Need to await to make sure browser succeeds
|
||||||
|
return await webVerify(curve, hash_algo, signature, message, publicKey);
|
||||||
|
} catch (err) {
|
||||||
|
util.print_debug_error("Browser did not support verifying: " + err.message);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'node':
|
||||||
|
return nodeVerify(curve, hash_algo, signature, message, publicKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const digest = (typeof hash_algo === 'undefined') ? message : hashed;
|
||||||
|
return ellipticVerify(curve, signature, digest, publicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { sign, verify };
|
export default { sign, verify, ellipticVerify, ellipticSign };
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////
|
||||||
|
// //
|
||||||
|
// Helper functions //
|
||||||
|
// //
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
async function ellipticSign(curve, hashed, privateKey) {
|
||||||
|
const indutnyCurve = await getIndutnyCurve(curve.name);
|
||||||
|
const key = keyFromPrivate(indutnyCurve, privateKey);
|
||||||
|
const signature = key.sign(hashed);
|
||||||
|
return {
|
||||||
|
r: signature.r.toArrayLike(Uint8Array),
|
||||||
|
s: signature.s.toArrayLike(Uint8Array)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ellipticVerify(curve, signature, digest, publicKey) {
|
||||||
|
const indutnyCurve = await getIndutnyCurve(curve.name);
|
||||||
|
const key = keyFromPublic(indutnyCurve, publicKey);
|
||||||
|
return key.verify(digest, signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function webSign(curve, hash_algo, message, keyPair) {
|
||||||
|
const len = curve.payloadSize;
|
||||||
|
const jwk = privateToJwk(curve.payloadSize, webCurves[curve.name], keyPair.publicKey, keyPair.privateKey);
|
||||||
|
const key = await webCrypto.importKey(
|
||||||
|
"jwk",
|
||||||
|
jwk,
|
||||||
|
{
|
||||||
|
"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, len),
|
||||||
|
s: signature.slice(len, len << 1)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function webVerify(curve, hash_algo, { r, s }, message, publicKey) {
|
||||||
|
const len = curve.payloadSize;
|
||||||
|
const jwk = rawPublicToJwk(curve.payloadSize, webCurves[curve.name], publicKey);
|
||||||
|
const key = await webCrypto.importKey(
|
||||||
|
"jwk",
|
||||||
|
jwk,
|
||||||
|
{
|
||||||
|
"name": "ECDSA",
|
||||||
|
"namedCurve": webCurves[curve.name],
|
||||||
|
"hash": { name: enums.read(enums.webHash, curve.hash) }
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
["verify"]
|
||||||
|
);
|
||||||
|
|
||||||
|
const signature = util.concatUint8Array([
|
||||||
|
new Uint8Array(len - r.length), r,
|
||||||
|
new Uint8Array(len - s.length), s
|
||||||
|
]).buffer;
|
||||||
|
|
||||||
|
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 sign = nodeCrypto.createSign(enums.read(enums.hash, hash_algo));
|
||||||
|
sign.write(message);
|
||||||
|
sign.end();
|
||||||
|
const key = ECPrivateKey.encode({
|
||||||
|
version: 1,
|
||||||
|
parameters: curve.oid,
|
||||||
|
privateKey: Array.from(keyPair.privateKey),
|
||||||
|
publicKey: { unused: 0, data: Array.from(keyPair.publicKey) }
|
||||||
|
}, 'pem', {
|
||||||
|
label: 'EC PRIVATE KEY'
|
||||||
|
});
|
||||||
|
|
||||||
|
return ECDSASignature.decode(sign.sign(key), 'der');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function nodeVerify(curve, hash_algo, { r, s }, message, publicKey) {
|
||||||
|
const verify = nodeCrypto.createVerify(enums.read(enums.hash, hash_algo));
|
||||||
|
verify.write(message);
|
||||||
|
verify.end();
|
||||||
|
const key = SubjectPublicKeyInfo.encode({
|
||||||
|
algorithm: {
|
||||||
|
algorithm: [1, 2, 840, 10045, 2, 1],
|
||||||
|
parameters: curve.oid
|
||||||
|
},
|
||||||
|
subjectPublicKey: { unused: 0, data: Array.from(publicKey) }
|
||||||
|
}, 'pem', {
|
||||||
|
label: 'PUBLIC KEY'
|
||||||
|
});
|
||||||
|
const signature = ECDSASignature.encode({
|
||||||
|
r: new BN(r), s: new BN(s)
|
||||||
|
}, 'der');
|
||||||
|
|
||||||
|
try {
|
||||||
|
return verify.verify(key, signature);
|
||||||
|
} catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Originally written by Owen Smith https://github.com/omsmith
|
||||||
|
// Adapted on Feb 2018 from https://github.com/Brightspace/node-jwk-to-pem/
|
||||||
|
|
||||||
|
/* eslint-disable no-invalid-this */
|
||||||
|
|
||||||
|
const asn1 = nodeCrypto ? require('asn1.js') : undefined;
|
||||||
|
|
||||||
|
const ECDSASignature = nodeCrypto ?
|
||||||
|
asn1.define('ECDSASignature', function() {
|
||||||
|
this.seq().obj(
|
||||||
|
this.key('r').int(),
|
||||||
|
this.key('s').int()
|
||||||
|
);
|
||||||
|
}) : undefined;
|
||||||
|
|
||||||
|
const ECPrivateKey = nodeCrypto ?
|
||||||
|
asn1.define('ECPrivateKey', function() {
|
||||||
|
this.seq().obj(
|
||||||
|
this.key('version').int(),
|
||||||
|
this.key('privateKey').octstr(),
|
||||||
|
this.key('parameters').explicit(0).optional().any(),
|
||||||
|
this.key('publicKey').explicit(1).optional().bitstr()
|
||||||
|
);
|
||||||
|
}) : undefined;
|
||||||
|
|
||||||
|
const AlgorithmIdentifier = nodeCrypto ?
|
||||||
|
asn1.define('AlgorithmIdentifier', function() {
|
||||||
|
this.seq().obj(
|
||||||
|
this.key('algorithm').objid(),
|
||||||
|
this.key('parameters').optional().any()
|
||||||
|
);
|
||||||
|
}) : undefined;
|
||||||
|
|
||||||
|
const SubjectPublicKeyInfo = nodeCrypto ?
|
||||||
|
asn1.define('SubjectPublicKeyInfo', function() {
|
||||||
|
this.seq().obj(
|
||||||
|
this.key('algorithm').use(AlgorithmIdentifier),
|
||||||
|
this.key('subjectPublicKey').bitstr()
|
||||||
|
);
|
||||||
|
}) : undefined;
|
||||||
|
|
85
src/crypto/public_key/elliptic/indutnyKey.js
Normal file
85
src/crypto/public_key/elliptic/indutnyKey.js
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @fileoverview Wrapper for a KeyPair of an curve from indutny/elliptic library
|
||||||
|
* @requires enums
|
||||||
|
* @requires asn1.js
|
||||||
|
* @module crypto/public_key/elliptic/indutnyKey
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { loadScript, dl } from '../../../lightweight_helper';
|
||||||
|
import config from '../../../config';
|
||||||
|
import util from '../../../util';
|
||||||
|
|
||||||
|
export function keyFromPrivate(indutnyCurve, priv) {
|
||||||
|
const keyPair = indutnyCurve.keyPair({ priv: priv });
|
||||||
|
return keyPair;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function keyFromPublic(indutnyCurve, pub) {
|
||||||
|
const keyPair = indutnyCurve.keyPair({ pub: pub });
|
||||||
|
if (keyPair.validate().result !== true) {
|
||||||
|
throw new Error('Invalid elliptic public key');
|
||||||
|
}
|
||||||
|
return keyPair;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load elliptic on demand to the window.openpgp.elliptic
|
||||||
|
* @returns {Promise<elliptic>}
|
||||||
|
*/
|
||||||
|
async function loadEllipticPromise() {
|
||||||
|
const path = config.indutny_elliptic_path;
|
||||||
|
const options = config.indutny_elliptic_fetch_options;
|
||||||
|
const ellipticDlPromise = dl(path, options).catch(() => dl(path, options));
|
||||||
|
const ellipticContents = await ellipticDlPromise;
|
||||||
|
const mainUrl = URL.createObjectURL(new Blob([ellipticContents], { type: 'text/javascript' }));
|
||||||
|
await loadScript(mainUrl);
|
||||||
|
URL.revokeObjectURL(mainUrl);
|
||||||
|
if (!window.openpgp.elliptic) {
|
||||||
|
throw new Error('Elliptic library failed to load correctly');
|
||||||
|
}
|
||||||
|
return window.openpgp.elliptic;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ellipticPromise;
|
||||||
|
|
||||||
|
function loadElliptic() {
|
||||||
|
if (!config.external_indutny_elliptic) {
|
||||||
|
return require('elliptic');
|
||||||
|
}
|
||||||
|
if (util.detectNode()) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
return require(config.indutny_elliptic_path);
|
||||||
|
}
|
||||||
|
if (!ellipticPromise) {
|
||||||
|
ellipticPromise = loadEllipticPromise().catch(e => {
|
||||||
|
ellipticPromise = undefined;
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return ellipticPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getIndutnyCurve(name) {
|
||||||
|
if (!config.use_indutny_elliptic) {
|
||||||
|
throw new Error('This curve is only supported in the full build of OpenPGP.js');
|
||||||
|
}
|
||||||
|
const elliptic = await loadElliptic();
|
||||||
|
return new elliptic.ec(name);
|
||||||
|
}
|
|
@ -1,274 +0,0 @@
|
||||||
// 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
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @fileoverview Wrapper for a KeyPair of an Elliptic Curve
|
|
||||||
* @requires bn.js
|
|
||||||
* @requires web-stream-tools
|
|
||||||
* @requires crypto/public_key/elliptic/curves
|
|
||||||
* @requires util
|
|
||||||
* @requires enums
|
|
||||||
* @requires asn1.js
|
|
||||||
* @module crypto/public_key/elliptic/key
|
|
||||||
*/
|
|
||||||
|
|
||||||
import BN from 'bn.js';
|
|
||||||
import stream from 'web-stream-tools';
|
|
||||||
import { webCurves } from './curves';
|
|
||||||
import util from '../../../util';
|
|
||||||
import enums from '../../../enums';
|
|
||||||
|
|
||||||
const webCrypto = util.getWebCrypto();
|
|
||||||
const nodeCrypto = util.getNodeCrypto();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function KeyPair(curve, options) {
|
|
||||||
this.curve = curve;
|
|
||||||
this.keyType = curve.curve.type === 'edwards' ? enums.publicKey.eddsa : enums.publicKey.ecdsa;
|
|
||||||
this.keyPair = this.curve.curve.keyPair(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyPair.prototype.sign = async function (message, hash_algo, hashed) {
|
|
||||||
if (message && !message.locked) {
|
|
||||||
message = await stream.readToEnd(message);
|
|
||||||
if (this.curve.web && util.getWebCrypto()) {
|
|
||||||
// If browser doesn't support a curve, we'll catch it
|
|
||||||
try {
|
|
||||||
// need to await to make sure browser succeeds
|
|
||||||
const signature = await webSign(this.curve, hash_algo, message, this.keyPair);
|
|
||||||
return signature;
|
|
||||||
} catch (err) {
|
|
||||||
util.print_debug("Browser did not support signing: " + err.message);
|
|
||||||
}
|
|
||||||
} else if (this.curve.node && util.getNodeCrypto()) {
|
|
||||||
return nodeSign(this.curve, hash_algo, message, this.keyPair);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const digest = (typeof hash_algo === 'undefined') ? message : hashed;
|
|
||||||
return this.keyPair.sign(digest);
|
|
||||||
};
|
|
||||||
|
|
||||||
KeyPair.prototype.verify = async function (message, signature, hash_algo, hashed) {
|
|
||||||
if (message && !message.locked) {
|
|
||||||
message = await stream.readToEnd(message);
|
|
||||||
if (this.curve.web && util.getWebCrypto()) {
|
|
||||||
// If browser doesn't support a curve, we'll catch it
|
|
||||||
try {
|
|
||||||
// need to await to make sure browser succeeds
|
|
||||||
const result = await webVerify(this.curve, hash_algo, signature, message, this.keyPair.getPublic());
|
|
||||||
return result;
|
|
||||||
} catch (err) {
|
|
||||||
util.print_debug("Browser did not support signing: " + err.message);
|
|
||||||
}
|
|
||||||
} else if (this.curve.node && util.getNodeCrypto()) {
|
|
||||||
return nodeVerify(this.curve, hash_algo, signature, message, this.keyPair.getPublic());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const digest = (typeof hash_algo === 'undefined') ? message : hashed;
|
|
||||||
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 () {
|
|
||||||
const compact = this.curve.curve.curve.type === 'edwards' ||
|
|
||||||
this.curve.curve.curve.type === 'mont';
|
|
||||||
return this.keyPair.getPublic('array', compact);
|
|
||||||
};
|
|
||||||
|
|
||||||
KeyPair.prototype.getPrivate = function () {
|
|
||||||
if (this.curve.keyType === enums.publicKey.eddsa) {
|
|
||||||
return this.keyPair.getSecret();
|
|
||||||
}
|
|
||||||
return this.keyPair.getPrivate().toArray();
|
|
||||||
};
|
|
||||||
|
|
||||||
export default KeyPair;
|
|
||||||
|
|
||||||
//////////////////////////
|
|
||||||
// //
|
|
||||||
// Helper functions //
|
|
||||||
// //
|
|
||||||
//////////////////////////
|
|
||||||
|
|
||||||
|
|
||||||
async function webSign(curve, hash_algo, message, keyPair) {
|
|
||||||
const len = curve.payloadSize;
|
|
||||||
const key = await webCrypto.importKey(
|
|
||||||
"jwk",
|
|
||||||
{
|
|
||||||
"kty": "EC",
|
|
||||||
"crv": webCurves[curve.name],
|
|
||||||
"x": util.Uint8Array_to_b64(new Uint8Array(keyPair.getPublic().getX().toArray('be', len)), true),
|
|
||||||
"y": util.Uint8Array_to_b64(new Uint8Array(keyPair.getPublic().getY().toArray('be', len)), true),
|
|
||||||
"d": util.Uint8Array_to_b64(new Uint8Array(keyPair.getPrivate().toArray('be', len)), 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: new BN(signature.slice(0, len)),
|
|
||||||
s: new BN(signature.slice(len, len << 1))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function webVerify(curve, hash_algo, { r, s }, message, publicKey) {
|
|
||||||
const len = curve.payloadSize;
|
|
||||||
const key = await webCrypto.importKey(
|
|
||||||
"jwk",
|
|
||||||
{
|
|
||||||
"kty": "EC",
|
|
||||||
"crv": webCurves[curve.name],
|
|
||||||
"x": util.Uint8Array_to_b64(new Uint8Array(publicKey.getX().toArray('be', len)), true),
|
|
||||||
"y": util.Uint8Array_to_b64(new Uint8Array(publicKey.getY().toArray('be', len)), true),
|
|
||||||
"use": "sig",
|
|
||||||
"kid": "ECDSA Public Key"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ECDSA",
|
|
||||||
"namedCurve": webCurves[curve.name],
|
|
||||||
"hash": { name: enums.read(enums.webHash, curve.hash) }
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
["verify"]
|
|
||||||
);
|
|
||||||
|
|
||||||
const signature = util.concatUint8Array([
|
|
||||||
new Uint8Array(len - r.length), r,
|
|
||||||
new Uint8Array(len - s.length), s
|
|
||||||
]).buffer;
|
|
||||||
|
|
||||||
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 sign = nodeCrypto.createSign(enums.read(enums.hash, hash_algo));
|
|
||||||
sign.write(message);
|
|
||||||
sign.end();
|
|
||||||
|
|
||||||
const key = ECPrivateKey.encode({
|
|
||||||
version: 1,
|
|
||||||
parameters: curve.oid,
|
|
||||||
privateKey: keyPair.getPrivate().toArray(),
|
|
||||||
publicKey: { unused: 0, data: keyPair.getPublic().encode() }
|
|
||||||
}, 'pem', {
|
|
||||||
label: 'EC PRIVATE KEY'
|
|
||||||
});
|
|
||||||
|
|
||||||
return ECDSASignature.decode(sign.sign(key), 'der');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function nodeVerify(curve, hash_algo, { r, s }, message, publicKey) {
|
|
||||||
const verify = nodeCrypto.createVerify(enums.read(enums.hash, hash_algo));
|
|
||||||
verify.write(message);
|
|
||||||
verify.end();
|
|
||||||
|
|
||||||
const key = SubjectPublicKeyInfo.encode({
|
|
||||||
algorithm: {
|
|
||||||
algorithm: [1, 2, 840, 10045, 2, 1],
|
|
||||||
parameters: curve.oid
|
|
||||||
},
|
|
||||||
subjectPublicKey: { unused: 0, data: publicKey.encode() }
|
|
||||||
}, 'pem', {
|
|
||||||
label: 'PUBLIC KEY'
|
|
||||||
});
|
|
||||||
|
|
||||||
const signature = ECDSASignature.encode({
|
|
||||||
r: new BN(r), s: new BN(s)
|
|
||||||
}, 'der');
|
|
||||||
|
|
||||||
try {
|
|
||||||
return verify.verify(key, signature);
|
|
||||||
} catch (err) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Originally written by Owen Smith https://github.com/omsmith
|
|
||||||
// Adapted on Feb 2018 from https://github.com/Brightspace/node-jwk-to-pem/
|
|
||||||
|
|
||||||
/* eslint-disable no-invalid-this */
|
|
||||||
|
|
||||||
const asn1 = nodeCrypto ? require('asn1.js') : undefined;
|
|
||||||
|
|
||||||
const ECDSASignature = nodeCrypto ?
|
|
||||||
asn1.define('ECDSASignature', function() {
|
|
||||||
this.seq().obj(
|
|
||||||
this.key('r').int(),
|
|
||||||
this.key('s').int()
|
|
||||||
);
|
|
||||||
}) : undefined;
|
|
||||||
|
|
||||||
const ECPrivateKey = nodeCrypto ?
|
|
||||||
asn1.define('ECPrivateKey', function() {
|
|
||||||
this.seq().obj(
|
|
||||||
this.key('version').int(),
|
|
||||||
this.key('privateKey').octstr(),
|
|
||||||
this.key('parameters').explicit(0).optional().any(),
|
|
||||||
this.key('publicKey').explicit(1).optional().bitstr()
|
|
||||||
);
|
|
||||||
}) : undefined;
|
|
||||||
|
|
||||||
const AlgorithmIdentifier = nodeCrypto ?
|
|
||||||
asn1.define('AlgorithmIdentifier', function() {
|
|
||||||
this.seq().obj(
|
|
||||||
this.key('algorithm').objid(),
|
|
||||||
this.key('parameters').optional().any()
|
|
||||||
);
|
|
||||||
}) : undefined;
|
|
||||||
|
|
||||||
const SubjectPublicKeyInfo = nodeCrypto ?
|
|
||||||
asn1.define('SubjectPublicKeyInfo', function() {
|
|
||||||
this.seq().obj(
|
|
||||||
this.key('algorithm').use(AlgorithmIdentifier),
|
|
||||||
this.key('subjectPublicKey').bitstr()
|
|
||||||
);
|
|
||||||
}) : undefined;
|
|
|
@ -122,8 +122,9 @@ export default {
|
||||||
}
|
}
|
||||||
case enums.publicKey.ecdsa: {
|
case enums.publicKey.ecdsa: {
|
||||||
const oid = key_params[0];
|
const oid = key_params[0];
|
||||||
|
const Q = key_params[1].toUint8Array();
|
||||||
const d = key_params[2].toUint8Array();
|
const d = key_params[2].toUint8Array();
|
||||||
const signature = await publicKey.elliptic.ecdsa.sign(oid, hash_algo, data, d, hashed);
|
const signature = await publicKey.elliptic.ecdsa.sign(oid, hash_algo, data, Q, d, hashed);
|
||||||
return util.concatUint8Array([
|
return util.concatUint8Array([
|
||||||
util.Uint8Array_to_MPI(signature.r),
|
util.Uint8Array_to_MPI(signature.r),
|
||||||
util.Uint8Array_to_MPI(signature.s)
|
util.Uint8Array_to_MPI(signature.s)
|
||||||
|
|
|
@ -153,3 +153,9 @@ export { default as HKP } from './hkp';
|
||||||
* @name module:openpgp.WKD
|
* @name module:openpgp.WKD
|
||||||
*/
|
*/
|
||||||
export { default as WKD } from './wkd';
|
export { default as WKD } from './wkd';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see module:lightweight
|
||||||
|
*/
|
||||||
|
import * as lightweightMod from './lightweight_helper';
|
||||||
|
export const lightweight = lightweightMod;
|
||||||
|
|
26
src/lightweight_helper.js
Normal file
26
src/lightweight_helper.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
/**
|
||||||
|
* Load script from path
|
||||||
|
* @param {String} path
|
||||||
|
*/
|
||||||
|
export const loadScript = path => {
|
||||||
|
if (typeof importScripts !== 'undefined') {
|
||||||
|
return importScripts(path);
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = path;
|
||||||
|
script.onload = () => resolve();
|
||||||
|
script.onerror = e => reject(new Error(e.message));
|
||||||
|
document.head.appendChild(script);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download script from path
|
||||||
|
* @param {String} path fetch path
|
||||||
|
* @param {Object} options fetch options
|
||||||
|
*/
|
||||||
|
export const dl = async function(path, options) {
|
||||||
|
const response = await fetch(path, options);
|
||||||
|
return response.arrayBuffer();
|
||||||
|
};
|
|
@ -573,9 +573,12 @@ Message.prototype.verify = async function(keys, date = new Date(), streaming) {
|
||||||
if (literalDataList.length !== 1) {
|
if (literalDataList.length !== 1) {
|
||||||
throw new Error('Can only verify message with one literal data packet.');
|
throw new Error('Can only verify message with one literal data packet.');
|
||||||
}
|
}
|
||||||
|
if (!streaming) {
|
||||||
|
msg.packets.concat(await stream.readToEnd(msg.packets.stream, _ => _));
|
||||||
|
}
|
||||||
const onePassSigList = msg.packets.filterByTag(enums.packet.onePassSignature).reverse();
|
const onePassSigList = msg.packets.filterByTag(enums.packet.onePassSignature).reverse();
|
||||||
const signatureList = msg.packets.filterByTag(enums.packet.signature);
|
const signatureList = msg.packets.filterByTag(enums.packet.signature);
|
||||||
if (onePassSigList.length && !signatureList.length && msg.packets.stream) {
|
if (streaming && onePassSigList.length && !signatureList.length && msg.packets.stream) {
|
||||||
await Promise.all(onePassSigList.map(async onePassSig => {
|
await Promise.all(onePassSigList.map(async onePassSig => {
|
||||||
onePassSig.correspondingSig = new Promise((resolve, reject) => {
|
onePassSig.correspondingSig = new Promise((resolve, reject) => {
|
||||||
onePassSig.correspondingSigResolve = resolve;
|
onePassSig.correspondingSigResolve = resolve;
|
||||||
|
@ -602,9 +605,9 @@ Message.prototype.verify = async function(keys, date = new Date(), streaming) {
|
||||||
await writer.abort(e);
|
await writer.abort(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return createVerificationObjects(onePassSigList, literalDataList, keys, date, false);
|
return createVerificationObjects(onePassSigList, literalDataList, keys, date, false, streaming);
|
||||||
}
|
}
|
||||||
return createVerificationObjects(signatureList, literalDataList, keys, date, false);
|
return createVerificationObjects(signatureList, literalDataList, keys, date, false, streaming);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -637,7 +640,7 @@ Message.prototype.verifyDetached = function(signature, keys, date = new Date())
|
||||||
* valid: Boolean}>>} list of signer's keyid and validity of signature
|
* valid: Boolean}>>} list of signer's keyid and validity of signature
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
async function createVerificationObject(signature, literalDataList, keys, date = new Date(), detached = false) {
|
async function createVerificationObject(signature, literalDataList, keys, date = new Date(), detached = false, streaming = false) {
|
||||||
let primaryKey = null;
|
let primaryKey = null;
|
||||||
let signingKey = null;
|
let signingKey = null;
|
||||||
await Promise.all(keys.map(async function(key) {
|
await Promise.all(keys.map(async function(key) {
|
||||||
|
@ -656,7 +659,7 @@ async function createVerificationObject(signature, literalDataList, keys, date =
|
||||||
if (!signingKey) {
|
if (!signingKey) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const verified = await signature.verify(signingKey.keyPacket, signature.signatureType, literalDataList[0], detached);
|
const verified = await signature.verify(signingKey.keyPacket, signature.signatureType, literalDataList[0], detached, streaming);
|
||||||
const sig = await signaturePacket;
|
const sig = await signaturePacket;
|
||||||
if (sig.isExpired(date) || !(
|
if (sig.isExpired(date) || !(
|
||||||
sig.created >= signingKey.getCreationTime() &&
|
sig.created >= signingKey.getCreationTime() &&
|
||||||
|
@ -699,11 +702,11 @@ async function createVerificationObject(signature, literalDataList, keys, date =
|
||||||
* valid: Boolean}>>} list of signer's keyid and validity of signature
|
* valid: Boolean}>>} list of signer's keyid and validity of signature
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
export async function createVerificationObjects(signatureList, literalDataList, keys, date = new Date(), detached = false) {
|
export async function createVerificationObjects(signatureList, literalDataList, keys, date = new Date(), detached = false, streaming = false) {
|
||||||
return Promise.all(signatureList.filter(function(signature) {
|
return Promise.all(signatureList.filter(function(signature) {
|
||||||
return ['text', 'binary'].includes(enums.read(enums.signature, signature.signatureType));
|
return ['text', 'binary'].includes(enums.read(enums.signature, signature.signatureType));
|
||||||
}).map(async function(signature) {
|
}).map(async function(signature) {
|
||||||
return createVerificationObject(signature, literalDataList, keys, date, detached);
|
return createVerificationObject(signature, literalDataList, keys, date, detached, streaming);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -680,7 +680,7 @@ Signature.prototype.hash = async function(signatureType, data, toHash, detached
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* verifys the signature packet. Note: not signature types are implemented
|
* verifies the signature packet. Note: not all signature types are implemented
|
||||||
* @param {module:packet.PublicSubkey|module:packet.PublicKey|
|
* @param {module:packet.PublicSubkey|module:packet.PublicKey|
|
||||||
* module:packet.SecretSubkey|module:packet.SecretKey} key the public key to verify the signature
|
* module:packet.SecretSubkey|module:packet.SecretKey} key the public key to verify the signature
|
||||||
* @param {module:enums.signature} signatureType expected signature type
|
* @param {module:enums.signature} signatureType expected signature type
|
||||||
|
@ -689,7 +689,7 @@ Signature.prototype.hash = async function(signatureType, data, toHash, detached
|
||||||
* @returns {Promise<Boolean>} True if message is verified, else false.
|
* @returns {Promise<Boolean>} True if message is verified, else false.
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
Signature.prototype.verify = async function (key, signatureType, data, detached = false) {
|
Signature.prototype.verify = async function (key, signatureType, data, detached = false, streaming = false) {
|
||||||
const publicKeyAlgorithm = enums.write(enums.publicKey, this.publicKeyAlgorithm);
|
const publicKeyAlgorithm = enums.write(enums.publicKey, this.publicKeyAlgorithm);
|
||||||
const hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm);
|
const hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm);
|
||||||
|
|
||||||
|
@ -703,10 +703,10 @@ Signature.prototype.verify = async function (key, signatureType, data, detached
|
||||||
hash = this.hashed;
|
hash = this.hashed;
|
||||||
} else {
|
} else {
|
||||||
toHash = this.toHash(signatureType, data, detached);
|
toHash = this.toHash(signatureType, data, detached);
|
||||||
|
if (!streaming) toHash = await stream.readToEnd(toHash);
|
||||||
hash = await this.hash(signatureType, data, toHash);
|
hash = await this.hash(signatureType, data, toHash);
|
||||||
}
|
}
|
||||||
hash = await stream.readToEnd(hash);
|
hash = await stream.readToEnd(hash);
|
||||||
|
|
||||||
if (this.signedHashValue[0] !== hash[0] ||
|
if (this.signedHashValue[0] !== hash[0] ||
|
||||||
this.signedHashValue[1] !== hash[1]) {
|
this.signedHashValue[1] !== hash[1]) {
|
||||||
this.verified = false;
|
this.verified = false;
|
||||||
|
@ -736,7 +736,6 @@ Signature.prototype.verify = async function (key, signatureType, data, detached
|
||||||
mpi[j] = new type_mpi();
|
mpi[j] = new type_mpi();
|
||||||
i += mpi[j].read(this.signature.subarray(i, this.signature.length), endian);
|
i += mpi[j].read(this.signature.subarray(i, this.signature.length), endian);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.verified = await crypto.signature.verify(
|
this.verified = await crypto.signature.verify(
|
||||||
publicKeyAlgorithm, hashAlgorithm, mpi, key.params,
|
publicKeyAlgorithm, hashAlgorithm, mpi, key.params,
|
||||||
toHash, hash
|
toHash, hash
|
||||||
|
|
|
@ -5,7 +5,7 @@ chai.use(require('chai-as-promised'));
|
||||||
|
|
||||||
const expect = chai.expect;
|
const expect = chai.expect;
|
||||||
|
|
||||||
describe('Elliptic Curve Cryptography', function () {
|
describe('Elliptic Curve Cryptography @lightweight', function () {
|
||||||
const elliptic_curves = openpgp.crypto.publicKey.elliptic;
|
const elliptic_curves = openpgp.crypto.publicKey.elliptic;
|
||||||
const key_data = {
|
const key_data = {
|
||||||
p256: {
|
p256: {
|
||||||
|
@ -152,7 +152,11 @@ describe('Elliptic Curve Cryptography', function () {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
it('Creating KeyPair', function () {
|
it('Creating KeyPair', function () {
|
||||||
const names = ['p256', 'p384', 'p521', 'secp256k1', 'curve25519', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1'];
|
if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) {
|
||||||
|
this.skip();
|
||||||
|
}
|
||||||
|
const names = openpgp.config.use_indutny_elliptic ? ['p256', 'p384', 'p521', 'secp256k1', 'curve25519', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1'] :
|
||||||
|
['p256', 'p384', 'p521', 'curve25519'];
|
||||||
return Promise.all(names.map(function (name) {
|
return Promise.all(names.map(function (name) {
|
||||||
const curve = new elliptic_curves.Curve(name);
|
const curve = new elliptic_curves.Curve(name);
|
||||||
return curve.genKeyPair().then(keyPair => {
|
return curve.genKeyPair().then(keyPair => {
|
||||||
|
@ -160,54 +164,28 @@ describe('Elliptic Curve Cryptography', function () {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
it('Creating KeyPair from data', function (done) {
|
|
||||||
for (const name in key_data) {
|
|
||||||
const pair = key_data[name];
|
|
||||||
const curve = new elliptic_curves.Curve(name);
|
|
||||||
expect(curve).to.exist;
|
|
||||||
const keyPair = curve.keyFromPrivate(pair.priv);
|
|
||||||
expect(keyPair).to.exist;
|
|
||||||
const pub = keyPair.getPublic();
|
|
||||||
expect(pub).to.exist;
|
|
||||||
expect(openpgp.util.Uint8Array_to_hex(pub)).to.equal(openpgp.util.Uint8Array_to_hex(pair.pub));
|
|
||||||
}
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
it('Signature verification', function (done) {
|
it('Signature verification', function (done) {
|
||||||
const curve = new elliptic_curves.Curve('p256');
|
|
||||||
const key = curve.keyFromPublic(signature_data.pub);
|
|
||||||
expect(
|
expect(
|
||||||
key.verify(signature_data.message, signature_data.signature, 8, signature_data.hashed)
|
elliptic_curves.ecdsa.verify('p256', 8, signature_data.signature, signature_data.message, signature_data.pub, signature_data.hashed)
|
||||||
).to.eventually.be.true.notify(done);
|
).to.eventually.be.true.notify(done);
|
||||||
});
|
});
|
||||||
it('Invalid signature', function (done) {
|
it('Invalid signature', function (done) {
|
||||||
const curve = new elliptic_curves.Curve('p256');
|
|
||||||
const key = curve.keyFromPublic(key_data.p256.pub);
|
|
||||||
expect(
|
expect(
|
||||||
key.verify(signature_data.message, signature_data.signature, 8, signature_data.hashed)
|
elliptic_curves.ecdsa.verify('p256', 8, signature_data.signature, signature_data.message, key_data.p256.pub, signature_data.hashed)
|
||||||
).to.eventually.be.false.notify(done);
|
).to.eventually.be.false.notify(done);
|
||||||
});
|
});
|
||||||
it('Signature generation', function () {
|
it('Signature generation', function () {
|
||||||
const curve = new elliptic_curves.Curve('p256');
|
return elliptic_curves.ecdsa.sign('p256', 8, signature_data.message, key_data.p256.pub, key_data.p256.priv, signature_data.hashed).then(async signature => {
|
||||||
let key = curve.keyFromPrivate(key_data.p256.priv);
|
|
||||||
return key.sign(signature_data.message, 8, signature_data.hashed).then(async ({ r, s }) => {
|
|
||||||
const signature = { r: new Uint8Array(r.toArray()), s: new Uint8Array(s.toArray()) };
|
|
||||||
key = curve.keyFromPublic(key_data.p256.pub);
|
|
||||||
await expect(
|
await expect(
|
||||||
key.verify(signature_data.message, signature, 8, signature_data.hashed)
|
elliptic_curves.ecdsa.verify('p256', 8, signature, signature_data.message, key_data.p256.pub, signature_data.hashed)
|
||||||
).to.eventually.be.true;
|
).to.eventually.be.true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('Shared secret generation', function (done) {
|
it('Shared secret generation', async function () {
|
||||||
const curve = new elliptic_curves.Curve('p256');
|
const curve = new elliptic_curves.Curve('p256');
|
||||||
let key1 = curve.keyFromPrivate(key_data.p256.priv);
|
const { sharedKey: shared1 } = await elliptic_curves.ecdh.genPrivateEphemeralKey(curve, signature_data.pub, key_data.p256.pub, key_data.p256.priv);
|
||||||
let key2 = curve.keyFromPublic(signature_data.pub);
|
const { sharedKey: shared2 } = await elliptic_curves.ecdh.genPrivateEphemeralKey(curve, key_data.p256.pub, signature_data.pub, signature_data.priv);
|
||||||
const shared1 = openpgp.util.Uint8Array_to_hex(key1.derive(key2).toArrayLike(Uint8Array));
|
expect(shared1).to.deep.equal(shared2);
|
||||||
key1 = curve.keyFromPublic(key_data.p256.pub);
|
|
||||||
key2 = curve.keyFromPrivate(signature_data.priv);
|
|
||||||
const shared2 = openpgp.util.Uint8Array_to_hex(key2.derive(key1).toArrayLike(Uint8Array));
|
|
||||||
expect(shared1).to.equal(shared2);
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('ECDSA signature', function () {
|
describe('ECDSA signature', function () {
|
||||||
|
@ -222,6 +200,17 @@ describe('Elliptic Curve Cryptography', function () {
|
||||||
oid, hash, { r: new Uint8Array(r), s: new Uint8Array(s) }, message, new Uint8Array(pub), await openpgp.crypto.hash.digest(hash, message)
|
oid, hash, { r: new Uint8Array(r), s: new Uint8Array(s) }, message, new Uint8Array(pub), await openpgp.crypto.hash.digest(hash, message)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
const verify_signature_elliptic = async function (oid, hash, r, s, message, pub) {
|
||||||
|
if (openpgp.util.isString(message)) {
|
||||||
|
message = openpgp.util.str_to_Uint8Array(message);
|
||||||
|
} else if (!openpgp.util.isUint8Array(message)) {
|
||||||
|
message = new Uint8Array(message);
|
||||||
|
}
|
||||||
|
const ecdsa = elliptic_curves.ecdsa;
|
||||||
|
return ecdsa.ellipticVerify(
|
||||||
|
new elliptic_curves.Curve(oid), { r: new Uint8Array(r), s: new Uint8Array(s) }, await openpgp.crypto.hash.digest(hash, message), new Uint8Array(pub)
|
||||||
|
);
|
||||||
|
};
|
||||||
const secp256k1_dummy_value = new Uint8Array([
|
const secp256k1_dummy_value = new Uint8Array([
|
||||||
0x10, 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,
|
||||||
|
@ -264,22 +253,48 @@ describe('Elliptic Curve Cryptography', function () {
|
||||||
)).to.be.rejectedWith(Error, /Not valid curve/)
|
)).to.be.rejectedWith(Error, /Not valid curve/)
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
it('Invalid public key', function () {
|
it('Invalid public key', async function () {
|
||||||
return Promise.all([
|
if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) {
|
||||||
expect(verify_signature(
|
this.skip();
|
||||||
|
}
|
||||||
|
if (openpgp.util.getNodeCrypto()) {
|
||||||
|
await expect(verify_signature(
|
||||||
'secp256k1', 8, [], [], [], []
|
'secp256k1', 8, [], [], [], []
|
||||||
)).to.be.rejectedWith(Error, /Unknown point format/),
|
)).to.eventually.be.false;
|
||||||
expect(verify_signature(
|
await expect(verify_signature(
|
||||||
'secp256k1', 8, [], [], [], secp256k1_invalid_point_format
|
'secp256k1', 8, [], [], [], secp256k1_invalid_point_format
|
||||||
)).to.be.rejectedWith(Error, /Unknown point format/)
|
)).to.eventually.be.false;
|
||||||
]);
|
}
|
||||||
|
if (openpgp.config.use_indutny_elliptic) {
|
||||||
|
return Promise.all([
|
||||||
|
expect(verify_signature_elliptic(
|
||||||
|
'secp256k1', 8, [], [], [], []
|
||||||
|
)).to.be.rejectedWith(Error, /Unknown point format/),
|
||||||
|
expect(verify_signature_elliptic(
|
||||||
|
'secp256k1', 8, [], [], [], secp256k1_invalid_point_format
|
||||||
|
)).to.be.rejectedWith(Error, /Unknown point format/)
|
||||||
|
]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
it('Invalid point', function (done) {
|
it('Invalid point', function () {
|
||||||
expect(verify_signature(
|
if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) {
|
||||||
'secp256k1', 8, [], [], [], secp256k1_invalid_point
|
this.skip();
|
||||||
)).to.be.rejectedWith(Error, /Invalid elliptic public key/).notify(done);
|
}
|
||||||
|
if (openpgp.util.getNodeCrypto()) {
|
||||||
|
expect(verify_signature(
|
||||||
|
'secp256k1', 8, [], [], [], secp256k1_invalid_point
|
||||||
|
)).to.eventually.be.false;
|
||||||
|
}
|
||||||
|
if (openpgp.config.use_indutny_elliptic) {
|
||||||
|
expect(verify_signature_elliptic(
|
||||||
|
'secp256k1', 8, [], [], [], secp256k1_invalid_point
|
||||||
|
)).to.be.rejectedWith(Error, /Invalid elliptic public key/);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
it('Invalid signature', function (done) {
|
it('Invalid signature', function (done) {
|
||||||
|
if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) {
|
||||||
|
this.skip();
|
||||||
|
}
|
||||||
expect(verify_signature(
|
expect(verify_signature(
|
||||||
'secp256k1', 8, [], [], [], secp256k1_point
|
'secp256k1', 8, [], [], [], secp256k1_point
|
||||||
)).to.eventually.be.false.notify(done);
|
)).to.eventually.be.false.notify(done);
|
||||||
|
@ -312,11 +327,11 @@ describe('Elliptic Curve Cryptography', function () {
|
||||||
it('Sign and verify message', function () {
|
it('Sign and verify message', function () {
|
||||||
const curve = new elliptic_curves.Curve('p521');
|
const curve = new elliptic_curves.Curve('p521');
|
||||||
return curve.genKeyPair().then(async keyPair => {
|
return curve.genKeyPair().then(async keyPair => {
|
||||||
const keyPublic = new Uint8Array(keyPair.getPublic());
|
const keyPublic = new Uint8Array(keyPair.publicKey);
|
||||||
const keyPrivate = new Uint8Array(keyPair.getPrivate());
|
const keyPrivate = new Uint8Array(keyPair.privateKey);
|
||||||
const oid = curve.oid;
|
const oid = curve.oid;
|
||||||
const message = p384_message;
|
const message = p384_message;
|
||||||
return elliptic_curves.ecdsa.sign(oid, 10, message, keyPrivate, await openpgp.crypto.hash.digest(10, message)).then(async signature => {
|
return elliptic_curves.ecdsa.sign(oid, 10, message, keyPublic, keyPrivate, await openpgp.crypto.hash.digest(10, message)).then(async signature => {
|
||||||
await expect(elliptic_curves.ecdsa.verify(oid, 10, signature, message, keyPublic, await openpgp.crypto.hash.digest(10, message)))
|
await expect(elliptic_curves.ecdsa.verify(oid, 10, signature, message, keyPublic, await openpgp.crypto.hash.digest(10, message)))
|
||||||
.to.eventually.be.true;
|
.to.eventually.be.true;
|
||||||
});
|
});
|
||||||
|
@ -381,16 +396,25 @@ describe('Elliptic Curve Cryptography', function () {
|
||||||
)).to.be.rejectedWith(Error, /Not valid curve/).notify(done);
|
)).to.be.rejectedWith(Error, /Not valid curve/).notify(done);
|
||||||
});
|
});
|
||||||
it('Invalid ephemeral key', function (done) {
|
it('Invalid ephemeral key', function (done) {
|
||||||
|
if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) {
|
||||||
|
this.skip();
|
||||||
|
}
|
||||||
expect(decrypt_message(
|
expect(decrypt_message(
|
||||||
'secp256k1', 2, 7, [], [], [], [], []
|
'secp256k1', 2, 7, [], [], [], [], []
|
||||||
)).to.be.rejectedWith(Error, /Private key is not valid for specified curve|Unknown point format/).notify(done);
|
)).to.be.rejectedWith(Error, /Private key is not valid for specified curve|Unknown point format/).notify(done);
|
||||||
});
|
});
|
||||||
it('Invalid elliptic public key', function (done) {
|
it('Invalid elliptic public key', function (done) {
|
||||||
|
if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) {
|
||||||
|
this.skip();
|
||||||
|
}
|
||||||
expect(decrypt_message(
|
expect(decrypt_message(
|
||||||
'secp256k1', 2, 7, secp256k1_value, secp256k1_point, secp256k1_invalid_point, secp256k1_data, []
|
'secp256k1', 2, 7, secp256k1_value, secp256k1_point, secp256k1_invalid_point, secp256k1_data, []
|
||||||
)).to.be.rejectedWith(Error, /Public key is not valid for specified curve|Failed to translate Buffer to a EC_POINT|Invalid elliptic public key/).notify(done);
|
)).to.be.rejectedWith(Error, /Public key is not valid for specified curve|Failed to translate Buffer to a EC_POINT|Invalid elliptic public key/).notify(done);
|
||||||
});
|
});
|
||||||
it('Invalid key data integrity', function (done) {
|
it('Invalid key data integrity', function (done) {
|
||||||
|
if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) {
|
||||||
|
this.skip();
|
||||||
|
}
|
||||||
expect(decrypt_message(
|
expect(decrypt_message(
|
||||||
'secp256k1', 2, 7, secp256k1_value, secp256k1_point, secp256k1_point, secp256k1_data, []
|
'secp256k1', 2, 7, secp256k1_value, secp256k1_point, secp256k1_point, secp256k1_data, []
|
||||||
)).to.be.rejectedWith(Error, /Key Data Integrity failed/).notify(done);
|
)).to.be.rejectedWith(Error, /Key Data Integrity failed/).notify(done);
|
||||||
|
@ -497,6 +521,9 @@ describe('Elliptic Curve Cryptography', function () {
|
||||||
|
|
||||||
describe('ECDHE key generation', function () {
|
describe('ECDHE key generation', function () {
|
||||||
it('Invalid curve', function (done) {
|
it('Invalid curve', function (done) {
|
||||||
|
if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) {
|
||||||
|
this.skip();
|
||||||
|
}
|
||||||
expect(genPublicEphemeralKey("secp256k1", Q1, fingerprint1)
|
expect(genPublicEphemeralKey("secp256k1", Q1, fingerprint1)
|
||||||
).to.be.rejectedWith(Error, /Public key is not valid for specified curve|Failed to translate Buffer to a EC_POINT|Unknown point format/).notify(done);
|
).to.be.rejectedWith(Error, /Public key is not valid for specified curve|Failed to translate Buffer to a EC_POINT|Unknown point format/).notify(done);
|
||||||
});
|
});
|
||||||
|
@ -539,7 +566,7 @@ describe('Elliptic Curve Cryptography', function () {
|
||||||
|
|
||||||
it('Comparing keys derived using webCrypto and elliptic', async function () {
|
it('Comparing keys derived using webCrypto and elliptic', async function () {
|
||||||
const names = ["p256", "p384", "p521"];
|
const names = ["p256", "p384", "p521"];
|
||||||
if (!openpgp.util.getWebCrypto()) {
|
if (!openpgp.util.getWebCrypto() || !openpgp.config.use_indutny_elliptic) {
|
||||||
this.skip();
|
this.skip();
|
||||||
}
|
}
|
||||||
return Promise.all(names.map(async function (name) {
|
return Promise.all(names.map(async function (name) {
|
||||||
|
@ -562,7 +589,7 @@ describe('Elliptic Curve Cryptography', function () {
|
||||||
});
|
});
|
||||||
it('Comparing keys derived using nodeCrypto and elliptic', async function () {
|
it('Comparing keys derived using nodeCrypto and elliptic', async function () {
|
||||||
const names = ["p256", "p384", "p521"];
|
const names = ["p256", "p384", "p521"];
|
||||||
if (!openpgp.util.getNodeCrypto()) {
|
if (!openpgp.util.getNodeCrypto() || !openpgp.config.use_indutny_elliptic) {
|
||||||
this.skip();
|
this.skip();
|
||||||
}
|
}
|
||||||
return Promise.all(names.map(async function (name) {
|
return Promise.all(names.map(async function (name) {
|
||||||
|
|
|
@ -8,7 +8,13 @@ const input = require('./testInputs.js');
|
||||||
|
|
||||||
const expect = chai.expect;
|
const expect = chai.expect;
|
||||||
|
|
||||||
(openpgp.config.ci ? describe.skip : describe)('Brainpool Cryptography', function () {
|
(openpgp.config.ci ? describe.skip : describe)('Brainpool Cryptography @lightweight', function () {
|
||||||
|
//only x25519 crypto is fully functional in lightbuild
|
||||||
|
if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) {
|
||||||
|
before(function() {
|
||||||
|
this.skip();
|
||||||
|
});
|
||||||
|
}
|
||||||
const data = {
|
const data = {
|
||||||
romeo: {
|
romeo: {
|
||||||
id: 'fa3d64c9bcf338bc',
|
id: 'fa3d64c9bcf338bc',
|
||||||
|
@ -222,7 +228,7 @@ EJ4QcD/oQ6x1M/8X/iKQCtxZP8RnlrbH7ExkNON5s5g=
|
||||||
const juliet = await load_pub_key('juliet');
|
const juliet = await load_pub_key('juliet');
|
||||||
const romeo = await load_priv_key('romeo');
|
const romeo = await load_priv_key('romeo');
|
||||||
const msg = await openpgp.message.readArmored(data.romeo.message_encrypted);
|
const msg = await openpgp.message.readArmored(data.romeo.message_encrypted);
|
||||||
const result = await openpgp.decrypt({privateKeys: romeo, publicKeys: [juliet], message: msg});
|
const result = await openpgp.decrypt({ privateKeys: romeo, publicKeys: [juliet], message: msg });
|
||||||
|
|
||||||
expect(result).to.exist;
|
expect(result).to.exist;
|
||||||
expect(result.data).to.equal(data.romeo.message);
|
expect(result.data).to.equal(data.romeo.message);
|
||||||
|
@ -241,6 +247,8 @@ EJ4QcD/oQ6x1M/8X/iKQCtxZP8RnlrbH7ExkNON5s5g=
|
||||||
expect(result.signatures[0].valid).to.be.true;
|
expect(result.signatures[0].valid).to.be.true;
|
||||||
});
|
});
|
||||||
it('Decrypt and verify message with leading zero in hash signed with old elliptic algorithm', async function () {
|
it('Decrypt and verify message with leading zero in hash signed with old elliptic algorithm', async function () {
|
||||||
|
//this test would not work with nodeCrypto, since message is signed with leading zero stripped from the hash
|
||||||
|
openpgp.config.use_native = false;
|
||||||
const juliet = await load_priv_key('juliet');
|
const juliet = await load_priv_key('juliet');
|
||||||
const romeo = await load_pub_key('romeo');
|
const romeo = await load_pub_key('romeo');
|
||||||
const msg = await openpgp.message.readArmored(data.romeo. message_encrypted_with_leading_zero_in_hash_signed_by_elliptic_with_old_implementation);
|
const msg = await openpgp.message.readArmored(data.romeo. message_encrypted_with_leading_zero_in_hash_signed_by_elliptic_with_old_implementation);
|
||||||
|
@ -331,12 +339,12 @@ function omnibus() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
tryTests('Brainpool Omnibus Tests', omnibus, {
|
tryTests('Brainpool Omnibus Tests @lightweight', omnibus, {
|
||||||
if: !openpgp.config.ci
|
if: !openpgp.config.ci && (openpgp.config.use_indutny_elliptic || openpgp.util.getNodeCrypto())
|
||||||
});
|
});
|
||||||
|
|
||||||
tryTests('Brainpool Omnibus Tests - Worker', omnibus, {
|
tryTests('Brainpool Omnibus Tests - Worker @lightweight', omnibus, {
|
||||||
if: typeof window !== 'undefined' && window.Worker,
|
if: typeof window !== 'undefined' && window.Worker && (openpgp.config.use_indutny_elliptic || openpgp.util.getNodeCrypto()),
|
||||||
before: async function() {
|
before: async function() {
|
||||||
await openpgp.initWorker({ path: '../dist/openpgp.worker.js' });
|
await openpgp.initWorker({ path: '../dist/openpgp.worker.js' });
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,234 +8,7 @@ const input = require('./testInputs.js');
|
||||||
|
|
||||||
const expect = chai.expect;
|
const expect = chai.expect;
|
||||||
|
|
||||||
describe('Elliptic Curve Cryptography', function () {
|
describe('Elliptic Curve Cryptography for NIST P-256,P-384,P-521 curves @lightweight', function () {
|
||||||
const 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?\n'
|
|
||||||
},
|
|
||||||
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?\n',
|
|
||||||
message_signed: [
|
|
||||||
'-----BEGIN PGP SIGNED MESSAGE-----',
|
|
||||||
'Hash: SHA256',
|
|
||||||
'',
|
|
||||||
'O Romeo, Romeo! Wherefore art thou Romeo?',
|
|
||||||
'',
|
|
||||||
'-----BEGIN PGP SIGNATURE-----',
|
|
||||||
'Version: OpenPGP.js v3.1.0',
|
|
||||||
'Comment: https://openpgpjs.org',
|
|
||||||
'',
|
|
||||||
'wl4EARMIABAFAltbFFMJEGQRYCGVm9/gAAAjugD/W/OZ++qiNlhy08OOflAN',
|
|
||||||
'rjjX3rknSZyUkr96HD4VWVsBAPL9QjyHI3714cdkQmwYGiG8TVrtPetnqHho',
|
|
||||||
'Ppmby7/I',
|
|
||||||
'=IyBz',
|
|
||||||
'-----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')
|
|
||||||
}
|
|
||||||
};
|
|
||||||
async function load_pub_key(name) {
|
|
||||||
if (data[name].pub_key) {
|
|
||||||
return data[name].pub_key;
|
|
||||||
}
|
|
||||||
const pub = await openpgp.key.readArmored(data[name].pub);
|
|
||||||
expect(pub).to.exist;
|
|
||||||
expect(pub.err).to.not.exist;
|
|
||||||
expect(pub.keys).to.have.length(1);
|
|
||||||
expect(pub.keys[0].getKeyId().toHex()).to.equal(data[name].id);
|
|
||||||
data[name].pub_key = pub.keys[0];
|
|
||||||
return data[name].pub_key;
|
|
||||||
}
|
|
||||||
async function load_priv_key(name) {
|
|
||||||
if (data[name].priv_key) {
|
|
||||||
return data[name].priv_key;
|
|
||||||
}
|
|
||||||
const pk = await openpgp.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].getKeyId().toHex()).to.equal(data[name].id);
|
|
||||||
expect(await pk.keys[0].decrypt(data[name].pass)).to.be.true;
|
|
||||||
data[name].priv_key = pk.keys[0];
|
|
||||||
return data[name].priv_key;
|
|
||||||
}
|
|
||||||
it('Load public key', async function () {
|
|
||||||
const romeoPublic = await load_pub_key('romeo');
|
|
||||||
expect(romeoPublic.users[0].userId.name).to.equal('Romeo Montague');
|
|
||||||
expect(romeoPublic.users[0].userId.email).to.equal('romeo@example.net');
|
|
||||||
expect(romeoPublic.users[0].userId.comment).to.equal('secp256k1');
|
|
||||||
const julietPublic = await load_pub_key('juliet');
|
|
||||||
expect(julietPublic.users[0].userId.name).to.equal('Juliet Capulet');
|
|
||||||
expect(julietPublic.users[0].userId.email).to.equal('juliet@example.net');
|
|
||||||
expect(julietPublic.users[0].userId.comment).to.equal('secp256k1');
|
|
||||||
});
|
|
||||||
it('Load private key', async function () {
|
|
||||||
await load_priv_key('romeo');
|
|
||||||
await load_priv_key('juliet');
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
it('Verify clear signed message', async function () {
|
|
||||||
const pub = await load_pub_key('juliet');
|
|
||||||
const msg = await openpgp.cleartext.readArmored(data.juliet.message_signed);
|
|
||||||
return openpgp.verify({publicKeys: [pub], message: msg}).then(function(result) {
|
|
||||||
expect(result).to.exist;
|
|
||||||
expect(result.data).to.equal(data.juliet.message);
|
|
||||||
expect(result.signatures).to.have.length(1);
|
|
||||||
expect(result.signatures[0].valid).to.be.true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('Sign message', async function () {
|
|
||||||
const romeoPrivate = await load_priv_key('romeo');
|
|
||||||
const signed = await openpgp.sign({privateKeys: [romeoPrivate], message: openpgp.cleartext.fromText(data.romeo.message)});
|
|
||||||
const romeoPublic = await load_pub_key('romeo');
|
|
||||||
const msg = await openpgp.cleartext.readArmored(signed.data);
|
|
||||||
const result = await openpgp.verify({publicKeys: [romeoPublic], message: msg});
|
|
||||||
|
|
||||||
expect(result).to.exist;
|
|
||||||
expect(result.data).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', async function () {
|
|
||||||
const juliet = await load_pub_key('juliet');
|
|
||||||
const romeo = await load_priv_key('romeo');
|
|
||||||
const msg = await openpgp.message.readArmored(data.juliet.message_encrypted);
|
|
||||||
const result = await openpgp.decrypt({privateKeys: romeo, publicKeys: [juliet], message: msg});
|
|
||||||
|
|
||||||
expect(result).to.exist;
|
|
||||||
expect(result.data).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', async function () {
|
|
||||||
const romeoPrivate = await load_priv_key('romeo');
|
|
||||||
const julietPublic = await load_pub_key('juliet');
|
|
||||||
const encrypted = await openpgp.encrypt({publicKeys: [julietPublic], privateKeys: [romeoPrivate], message: openpgp.message.fromText(data.romeo.message)});
|
|
||||||
|
|
||||||
const message = await openpgp.message.readArmored(encrypted.data);
|
|
||||||
const romeoPublic = await load_pub_key('romeo');
|
|
||||||
const julietPrivate = await load_priv_key('juliet');
|
|
||||||
const result = await openpgp.decrypt({privateKeys: julietPrivate, publicKeys: [romeoPublic], message: message});
|
|
||||||
|
|
||||||
expect(result).to.exist;
|
|
||||||
expect(result.data).to.equal(data.romeo.message);
|
|
||||||
expect(result.signatures).to.have.length(1);
|
|
||||||
expect(result.signatures[0].valid).to.be.true;
|
|
||||||
});
|
|
||||||
it('Generate key', function () {
|
|
||||||
const 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;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function omnibus() {
|
function omnibus() {
|
||||||
it('Omnibus NIST P-256 Test', function () {
|
it('Omnibus NIST P-256 Test', function () {
|
||||||
const options = { userIds: {name: "Hi", email: "hi@hel.lo"}, curve: "p256" };
|
const options = { userIds: {name: "Hi", email: "hi@hel.lo"}, curve: "p256" };
|
||||||
|
@ -295,6 +68,36 @@ describe('Elliptic Curve Cryptography', function () {
|
||||||
|
|
||||||
omnibus();
|
omnibus();
|
||||||
|
|
||||||
|
it('Sign message', async function () {
|
||||||
|
const testData = input.createSomeMessage();
|
||||||
|
let options = { userIds: {name: "Hi", email: "hi@hel.lo"}, curve: "p256" };
|
||||||
|
const firstKey = await openpgp.generateKey(options);
|
||||||
|
const signature = await openpgp.sign({ message: openpgp.cleartext.fromText(testData), privateKeys: firstKey.key });
|
||||||
|
const msg = await openpgp.cleartext.readArmored(signature.data);
|
||||||
|
const result = await openpgp.verify({ message: msg, publicKeys: firstKey.key.toPublic()});
|
||||||
|
expect(result.signatures[0].valid).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('encrypt and sign message', async function () {
|
||||||
|
const testData = input.createSomeMessage();
|
||||||
|
let options = { userIds: {name: "Hi", email: "hi@hel.lo"}, curve: "p256" };
|
||||||
|
const firstKey = await openpgp.generateKey(options);
|
||||||
|
options = { userIds: { name: "Bye", email: "bye@good.bye" }, curve: "p256" };
|
||||||
|
const secondKey = await openpgp.generateKey(options);
|
||||||
|
const encrypted = await openpgp.encrypt(
|
||||||
|
{ message: openpgp.message.fromText(testData),
|
||||||
|
publicKeys: [secondKey.key.toPublic()],
|
||||||
|
privateKeys: [firstKey.key] }
|
||||||
|
);
|
||||||
|
const msg = await openpgp.message.readArmored(encrypted.data);
|
||||||
|
const result = await openpgp.decrypt(
|
||||||
|
{ message: msg,
|
||||||
|
privateKeys: secondKey.key,
|
||||||
|
publicKeys: [firstKey.key.toPublic()] }
|
||||||
|
)
|
||||||
|
expect(result.signatures[0].valid).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
tryTests('ECC Worker Tests', omnibus, {
|
tryTests('ECC Worker Tests', omnibus, {
|
||||||
if: typeof window !== 'undefined' && window.Worker,
|
if: typeof window !== 'undefined' && window.Worker,
|
||||||
before: async function() {
|
before: async function() {
|
||||||
|
|
242
test/general/ecc_secp256k1.js
Normal file
242
test/general/ecc_secp256k1.js
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
/* globals tryTests: true */
|
||||||
|
|
||||||
|
const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp');
|
||||||
|
|
||||||
|
const chai = require('chai');
|
||||||
|
chai.use(require('chai-as-promised'));
|
||||||
|
|
||||||
|
const expect = chai.expect;
|
||||||
|
|
||||||
|
describe('Elliptic Curve Cryptography for secp256k1 curve @lightweight', function () {
|
||||||
|
if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) {
|
||||||
|
before(function() {
|
||||||
|
this.skip();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const 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?\n'
|
||||||
|
},
|
||||||
|
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?\n',
|
||||||
|
message_signed: [
|
||||||
|
'-----BEGIN PGP SIGNED MESSAGE-----',
|
||||||
|
'Hash: SHA256',
|
||||||
|
'',
|
||||||
|
'O Romeo, Romeo! Wherefore art thou Romeo?',
|
||||||
|
'',
|
||||||
|
'-----BEGIN PGP SIGNATURE-----',
|
||||||
|
'Version: OpenPGP.js v3.1.0',
|
||||||
|
'Comment: https://openpgpjs.org',
|
||||||
|
'',
|
||||||
|
'wl4EARMIABAFAltbFFMJEGQRYCGVm9/gAAAjugD/W/OZ++qiNlhy08OOflAN',
|
||||||
|
'rjjX3rknSZyUkr96HD4VWVsBAPL9QjyHI3714cdkQmwYGiG8TVrtPetnqHho',
|
||||||
|
'Ppmby7/I',
|
||||||
|
'=IyBz',
|
||||||
|
'-----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')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
async function load_pub_key(name) {
|
||||||
|
if (data[name].pub_key) {
|
||||||
|
return data[name].pub_key;
|
||||||
|
}
|
||||||
|
const pub = await openpgp.key.readArmored(data[name].pub);
|
||||||
|
expect(pub).to.exist;
|
||||||
|
expect(pub.err).to.not.exist;
|
||||||
|
expect(pub.keys).to.have.length(1);
|
||||||
|
expect(pub.keys[0].getKeyId().toHex()).to.equal(data[name].id);
|
||||||
|
data[name].pub_key = pub.keys[0];
|
||||||
|
return data[name].pub_key;
|
||||||
|
}
|
||||||
|
async function load_priv_key(name) {
|
||||||
|
if (data[name].priv_key) {
|
||||||
|
return data[name].priv_key;
|
||||||
|
}
|
||||||
|
const pk = await openpgp.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].getKeyId().toHex()).to.equal(data[name].id);
|
||||||
|
expect(await pk.keys[0].decrypt(data[name].pass)).to.be.true;
|
||||||
|
data[name].priv_key = pk.keys[0];
|
||||||
|
return data[name].priv_key;
|
||||||
|
}
|
||||||
|
it('Load public key', async function () {
|
||||||
|
const romeoPublic = await load_pub_key('romeo');
|
||||||
|
expect(romeoPublic.users[0].userId.name).to.equal('Romeo Montague');
|
||||||
|
expect(romeoPublic.users[0].userId.email).to.equal('romeo@example.net');
|
||||||
|
expect(romeoPublic.users[0].userId.comment).to.equal('secp256k1');
|
||||||
|
const julietPublic = await load_pub_key('juliet');
|
||||||
|
expect(julietPublic.users[0].userId.name).to.equal('Juliet Capulet');
|
||||||
|
expect(julietPublic.users[0].userId.email).to.equal('juliet@example.net');
|
||||||
|
expect(julietPublic.users[0].userId.comment).to.equal('secp256k1');
|
||||||
|
});
|
||||||
|
it('Load private key', async function () {
|
||||||
|
await load_priv_key('romeo');
|
||||||
|
await load_priv_key('juliet');
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
it('Verify clear signed message', async function () {
|
||||||
|
const pub = await load_pub_key('juliet');
|
||||||
|
const msg = await openpgp.cleartext.readArmored(data.juliet.message_signed);
|
||||||
|
return openpgp.verify({publicKeys: [pub], message: msg}).then(function(result) {
|
||||||
|
expect(result).to.exist;
|
||||||
|
expect(result.data).to.equal(data.juliet.message);
|
||||||
|
expect(result.signatures).to.have.length(1);
|
||||||
|
expect(result.signatures[0].valid).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Sign message', async function () {
|
||||||
|
const romeoPrivate = await load_priv_key('romeo');
|
||||||
|
const signed = await openpgp.sign({privateKeys: [romeoPrivate], message: openpgp.cleartext.fromText(data.romeo.message)});
|
||||||
|
const romeoPublic = await load_pub_key('romeo');
|
||||||
|
const msg = await openpgp.cleartext.readArmored(signed.data);
|
||||||
|
const result = await openpgp.verify({publicKeys: [romeoPublic], message: msg});
|
||||||
|
|
||||||
|
expect(result).to.exist;
|
||||||
|
expect(result.data).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', async function () {
|
||||||
|
const juliet = await load_pub_key('juliet');
|
||||||
|
const romeo = await load_priv_key('romeo');
|
||||||
|
const msg = await openpgp.message.readArmored(data.juliet.message_encrypted);
|
||||||
|
const result = await openpgp.decrypt({privateKeys: romeo, publicKeys: [juliet], message: msg});
|
||||||
|
|
||||||
|
expect(result).to.exist;
|
||||||
|
expect(result.data).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', async function () {
|
||||||
|
const romeoPrivate = await load_priv_key('romeo');
|
||||||
|
const julietPublic = await load_pub_key('juliet');
|
||||||
|
const encrypted = await openpgp.encrypt({publicKeys: [julietPublic], privateKeys: [romeoPrivate], message: openpgp.message.fromText(data.romeo.message)});
|
||||||
|
|
||||||
|
const message = await openpgp.message.readArmored(encrypted.data);
|
||||||
|
const romeoPublic = await load_pub_key('romeo');
|
||||||
|
const julietPrivate = await load_priv_key('juliet');
|
||||||
|
const result = await openpgp.decrypt({privateKeys: julietPrivate, publicKeys: [romeoPublic], message: message});
|
||||||
|
|
||||||
|
expect(result).to.exist;
|
||||||
|
expect(result.data).to.equal(data.romeo.message);
|
||||||
|
expect(result.signatures).to.have.length(1);
|
||||||
|
expect(result.signatures[0].valid).to.be.true;
|
||||||
|
});
|
||||||
|
it('Generate key', function () {
|
||||||
|
const 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;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -10,6 +10,7 @@ describe('General', function () {
|
||||||
require('./wkd.js');
|
require('./wkd.js');
|
||||||
require('./oid.js');
|
require('./oid.js');
|
||||||
require('./ecc_nist.js');
|
require('./ecc_nist.js');
|
||||||
|
require('./ecc_secp256k1.js');
|
||||||
require('./x25519.js');
|
require('./x25519.js');
|
||||||
require('./brainpool.js');
|
require('./brainpool.js');
|
||||||
require('./decompression.js');
|
require('./decompression.js');
|
||||||
|
|
|
@ -827,10 +827,7 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA
|
||||||
return openpgp.verify({ publicKeys: [pubKey], message: sMsg }).then(async function(cleartextSig) {
|
return openpgp.verify({ publicKeys: [pubKey], message: sMsg }).then(async function(cleartextSig) {
|
||||||
expect(cleartextSig).to.exist;
|
expect(cleartextSig).to.exist;
|
||||||
expect(openpgp.util.nativeEOL(openpgp.util.Uint8Array_to_str(await openpgp.stream.readToEnd(cleartextSig.data)))).to.equal(plaintext);
|
expect(openpgp.util.nativeEOL(openpgp.util.Uint8Array_to_str(await openpgp.stream.readToEnd(cleartextSig.data)))).to.equal(plaintext);
|
||||||
expect(cleartextSig.signatures).to.have.length(1);
|
expect(cleartextSig.signatures).to.have.length(0);
|
||||||
expect(cleartextSig.signatures[0].valid).to.be.null;
|
|
||||||
expect(cleartextSig.signatures[0].error.message).to.equal('Corresponding signature packet missing');
|
|
||||||
expect(cleartextSig.signatures[0].signature.packets.length).to.equal(0);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ describe('Unit Tests', function () {
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
openpgp.config.s2k_iteration_count_byte = 0;
|
openpgp.config.s2k_iteration_count_byte = 0;
|
||||||
|
openpgp.config.indutny_elliptic_path = '../dist/elliptic.min.js';
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
if (window.scrollY >= document.body.scrollHeight - window.innerHeight - 100
|
if (window.scrollY >= document.body.scrollHeight - window.innerHeight - 100
|
||||||
|
|
|
@ -9,14 +9,14 @@ if [ $OPENPGPJSTEST = "coverage" ]; then
|
||||||
|
|
||||||
elif [ $OPENPGPJSTEST = "unit" ]; then
|
elif [ $OPENPGPJSTEST = "unit" ]; then
|
||||||
echo "Running OpenPGP.js unit tests on node.js."
|
echo "Running OpenPGP.js unit tests on node.js."
|
||||||
npm test
|
grunt build test --lightweight=$LIGHTWEIGHT
|
||||||
|
|
||||||
elif [ $OPENPGPJSTEST = "browserstack" ]; then
|
elif [ $OPENPGPJSTEST = "browserstack" ]; then
|
||||||
echo "Running OpenPGP.js browser unit tests on Browserstack."
|
echo "Running OpenPGP.js browser unit tests on Browserstack."
|
||||||
|
|
||||||
grunt build browserify:unittests copy:browsertest --compat=$COMPAT
|
grunt build browserify:unittests copy:browsertest --compat=$COMPAT
|
||||||
echo -n "Using config: "
|
echo -n "Using config: "
|
||||||
echo "{\"browsers\": [$BROWSER], \"test_framework\": \"mocha\", \"test_path\": [\"test/unittests.html?ci=true\"], \"timeout\": 1800, \"exit_with_fail\": true, \"project\": \"openpgpjs/${TRAVIS_EVENT_TYPE:-push}${COMPAT:+/compat}\"}" > browserstack.json
|
echo "{\"browsers\": [$BROWSER], \"test_framework\": \"mocha\", \"test_path\": [\"test/unittests.html?ci=true\"], \"timeout\": 1800, \"exit_with_fail\": true, \"project\": \"openpgpjs/${TRAVIS_EVENT_TYPE:-push}${COMPAT:+/compat}${LIGHTWEIGHT:+/lightweight}\"}" > browserstack.json
|
||||||
cat browserstack.json
|
cat browserstack.json
|
||||||
|
|
||||||
result=0
|
result=0
|
||||||
|
|
Loading…
Reference in New Issue
Block a user