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
|
||||
}
|
||||
],
|
||||
"eol-last": "off",
|
||||
"eol-last": ["error", "always"],
|
||||
"eqeqeq": "error",
|
||||
"for-direction": "error",
|
||||
"func-call-spacing": "error",
|
||||
|
@ -186,7 +186,7 @@ module.exports = {
|
|||
}
|
||||
],
|
||||
"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-negated-condition": "off",
|
||||
"no-negated-in-lhs": "error",
|
||||
|
|
|
@ -15,10 +15,14 @@ matrix:
|
|||
env: OPENPGP_NODE_JS='10' OPENPGPJSTEST='unit'
|
||||
- node_js: "12"
|
||||
env: OPENPGP_NODE_JS='12' OPENPGPJSTEST='unit'
|
||||
- node_js: "10"
|
||||
env: OPENPGP_NODE_JS='10' OPENPGPJSTEST='unit' LIGHTWEIGHT=1
|
||||
- node_js: "9"
|
||||
env: BROWSER='"firefox_26"' OPENPGPJSTEST='browserstack' COMPAT=1
|
||||
- node_js: "9"
|
||||
env: BROWSER='"firefox_61"' OPENPGPJSTEST='browserstack'
|
||||
- node_js: "10"
|
||||
env: BROWSER='"chrome_68"' OPENPGPJSTEST='browserstack' LIGHTWEIGHT=1
|
||||
- node_js: "9"
|
||||
env: BROWSER='"chrome_49"' OPENPGPJSTEST='browserstack' COMPAT=1
|
||||
- node_js: "10"
|
||||
|
|
94
Gruntfile.js
94
Gruntfile.js
|
@ -1,20 +1,12 @@
|
|||
module.exports = function(grunt) {
|
||||
|
||||
var version = grunt.option('release');
|
||||
var 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';
|
||||
};
|
||||
const version = grunt.option('release');
|
||||
const fs = require('fs');
|
||||
|
||||
// Project configuration.
|
||||
const dev = !!grunt.option('dev');
|
||||
const compat = !!grunt.option('compat');
|
||||
const lightweight = !!grunt.option('lightweight');
|
||||
const plugins = compat ? [
|
||||
"transform-async-to-generator",
|
||||
"syntax-async-functions",
|
||||
|
@ -50,7 +42,7 @@ module.exports = function(grunt) {
|
|||
debug: dev,
|
||||
standalone: 'openpgp'
|
||||
},
|
||||
cacheFile: 'browserify-cache' + (compat ? '-compat' : '') + '.json',
|
||||
cacheFile: 'browserify-cache' + (compat ? '-compat' : '') + (lightweight ? '-lightweight' : '') + '.json',
|
||||
// Don't bundle these packages with openpgp.js
|
||||
external: ['crypto', 'zlib', 'node-localstorage', 'node-fetch', 'asn1.js', 'stream', 'buffer'].concat(
|
||||
compat ? [] : [
|
||||
|
@ -63,8 +55,12 @@ module.exports = function(grunt) {
|
|||
'core-js/fn/typed/uint8-array',
|
||||
'core-js/fn/string/repeat',
|
||||
'core-js/fn/symbol',
|
||||
'core-js/fn/object/assign',
|
||||
]
|
||||
'core-js/fn/object/assign'
|
||||
],
|
||||
lightweight ? [
|
||||
'elliptic',
|
||||
'elliptic.min.js'
|
||||
] : []
|
||||
),
|
||||
transform: [
|
||||
["babelify", {
|
||||
|
@ -131,6 +127,26 @@ module.exports = function(grunt) {
|
|||
from: "openpgp.js",
|
||||
to: "openpgp.min.js"
|
||||
}]
|
||||
},
|
||||
lightweight_build: {
|
||||
src: ['dist/openpgp.js'],
|
||||
overwrite: true,
|
||||
replacements: [
|
||||
{
|
||||
from: "external_indutny_elliptic: false",
|
||||
to: "external_indutny_elliptic: true"
|
||||
}
|
||||
]
|
||||
},
|
||||
indutny_global: {
|
||||
src: ['dist/elliptic.min.js'],
|
||||
overwrite: true,
|
||||
replacements: [
|
||||
{
|
||||
from: 'b.elliptic=a()',
|
||||
to: 'b.openpgp.elliptic=a()'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
terser: {
|
||||
|
@ -141,13 +157,13 @@ module.exports = function(grunt) {
|
|||
},
|
||||
options: {
|
||||
safari10: true
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
header: {
|
||||
openpgp: {
|
||||
options: {
|
||||
text: '/*! OpenPGP.js v<%= pkg.version %> - ' +
|
||||
text: '/*! OpenPGP.js v<%= pkg.version %> - ' +
|
||||
'<%= grunt.template.today("yyyy-mm-dd") %> - ' +
|
||||
'this is LGPL licensed code, see LICENSE/our website <%= pkg.homepage %> for more information. */'
|
||||
},
|
||||
|
@ -168,8 +184,11 @@ module.exports = function(grunt) {
|
|||
}
|
||||
},
|
||||
eslint: {
|
||||
target: ['src/**/*.js'],
|
||||
options: { configFile: '.eslintrc.js' }
|
||||
target: ['src/**/*.js', './Gruntfile.js'],
|
||||
options: {
|
||||
configFile: '.eslintrc.js',
|
||||
fix: !!grunt.option('fix')
|
||||
}
|
||||
},
|
||||
jsdoc: {
|
||||
dist: {
|
||||
|
@ -194,7 +213,8 @@ module.exports = function(grunt) {
|
|||
unittests: {
|
||||
options: {
|
||||
reporter: 'spec',
|
||||
timeout: 120000
|
||||
timeout: 120000,
|
||||
grep: lightweight ? 'lightweight' : undefined
|
||||
},
|
||||
src: ['test/unittests.js']
|
||||
}
|
||||
|
@ -212,9 +232,24 @@ module.exports = function(grunt) {
|
|||
cwd: 'dist/',
|
||||
src: ['*.js'],
|
||||
dest: 'dist/compat/'
|
||||
},
|
||||
openpgp_lightweight: {
|
||||
expand: true,
|
||||
cwd: 'dist/',
|
||||
src: ['*.js'],
|
||||
dest: 'dist/lightweight/'
|
||||
},
|
||||
indutny_elliptic: {
|
||||
expand: true,
|
||||
flatten: true,
|
||||
src: ['./node_modules/elliptic/dist/elliptic.min.js'],
|
||||
dest: 'dist/'
|
||||
}
|
||||
},
|
||||
clean: ['dist/'],
|
||||
clean: {
|
||||
dist: ['dist/'],
|
||||
js: ['dist/*.js']
|
||||
},
|
||||
connect: {
|
||||
dev: {
|
||||
options: {
|
||||
|
@ -233,7 +268,7 @@ module.exports = function(grunt) {
|
|||
watch: {
|
||||
src: {
|
||||
files: ['src/**/*.js'],
|
||||
tasks: ['browserify:openpgp', 'browserify:worker']
|
||||
tasks: lightweight ? ['browserify:openpgp', 'browserify:worker', 'replace:lightweight_build'] : ['browserify:openpgp', 'browserify:worker']
|
||||
},
|
||||
test: {
|
||||
files: ['test/*.js', 'test/crypto/**/*.js', 'test/general/**/*.js', 'test/worker/**/*.js'],
|
||||
|
@ -279,25 +314,32 @@ module.exports = function(grunt) {
|
|||
});
|
||||
|
||||
function patchFile(options) {
|
||||
var path = './' + options.fileName,
|
||||
file = require(path);
|
||||
const path = './' + options.fileName;
|
||||
//eslint-disable-next-line
|
||||
const file = require(path);
|
||||
|
||||
if (options.version) {
|
||||
file.version = options.version;
|
||||
}
|
||||
|
||||
//eslint-disable-next-line
|
||||
fs.writeFileSync(path, JSON.stringify(file, null, 2) + '\n');
|
||||
}
|
||||
|
||||
// Build tasks
|
||||
grunt.registerTask('version', ['replace:openpgp']);
|
||||
grunt.registerTask('replace_min', ['replace:openpgp_min', 'replace:worker_min']);
|
||||
grunt.registerTask('build', ['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('default', ['build']);
|
||||
// Test/Dev tasks
|
||||
grunt.registerTask('test', ['eslint', 'mochaTest']);
|
||||
grunt.registerTask('coverage', ['mocha_istanbul:coverage']);
|
||||
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
|
||||
},
|
||||
"elliptic": {
|
||||
"version": "github:openpgpjs/elliptic#6b7801573b8940a49e7b8253176ece2725841efd",
|
||||
"from": "github:openpgpjs/elliptic#6b7801573b8940a49e7b8253176ece2725841efd",
|
||||
"version": "github:openpgpjs/elliptic#ab7d8268c60b6abeb175841c578c224ac5b2d279",
|
||||
"from": "github:openpgpjs/elliptic#ab7d8268c60b6abeb175841c578c224ac5b2d279",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bn.js": "^4.4.0",
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
"test/crypto"
|
||||
],
|
||||
"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",
|
||||
"test": "grunt test",
|
||||
"lint": "eslint src"
|
||||
|
@ -73,7 +73,7 @@
|
|||
"asmcrypto.js": "github:openpgpjs/asmcrypto#6e4e407b9b8ae317925a9e677cc7b4de3e447e83",
|
||||
"bn.js": "^4.11.8",
|
||||
"buffer": "^5.0.8",
|
||||
"elliptic": "github:openpgpjs/elliptic#6b7801573b8940a49e7b8253176ece2725841efd",
|
||||
"elliptic": "github:openpgpjs/elliptic#ab7d8268c60b6abeb175841c578c224ac5b2d279",
|
||||
"hash.js": "^1.1.3",
|
||||
"pako": "^1.0.6",
|
||||
"seek-bzip": "github:openpgpjs/seek-bzip#6187fc025851d35c4e104a25ea15a10b9b8d6f7d",
|
||||
|
|
|
@ -190,5 +190,25 @@ export default {
|
|||
* @memberof module:config
|
||||
* @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
|
||||
* @requires bn.js
|
||||
* @requires elliptic
|
||||
* @requires tweetnacl
|
||||
* @requires crypto/public_key/elliptic/key
|
||||
* @requires crypto/random
|
||||
* @requires enums
|
||||
* @requires util
|
||||
* @requires type/oid
|
||||
* @requires config
|
||||
* @module crypto/public_key/elliptic/curve
|
||||
*/
|
||||
|
||||
import BN from 'bn.js';
|
||||
import { ec as EC, eddsa as EdDSA } from 'elliptic';
|
||||
import KeyPair from './key';
|
||||
import nacl from 'tweetnacl/nacl-fast-light.js';
|
||||
import random from '../../random';
|
||||
import enums from '../../../enums';
|
||||
import util from '../../../util';
|
||||
import OID from '../../../type/oid';
|
||||
import { getIndutnyCurve } from './indutnyKey';
|
||||
|
||||
const webCrypto = util.getWebCrypto();
|
||||
const nodeCrypto = util.getNodeCrypto();
|
||||
|
@ -152,16 +153,6 @@ function Curve(oid_or_name, params) {
|
|||
params = params || curves[this.name];
|
||||
|
||||
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.hash = params.hash;
|
||||
|
@ -169,49 +160,53 @@ function Curve(oid_or_name, params) {
|
|||
this.node = params.node && curves[this.name];
|
||||
this.web = params.web && curves[this.name];
|
||||
this.payloadSize = params.payloadSize;
|
||||
}
|
||||
|
||||
Curve.prototype.keyFromPrivate = function (priv) { // Not for ed25519
|
||||
return new KeyPair(this, { priv: priv });
|
||||
};
|
||||
|
||||
Curve.prototype.keyFromPublic = function (pub) {
|
||||
const keyPair = new KeyPair(this, { pub: pub });
|
||||
if (
|
||||
this.keyType === enums.publicKey.ecdsa &&
|
||||
keyPair.keyPair.validate().result !== true
|
||||
) {
|
||||
throw new Error('Invalid elliptic public key');
|
||||
if (this.web && util.getWebCrypto()) {
|
||||
this.type = 'web';
|
||||
} else if (this.node && util.getNodeCrypto()) {
|
||||
this.type = 'node';
|
||||
} else if (this.name === 'curve25519') {
|
||||
this.type = 'curve25519';
|
||||
} else if (this.name === 'ed25519') {
|
||||
this.type = 'ed25519';
|
||||
}
|
||||
return keyPair;
|
||||
};
|
||||
}
|
||||
|
||||
Curve.prototype.genKeyPair = async function () {
|
||||
let keyPair;
|
||||
if (this.web && util.getWebCrypto()) {
|
||||
// If browser doesn't support a curve, we'll catch it
|
||||
try {
|
||||
keyPair = await webGenKeyPair(this.name);
|
||||
} catch (err) {
|
||||
util.print_debug("Browser did not support signing: " + err.message);
|
||||
switch (this.type) {
|
||||
case 'web':
|
||||
try {
|
||||
return await webGenKeyPair(this.name);
|
||||
} catch (err) {
|
||||
util.print_debug_error("Browser did not support generating ec key " + err.message);
|
||||
break;
|
||||
}
|
||||
case 'node':
|
||||
return nodeGenKeyPair(this.name);
|
||||
case 'curve25519': {
|
||||
const privateKey = await random.getRandomBytes(32);
|
||||
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()) {
|
||||
keyPair = await nodeGenKeyPair(this.name);
|
||||
}
|
||||
|
||||
if (!keyPair || !keyPair.priv) {
|
||||
// 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() };
|
||||
case 'ed25519': {
|
||||
const privateKey = await random.getRandomBytes(32);
|
||||
const keyPair = nacl.sign.keyPair.fromSeed(privateKey);
|
||||
const publicKey = util.concatUint8Array([new Uint8Array([0x40]), keyPair.publicKey]);
|
||||
return { publicKey, privateKey };
|
||||
}
|
||||
}
|
||||
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) {
|
||||
|
@ -219,8 +214,8 @@ async function generate(curve) {
|
|||
const keyPair = await curve.genKeyPair();
|
||||
return {
|
||||
oid: curve.oid,
|
||||
Q: new BN(keyPair.getPublic()),
|
||||
d: new BN(keyPair.getPrivate()),
|
||||
Q: new BN(keyPair.publicKey),
|
||||
d: new BN(keyPair.privateKey),
|
||||
hash: curve.hash,
|
||||
cipher: curve.cipher
|
||||
};
|
||||
|
@ -233,7 +228,7 @@ function getPreferredHashAlgo(oid) {
|
|||
export default Curve;
|
||||
|
||||
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);
|
||||
|
||||
return {
|
||||
pub: {
|
||||
x: util.b64_to_Uint8Array(publicKey.x, true),
|
||||
y: util.b64_to_Uint8Array(publicKey.y, true)
|
||||
},
|
||||
priv: util.b64_to_Uint8Array(privateKey.d, true)
|
||||
publicKey: jwkToRawPublic(publicKey),
|
||||
privateKey: util.b64_to_Uint8Array(privateKey.d, true)
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -263,9 +255,69 @@ async function nodeGenKeyPair(name) {
|
|||
// Note: ECDSA and ECDH key generation is structurally equivalent
|
||||
const ecdh = nodeCrypto.createECDH(nodeCurves[name]);
|
||||
await ecdh.generateKeys();
|
||||
|
||||
return {
|
||||
pub: ecdh.getPublicKey().toJSON().data,
|
||||
priv: ecdh.getPrivateKey().toJSON().data
|
||||
publicKey: new Uint8Array(ecdh.getPublicKey()),
|
||||
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/aes_kw
|
||||
* @requires crypto/cipher
|
||||
* @requires crypto/random
|
||||
* @requires crypto/hash
|
||||
* @requires type/kdf_params
|
||||
* @requires enums
|
||||
|
@ -31,13 +32,15 @@
|
|||
|
||||
import BN from 'bn.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 cipher from '../../cipher';
|
||||
import random from '../../random';
|
||||
import hash from '../../hash';
|
||||
import type_kdf_params from '../../../type/kdf_params';
|
||||
import enums from '../../../enums';
|
||||
import util from '../../../util';
|
||||
import { keyFromPublic, keyFromPrivate, getIndutnyCurve } from './indutnyKey';
|
||||
|
||||
const webCrypto = util.getWebCrypto();
|
||||
const nodeCrypto = util.getNodeCrypto();
|
||||
|
@ -87,17 +90,15 @@ async function kdf(hash_algo, X, length, param, stripLeading = false, stripTrail
|
|||
* @async
|
||||
*/
|
||||
async function genPublicEphemeralKey(curve, Q) {
|
||||
switch (curve.name) {
|
||||
switch (curve.type) {
|
||||
case 'curve25519': {
|
||||
const { secretKey: d } = nacl.box.keyPair();
|
||||
const d = await random.getRandomBytes(32);
|
||||
const { secretKey, sharedKey } = await genPrivateEphemeralKey(curve, Q, null, d);
|
||||
let { publicKey } = nacl.box.keyPair.fromSecretKey(secretKey);
|
||||
publicKey = util.concatUint8Array([new Uint8Array([0x40]), publicKey]);
|
||||
return { publicKey, sharedKey }; // Note: sharedKey is little-endian here, unlike below
|
||||
}
|
||||
case 'p256':
|
||||
case 'p384':
|
||||
case 'p521': {
|
||||
case 'web':
|
||||
if (curve.web && util.getWebCrypto()) {
|
||||
try {
|
||||
return await webPublicEphemeralKey(curve, Q);
|
||||
|
@ -105,10 +106,9 @@ async function genPublicEphemeralKey(curve, Q) {
|
|||
util.print_debug_error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (curve.node && nodeCrypto) {
|
||||
return nodePublicEphemeralKey(curve, Q);
|
||||
break;
|
||||
case 'node':
|
||||
return nodePublicEphemeralKey(curve, Q);
|
||||
}
|
||||
return ellipticPublicEphemeralKey(curve, Q);
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ async function encrypt(oid, cipher_algo, hash_algo, m, Q, fingerprint) {
|
|||
* @async
|
||||
*/
|
||||
async function genPrivateEphemeralKey(curve, V, Q, d) {
|
||||
switch (curve.name) {
|
||||
switch (curve.type) {
|
||||
case 'curve25519': {
|
||||
const one = new BN(1);
|
||||
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));
|
||||
return { secretKey, sharedKey }; // Note: sharedKey is little-endian here, unlike below
|
||||
}
|
||||
case 'p256':
|
||||
case 'p384':
|
||||
case 'p521': {
|
||||
case 'web':
|
||||
if (curve.web && util.getWebCrypto()) {
|
||||
try {
|
||||
return await webPrivateEphemeralKey(curve, V, Q, d);
|
||||
|
@ -167,10 +165,9 @@ async function genPrivateEphemeralKey(curve, V, Q, d) {
|
|||
util.print_debug_error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (curve.node && nodeCrypto) {
|
||||
return nodePrivateEphemeralKey(curve, V, d);
|
||||
break;
|
||||
case 'node':
|
||||
return nodePrivateEphemeralKey(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 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(
|
||||
"jwk",
|
||||
recipient,
|
||||
|
@ -318,11 +315,12 @@ async function webPublicEphemeralKey(curve, Q) {
|
|||
* @async
|
||||
*/
|
||||
async function ellipticPrivateEphemeralKey(curve, V, d) {
|
||||
V = curve.keyFromPublic(V);
|
||||
d = curve.keyFromPrivate(d);
|
||||
const indutnyCurve = await getIndutnyCurve(curve.name);
|
||||
V = keyFromPublic(indutnyCurve, V);
|
||||
d = keyFromPrivate(indutnyCurve, d);
|
||||
const secretKey = new Uint8Array(d.getPrivate());
|
||||
const S = d.derive(V);
|
||||
const len = curve.curve.curve.p.byteLength();
|
||||
const S = d.derive(V.getPublic());
|
||||
const len = indutnyCurve.curve.p.byteLength();
|
||||
const sharedKey = S.toArrayLike(Uint8Array, 'be', len);
|
||||
return { secretKey, sharedKey };
|
||||
}
|
||||
|
@ -336,11 +334,13 @@ async function ellipticPrivateEphemeralKey(curve, V, d) {
|
|||
* @async
|
||||
*/
|
||||
async function ellipticPublicEphemeralKey(curve, Q) {
|
||||
const indutnyCurve = await getIndutnyCurve(curve.name);
|
||||
const v = await curve.genKeyPair();
|
||||
Q = curve.keyFromPublic(Q);
|
||||
const publicKey = new Uint8Array(v.getPublic());
|
||||
const S = v.derive(Q);
|
||||
const len = curve.curve.curve.p.byteLength();
|
||||
Q = keyFromPublic(indutnyCurve, Q);
|
||||
const V = keyFromPrivate(indutnyCurve, v.privateKey);
|
||||
const publicKey = v.publicKey;
|
||||
const S = V.derive(Q.getPublic());
|
||||
const len = indutnyCurve.curve.p.byteLength();
|
||||
const sharedKey = S.toArrayLike(Uint8Array, 'be', len);
|
||||
return { publicKey, sharedKey };
|
||||
}
|
||||
|
@ -378,52 +378,4 @@ async function nodePublicEphemeralKey(curve, Q) {
|
|||
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 };
|
||||
|
|
|
@ -17,31 +17,62 @@
|
|||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
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
|
||||
* @param {module:type/oid} oid Elliptic curve object identifier
|
||||
* @param {module:enums.hash} hash_algo Hash algorithm used to sign
|
||||
* @param {Uint8Array} m Message to sign
|
||||
* @param {Uint8Array} d Private key used to sign the message
|
||||
* @param {Uint8Array} hashed The hashed message
|
||||
* @param {module:type/oid} oid Elliptic curve object identifier
|
||||
* @param {module:enums.hash} hash_algo Hash algorithm used to sign
|
||||
* @param {Uint8Array} message Message to sign
|
||||
* @param {Uint8Array} publicKey Public key
|
||||
* @param {Uint8Array} privateKey Private key used to sign the message
|
||||
* @param {Uint8Array} hashed The hashed message
|
||||
* @returns {{r: Uint8Array,
|
||||
* s: Uint8Array}} Signature of the message
|
||||
* s: Uint8Array}} Signature of the message
|
||||
* @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 key = curve.keyFromPrivate(d);
|
||||
const signature = await key.sign(m, hash_algo, hashed);
|
||||
return {
|
||||
r: signature.r.toArrayLike(Uint8Array),
|
||||
s: signature.s.toArrayLike(Uint8Array)
|
||||
};
|
||||
if (message && !message.locked) {
|
||||
message = await stream.readToEnd(message);
|
||||
const keyPair = { publicKey, privateKey };
|
||||
switch (curve.type) {
|
||||
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 {{r: Uint8Array,
|
||||
s: Uint8Array}} signature Signature to verify
|
||||
* @param {Uint8Array} m Message to verify
|
||||
* @param {Uint8Array} Q Public key used to verify the message
|
||||
* @param {Uint8Array} message Message to verify
|
||||
* @param {Uint8Array} publicKey Public key used to verify the message
|
||||
* @param {Uint8Array} hashed The hashed message
|
||||
* @returns {Boolean}
|
||||
* @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 key = curve.keyFromPublic(Q);
|
||||
return key.verify(m, signature, hash_algo, hashed);
|
||||
if (message && !message.locked) {
|
||||
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: {
|
||||
const oid = key_params[0];
|
||||
const Q = key_params[1].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([
|
||||
util.Uint8Array_to_MPI(signature.r),
|
||||
util.Uint8Array_to_MPI(signature.s)
|
||||
|
|
|
@ -153,3 +153,9 @@ export { default as HKP } from './hkp';
|
|||
* @name module:openpgp.WKD
|
||||
*/
|
||||
export { default as WKD } from './wkd';
|
||||
|
||||
/**
|
||||
* @see module:lightweight
|
||||
*/
|
||||
import * as lightweightMod from './lightweight_helper';
|
||||
export const lightweight = lightweightMod;
|
||||
|
|
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) {
|
||||
throw new Error('Can only verify message with one literal data packet.');
|
||||
}
|
||||
if (!streaming) {
|
||||
msg.packets.concat(await stream.readToEnd(msg.packets.stream, _ => _));
|
||||
}
|
||||
const onePassSigList = msg.packets.filterByTag(enums.packet.onePassSignature).reverse();
|
||||
const signatureList = msg.packets.filterByTag(enums.packet.signature);
|
||||
if (onePassSigList.length && !signatureList.length && msg.packets.stream) {
|
||||
if (streaming && onePassSigList.length && !signatureList.length && msg.packets.stream) {
|
||||
await Promise.all(onePassSigList.map(async onePassSig => {
|
||||
onePassSig.correspondingSig = new Promise((resolve, reject) => {
|
||||
onePassSig.correspondingSigResolve = resolve;
|
||||
|
@ -602,9 +605,9 @@ Message.prototype.verify = async function(keys, date = new Date(), streaming) {
|
|||
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
|
||||
* @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 signingKey = null;
|
||||
await Promise.all(keys.map(async function(key) {
|
||||
|
@ -656,7 +659,7 @@ async function createVerificationObject(signature, literalDataList, keys, date =
|
|||
if (!signingKey) {
|
||||
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;
|
||||
if (sig.isExpired(date) || !(
|
||||
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
|
||||
* @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 ['text', 'binary'].includes(enums.read(enums.signature, signature.signatureType));
|
||||
}).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|
|
||||
* module:packet.SecretSubkey|module:packet.SecretKey} key the public key to verify the signature
|
||||
* @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.
|
||||
* @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 hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm);
|
||||
|
||||
|
@ -703,10 +703,10 @@ Signature.prototype.verify = async function (key, signatureType, data, detached
|
|||
hash = this.hashed;
|
||||
} else {
|
||||
toHash = this.toHash(signatureType, data, detached);
|
||||
if (!streaming) toHash = await stream.readToEnd(toHash);
|
||||
hash = await this.hash(signatureType, data, toHash);
|
||||
}
|
||||
hash = await stream.readToEnd(hash);
|
||||
|
||||
if (this.signedHashValue[0] !== hash[0] ||
|
||||
this.signedHashValue[1] !== hash[1]) {
|
||||
this.verified = false;
|
||||
|
@ -736,7 +736,6 @@ Signature.prototype.verify = async function (key, signatureType, data, detached
|
|||
mpi[j] = new type_mpi();
|
||||
i += mpi[j].read(this.signature.subarray(i, this.signature.length), endian);
|
||||
}
|
||||
|
||||
this.verified = await crypto.signature.verify(
|
||||
publicKeyAlgorithm, hashAlgorithm, mpi, key.params,
|
||||
toHash, hash
|
||||
|
|
|
@ -5,7 +5,7 @@ chai.use(require('chai-as-promised'));
|
|||
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('Elliptic Curve Cryptography', function () {
|
||||
describe('Elliptic Curve Cryptography @lightweight', function () {
|
||||
const elliptic_curves = openpgp.crypto.publicKey.elliptic;
|
||||
const key_data = {
|
||||
p256: {
|
||||
|
@ -152,7 +152,11 @@ describe('Elliptic Curve Cryptography', function () {
|
|||
done();
|
||||
});
|
||||
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) {
|
||||
const curve = new elliptic_curves.Curve(name);
|
||||
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) {
|
||||
const curve = new elliptic_curves.Curve('p256');
|
||||
const key = curve.keyFromPublic(signature_data.pub);
|
||||
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);
|
||||
});
|
||||
it('Invalid signature', function (done) {
|
||||
const curve = new elliptic_curves.Curve('p256');
|
||||
const key = curve.keyFromPublic(key_data.p256.pub);
|
||||
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);
|
||||
});
|
||||
it('Signature generation', function () {
|
||||
const curve = new elliptic_curves.Curve('p256');
|
||||
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);
|
||||
return elliptic_curves.ecdsa.sign('p256', 8, signature_data.message, key_data.p256.pub, key_data.p256.priv, signature_data.hashed).then(async signature => {
|
||||
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;
|
||||
});
|
||||
});
|
||||
it('Shared secret generation', function (done) {
|
||||
it('Shared secret generation', async function () {
|
||||
const curve = new elliptic_curves.Curve('p256');
|
||||
let key1 = curve.keyFromPrivate(key_data.p256.priv);
|
||||
let key2 = curve.keyFromPublic(signature_data.pub);
|
||||
const shared1 = openpgp.util.Uint8Array_to_hex(key1.derive(key2).toArrayLike(Uint8Array));
|
||||
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();
|
||||
const { sharedKey: shared1 } = await elliptic_curves.ecdh.genPrivateEphemeralKey(curve, signature_data.pub, key_data.p256.pub, key_data.p256.priv);
|
||||
const { sharedKey: shared2 } = await elliptic_curves.ecdh.genPrivateEphemeralKey(curve, key_data.p256.pub, signature_data.pub, signature_data.priv);
|
||||
expect(shared1).to.deep.equal(shared2);
|
||||
});
|
||||
});
|
||||
describe('ECDSA signature', function () {
|
||||
|
@ -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)
|
||||
);
|
||||
};
|
||||
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([
|
||||
0x10, 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/)
|
||||
]);
|
||||
});
|
||||
it('Invalid public key', function () {
|
||||
return Promise.all([
|
||||
expect(verify_signature(
|
||||
it('Invalid public key', async function () {
|
||||
if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) {
|
||||
this.skip();
|
||||
}
|
||||
if (openpgp.util.getNodeCrypto()) {
|
||||
await expect(verify_signature(
|
||||
'secp256k1', 8, [], [], [], []
|
||||
)).to.be.rejectedWith(Error, /Unknown point format/),
|
||||
expect(verify_signature(
|
||||
)).to.eventually.be.false;
|
||||
await expect(verify_signature(
|
||||
'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) {
|
||||
expect(verify_signature(
|
||||
'secp256k1', 8, [], [], [], secp256k1_invalid_point
|
||||
)).to.be.rejectedWith(Error, /Invalid elliptic public key/).notify(done);
|
||||
it('Invalid point', function () {
|
||||
if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) {
|
||||
this.skip();
|
||||
}
|
||||
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) {
|
||||
if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) {
|
||||
this.skip();
|
||||
}
|
||||
expect(verify_signature(
|
||||
'secp256k1', 8, [], [], [], secp256k1_point
|
||||
)).to.eventually.be.false.notify(done);
|
||||
|
@ -312,11 +327,11 @@ describe('Elliptic Curve Cryptography', function () {
|
|||
it('Sign and verify message', function () {
|
||||
const curve = new elliptic_curves.Curve('p521');
|
||||
return curve.genKeyPair().then(async keyPair => {
|
||||
const keyPublic = new Uint8Array(keyPair.getPublic());
|
||||
const keyPrivate = new Uint8Array(keyPair.getPrivate());
|
||||
const keyPublic = new Uint8Array(keyPair.publicKey);
|
||||
const keyPrivate = new Uint8Array(keyPair.privateKey);
|
||||
const oid = curve.oid;
|
||||
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)))
|
||||
.to.eventually.be.true;
|
||||
});
|
||||
|
@ -381,16 +396,25 @@ describe('Elliptic Curve Cryptography', function () {
|
|||
)).to.be.rejectedWith(Error, /Not valid curve/).notify(done);
|
||||
});
|
||||
it('Invalid ephemeral key', function (done) {
|
||||
if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) {
|
||||
this.skip();
|
||||
}
|
||||
expect(decrypt_message(
|
||||
'secp256k1', 2, 7, [], [], [], [], []
|
||||
)).to.be.rejectedWith(Error, /Private key is not valid for specified curve|Unknown point format/).notify(done);
|
||||
});
|
||||
it('Invalid elliptic public key', function (done) {
|
||||
if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) {
|
||||
this.skip();
|
||||
}
|
||||
expect(decrypt_message(
|
||||
'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);
|
||||
});
|
||||
it('Invalid key data integrity', function (done) {
|
||||
if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) {
|
||||
this.skip();
|
||||
}
|
||||
expect(decrypt_message(
|
||||
'secp256k1', 2, 7, secp256k1_value, secp256k1_point, secp256k1_point, secp256k1_data, []
|
||||
)).to.be.rejectedWith(Error, /Key Data Integrity failed/).notify(done);
|
||||
|
@ -497,6 +521,9 @@ describe('Elliptic Curve Cryptography', function () {
|
|||
|
||||
describe('ECDHE key generation', function () {
|
||||
it('Invalid curve', function (done) {
|
||||
if (!openpgp.config.use_indutny_elliptic && !openpgp.util.getNodeCrypto()) {
|
||||
this.skip();
|
||||
}
|
||||
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);
|
||||
});
|
||||
|
@ -539,7 +566,7 @@ describe('Elliptic Curve Cryptography', function () {
|
|||
|
||||
it('Comparing keys derived using webCrypto and elliptic', async function () {
|
||||
const names = ["p256", "p384", "p521"];
|
||||
if (!openpgp.util.getWebCrypto()) {
|
||||
if (!openpgp.util.getWebCrypto() || !openpgp.config.use_indutny_elliptic) {
|
||||
this.skip();
|
||||
}
|
||||
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 () {
|
||||
const names = ["p256", "p384", "p521"];
|
||||
if (!openpgp.util.getNodeCrypto()) {
|
||||
if (!openpgp.util.getNodeCrypto() || !openpgp.config.use_indutny_elliptic) {
|
||||
this.skip();
|
||||
}
|
||||
return Promise.all(names.map(async function (name) {
|
||||
|
|
|
@ -8,7 +8,13 @@ const input = require('./testInputs.js');
|
|||
|
||||
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 = {
|
||||
romeo: {
|
||||
id: 'fa3d64c9bcf338bc',
|
||||
|
@ -222,7 +228,7 @@ EJ4QcD/oQ6x1M/8X/iKQCtxZP8RnlrbH7ExkNON5s5g=
|
|||
const juliet = await load_pub_key('juliet');
|
||||
const romeo = await load_priv_key('romeo');
|
||||
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.data).to.equal(data.romeo.message);
|
||||
|
@ -241,6 +247,8 @@ EJ4QcD/oQ6x1M/8X/iKQCtxZP8RnlrbH7ExkNON5s5g=
|
|||
expect(result.signatures[0].valid).to.be.true;
|
||||
});
|
||||
it('Decrypt and verify message with leading zero in hash signed with old elliptic algorithm', async function () {
|
||||
//this test would not work with nodeCrypto, since message is signed with leading zero stripped from the hash
|
||||
openpgp.config.use_native = false;
|
||||
const juliet = await load_priv_key('juliet');
|
||||
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);
|
||||
|
@ -331,12 +339,12 @@ function omnibus() {
|
|||
});
|
||||
}
|
||||
|
||||
tryTests('Brainpool Omnibus Tests', omnibus, {
|
||||
if: !openpgp.config.ci
|
||||
tryTests('Brainpool Omnibus Tests @lightweight', omnibus, {
|
||||
if: !openpgp.config.ci && (openpgp.config.use_indutny_elliptic || openpgp.util.getNodeCrypto())
|
||||
});
|
||||
|
||||
tryTests('Brainpool Omnibus Tests - Worker', omnibus, {
|
||||
if: typeof window !== 'undefined' && window.Worker,
|
||||
tryTests('Brainpool Omnibus Tests - Worker @lightweight', omnibus, {
|
||||
if: typeof window !== 'undefined' && window.Worker && (openpgp.config.use_indutny_elliptic || openpgp.util.getNodeCrypto()),
|
||||
before: async function() {
|
||||
await openpgp.initWorker({ path: '../dist/openpgp.worker.js' });
|
||||
},
|
||||
|
|
|
@ -8,234 +8,7 @@ const input = require('./testInputs.js');
|
|||
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('Elliptic Curve Cryptography', 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;
|
||||
});
|
||||
});
|
||||
|
||||
describe('Elliptic Curve Cryptography for NIST P-256,P-384,P-521 curves @lightweight', function () {
|
||||
function omnibus() {
|
||||
it('Omnibus NIST P-256 Test', function () {
|
||||
const options = { userIds: {name: "Hi", email: "hi@hel.lo"}, curve: "p256" };
|
||||
|
@ -295,6 +68,36 @@ describe('Elliptic Curve Cryptography', function () {
|
|||
|
||||
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, {
|
||||
if: typeof window !== 'undefined' && window.Worker,
|
||||
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('./oid.js');
|
||||
require('./ecc_nist.js');
|
||||
require('./ecc_secp256k1.js');
|
||||
require('./x25519.js');
|
||||
require('./brainpool.js');
|
||||
require('./decompression.js');
|
||||
|
|
|
@ -827,10 +827,7 @@ hkJiXopCSWKSlQInL1devkJJUWJmTmZeugJYlpdLAagQJM0JpsCqIQZwKgAA
|
|||
return openpgp.verify({ publicKeys: [pubKey], message: sMsg }).then(async function(cleartextSig) {
|
||||
expect(cleartextSig).to.exist;
|
||||
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[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);
|
||||
expect(cleartextSig.signatures).to.have.length(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ describe('Unit Tests', function () {
|
|||
|
||||
if (typeof window !== 'undefined') {
|
||||
openpgp.config.s2k_iteration_count_byte = 0;
|
||||
openpgp.config.indutny_elliptic_path = '../dist/elliptic.min.js';
|
||||
|
||||
afterEach(function () {
|
||||
if (window.scrollY >= document.body.scrollHeight - window.innerHeight - 100
|
||||
|
|
|
@ -9,14 +9,14 @@ if [ $OPENPGPJSTEST = "coverage" ]; then
|
|||
|
||||
elif [ $OPENPGPJSTEST = "unit" ]; then
|
||||
echo "Running OpenPGP.js unit tests on node.js."
|
||||
npm test
|
||||
grunt build test --lightweight=$LIGHTWEIGHT
|
||||
|
||||
elif [ $OPENPGPJSTEST = "browserstack" ]; then
|
||||
echo "Running OpenPGP.js browser unit tests on Browserstack."
|
||||
|
||||
grunt build browserify:unittests copy:browsertest --compat=$COMPAT
|
||||
echo -n "Using config: "
|
||||
echo "{\"browsers\": [$BROWSER], \"test_framework\": \"mocha\", \"test_path\": [\"test/unittests.html?ci=true\"], \"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
|
||||
|
||||
result=0
|
||||
|
|
Loading…
Reference in New Issue
Block a user