Bugfix in Native ECC in Node
This commit is contained in:
parent
5fac00eddb
commit
5e857e131e
10
Gruntfile.js
10
Gruntfile.js
|
@ -49,11 +49,12 @@ module.exports = function(grunt) {
|
||||||
standalone: 'openpgp'
|
standalone: 'openpgp'
|
||||||
},
|
},
|
||||||
// Don't bundle these packages with openpgp.js
|
// Don't bundle these packages with openpgp.js
|
||||||
external: ['crypto', 'zlib', 'node-localstorage', 'node-fetch', 'asn1.js', 'jwk-to-pem'],
|
external: ['crypto', 'zlib', 'node-localstorage', 'node-fetch', 'asn1.js'],
|
||||||
transform: [
|
transform: [
|
||||||
["babelify", {
|
["babelify", {
|
||||||
global: true,
|
global: true,
|
||||||
only: /^(?:.*\/node_modules\/asmcrypto\.js\/|(?!.*\/node_modules\/)).*$/, // Only babelify asmcrypto in node_modules
|
// Only babelify asmcrypto in node_modules
|
||||||
|
only: /^(?:.*\/node_modules\/asmcrypto\.js\/|(?!.*\/node_modules\/)).*$/,
|
||||||
plugins: ["transform-async-to-generator",
|
plugins: ["transform-async-to-generator",
|
||||||
"syntax-async-functions",
|
"syntax-async-functions",
|
||||||
"transform-regenerator",
|
"transform-regenerator",
|
||||||
|
@ -75,11 +76,12 @@ module.exports = function(grunt) {
|
||||||
debug: true,
|
debug: true,
|
||||||
standalone: 'openpgp'
|
standalone: 'openpgp'
|
||||||
},
|
},
|
||||||
external: ['crypto', 'zlib', 'node-localstorage', 'node-fetch', 'asn1.js', 'jwk-to-pem'],
|
external: ['crypto', 'zlib', 'node-localstorage', 'node-fetch', 'asn1.js'],
|
||||||
transform: [
|
transform: [
|
||||||
["babelify", {
|
["babelify", {
|
||||||
global: true,
|
global: true,
|
||||||
only: /^(?:.*\/node_modules\/asmcrypto\.js\/|(?!.*\/node_modules\/)).*$/, // Only babelify asmcrypto in node_modules
|
// Only babelify asmcrypto in node_modules
|
||||||
|
only: /^(?:.*\/node_modules\/asmcrypto\.js\/|(?!.*\/node_modules\/)).*$/,
|
||||||
plugins: ["transform-async-to-generator",
|
plugins: ["transform-async-to-generator",
|
||||||
"syntax-async-functions",
|
"syntax-async-functions",
|
||||||
"transform-regenerator",
|
"transform-regenerator",
|
||||||
|
|
|
@ -79,7 +79,6 @@
|
||||||
"compressjs": "github:openpgpjs/compressjs",
|
"compressjs": "github:openpgpjs/compressjs",
|
||||||
"elliptic": "github:openpgpjs/elliptic",
|
"elliptic": "github:openpgpjs/elliptic",
|
||||||
"hash.js": "^1.1.3",
|
"hash.js": "^1.1.3",
|
||||||
"jwk-to-pem": "^1.2.6",
|
|
||||||
"node-fetch": "^1.7.3",
|
"node-fetch": "^1.7.3",
|
||||||
"node-localstorage": "~1.3.0",
|
"node-localstorage": "~1.3.0",
|
||||||
"pako": "^1.0.6",
|
"pako": "^1.0.6",
|
||||||
|
|
|
@ -56,7 +56,7 @@ if (nodeCrypto) {
|
||||||
|
|
||||||
const curves = {
|
const curves = {
|
||||||
p256: {
|
p256: {
|
||||||
oid: util.Uint8Array_to_str([0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07]),
|
oid: [0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07],
|
||||||
keyType: enums.publicKey.ecdsa,
|
keyType: enums.publicKey.ecdsa,
|
||||||
hash: enums.hash.sha256,
|
hash: enums.hash.sha256,
|
||||||
cipher: enums.symmetric.aes128,
|
cipher: enums.symmetric.aes128,
|
||||||
|
@ -65,7 +65,7 @@ const curves = {
|
||||||
payloadSize: 32
|
payloadSize: 32
|
||||||
},
|
},
|
||||||
p384: {
|
p384: {
|
||||||
oid: util.Uint8Array_to_str([0x2B, 0x81, 0x04, 0x00, 0x22]),
|
oid: [0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x22],
|
||||||
keyType: enums.publicKey.ecdsa,
|
keyType: enums.publicKey.ecdsa,
|
||||||
hash: enums.hash.sha384,
|
hash: enums.hash.sha384,
|
||||||
cipher: enums.symmetric.aes192,
|
cipher: enums.symmetric.aes192,
|
||||||
|
@ -74,7 +74,7 @@ const curves = {
|
||||||
payloadSize: 48
|
payloadSize: 48
|
||||||
},
|
},
|
||||||
p521: {
|
p521: {
|
||||||
oid: util.Uint8Array_to_str([0x2B, 0x81, 0x04, 0x00, 0x23]),
|
oid: [0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x23],
|
||||||
keyType: enums.publicKey.ecdsa,
|
keyType: enums.publicKey.ecdsa,
|
||||||
hash: enums.hash.sha512,
|
hash: enums.hash.sha512,
|
||||||
cipher: enums.symmetric.aes256,
|
cipher: enums.symmetric.aes256,
|
||||||
|
@ -83,80 +83,83 @@ const curves = {
|
||||||
payloadSize: 66
|
payloadSize: 66
|
||||||
},
|
},
|
||||||
secp256k1: {
|
secp256k1: {
|
||||||
oid: util.Uint8Array_to_str([0x2B, 0x81, 0x04, 0x00, 0x0A]),
|
oid: [0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x0A],
|
||||||
keyType: enums.publicKey.ecdsa,
|
keyType: enums.publicKey.ecdsa,
|
||||||
hash: enums.hash.sha256,
|
hash: enums.hash.sha256,
|
||||||
cipher: enums.symmetric.aes128,
|
cipher: enums.symmetric.aes128,
|
||||||
node: false // FIXME when we replace jwk-to-pem or it supports this curve
|
node: nodeCurves.secp256k1
|
||||||
},
|
},
|
||||||
ed25519: {
|
ed25519: {
|
||||||
oid: util.Uint8Array_to_str([0x2B, 0x06, 0x01, 0x04, 0x01, 0xDA, 0x47, 0x0F, 0x01]),
|
oid: [0x06, 0x09, 0x2B, 0x06, 0x01, 0x04, 0x01, 0xDA, 0x47, 0x0F, 0x01],
|
||||||
keyType: enums.publicKey.eddsa,
|
keyType: enums.publicKey.eddsa,
|
||||||
hash: enums.hash.sha512,
|
hash: enums.hash.sha512,
|
||||||
payloadSize: 32
|
payloadSize: 32
|
||||||
},
|
},
|
||||||
curve25519: {
|
curve25519: {
|
||||||
oid: util.Uint8Array_to_str([0x2B, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01]),
|
oid: [0x06, 0x08, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01],
|
||||||
keyType: enums.publicKey.ecdsa,
|
keyType: enums.publicKey.ecdsa,
|
||||||
hash: enums.hash.sha256,
|
hash: enums.hash.sha256,
|
||||||
cipher: enums.symmetric.aes128
|
cipher: enums.symmetric.aes128
|
||||||
},
|
},
|
||||||
brainpoolP256r1: { // TODO 1.3.36.3.3.2.8.1.1.7
|
brainpoolP256r1: { // TODO 1.3.36.3.3.2.8.1.1.7
|
||||||
oid: util.Uint8Array_to_str([0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07])
|
oid: [0x06, 0x07, 0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07]
|
||||||
},
|
},
|
||||||
brainpoolP384r1: { // TODO 1.3.36.3.3.2.8.1.1.11
|
brainpoolP384r1: { // TODO 1.3.36.3.3.2.8.1.1.11
|
||||||
oid: util.Uint8Array_to_str([0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B])
|
oid: [0x06, 0x07, 0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B]
|
||||||
},
|
},
|
||||||
brainpoolP512r1: { // TODO 1.3.36.3.3.2.8.1.1.13
|
brainpoolP512r1: { // TODO 1.3.36.3.3.2.8.1.1.13
|
||||||
oid: util.Uint8Array_to_str([0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D])
|
oid: [0x06, 0x07, 0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Curve(oid_or_name, params) {
|
export default function Curve(oid_or_name, params) {
|
||||||
if (OID.prototype.isPrototypeOf(oid_or_name) &&
|
try {
|
||||||
enums.curve[oid_or_name.toHex()]) {
|
if (util.isArray(oid_or_name) ||
|
||||||
this.name = oid_or_name.toHex(); // by curve OID
|
util.isUint8Array(oid_or_name)) {
|
||||||
} else if (enums.curve[oid_or_name]) {
|
// by oid byte array
|
||||||
this.name = oid_or_name; // by curve name
|
oid_or_name = new OID(oid_or_name);
|
||||||
} else if (enums.curve[util.str_to_hex(oid_or_name)]) {
|
}
|
||||||
this.name = util.str_to_hex(oid_or_name); // by oid string
|
if (oid_or_name instanceof OID) {
|
||||||
} else {
|
// by curve OID
|
||||||
|
oid_or_name = oid_or_name.getName();
|
||||||
|
}
|
||||||
|
// by curve name or oid string
|
||||||
|
this.name = enums.write(enums.curve, oid_or_name);
|
||||||
|
} catch (err) {
|
||||||
throw new Error('Not valid curve');
|
throw new Error('Not valid curve');
|
||||||
}
|
}
|
||||||
this.name = enums.write(enums.curve, this.name);
|
|
||||||
this.oid = new OID(curves[this.name].oid);
|
|
||||||
|
|
||||||
params = params || curves[this.name];
|
params = params || curves[this.name];
|
||||||
|
|
||||||
this.keyType = params.keyType;
|
this.keyType = params.keyType;
|
||||||
switch (this.keyType) {
|
switch (this.keyType) {
|
||||||
case enums.publicKey.eddsa:
|
|
||||||
this.curve = new EdDSA(this.name);
|
|
||||||
break;
|
|
||||||
case enums.publicKey.ecdsa:
|
case enums.publicKey.ecdsa:
|
||||||
this.curve = new EC(this.name);
|
this.curve = new EC(this.name);
|
||||||
break;
|
break;
|
||||||
|
case enums.publicKey.eddsa:
|
||||||
|
this.curve = new EdDSA(this.name);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error('Unknown elliptic key type;');
|
throw new Error('Unknown elliptic key type;');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.oid = params.oid;
|
||||||
this.hash = params.hash;
|
this.hash = params.hash;
|
||||||
this.cipher = params.cipher;
|
this.cipher = params.cipher;
|
||||||
this.node = params.node && curves[this.name].node;
|
this.node = params.node && curves[this.name];
|
||||||
this.web = params.web && curves[this.name].web;
|
this.web = params.web && curves[this.name];
|
||||||
this.payloadSize = curves[this.name].payloadSize;
|
this.payloadSize = params.payloadSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
Curve.prototype.keyFromPrivate = function (priv) { // Not for ed25519
|
Curve.prototype.keyFromPrivate = function (priv) { // Not for ed25519
|
||||||
return new KeyPair(this.curve, { priv: priv });
|
return new KeyPair(this, { priv: priv });
|
||||||
};
|
};
|
||||||
|
|
||||||
Curve.prototype.keyFromSecret = function (secret) { // Only for ed25519
|
Curve.prototype.keyFromSecret = function (secret) { // Only for ed25519
|
||||||
return new KeyPair(this.curve, { secret: secret });
|
return new KeyPair(this, { secret: secret });
|
||||||
};
|
};
|
||||||
|
|
||||||
Curve.prototype.keyFromPublic = function (pub) {
|
Curve.prototype.keyFromPublic = function (pub) {
|
||||||
return new KeyPair(this.curve, { pub: pub });
|
return new KeyPair(this, { pub: pub });
|
||||||
};
|
};
|
||||||
|
|
||||||
Curve.prototype.genKeyPair = async function () {
|
Curve.prototype.genKeyPair = async function () {
|
||||||
|
@ -174,7 +177,9 @@ Curve.prototype.genKeyPair = async function () {
|
||||||
|
|
||||||
if (!keyPair || !keyPair.priv) {
|
if (!keyPair || !keyPair.priv) {
|
||||||
// elliptic fallback
|
// elliptic fallback
|
||||||
const r = await this.curve.genKeyPair({ entropy: util.Uint8Array_to_str(random.getRandomBytes(32)) });
|
const r = await this.curve.genKeyPair({
|
||||||
|
entropy: util.Uint8Array_to_str(random.getRandomBytes(32))
|
||||||
|
});
|
||||||
const compact = this.curve.curve.type === 'edwards' || this.curve.curve.type === 'mont';
|
const compact = this.curve.curve.type === 'edwards' || this.curve.curve.type === 'mont';
|
||||||
if (this.keyType === enums.publicKey.eddsa) {
|
if (this.keyType === enums.publicKey.eddsa) {
|
||||||
keyPair = { secret: r.getSecret() };
|
keyPair = { secret: r.getSecret() };
|
||||||
|
@ -182,14 +187,14 @@ Curve.prototype.genKeyPair = async function () {
|
||||||
keyPair = { pub: r.getPublic('array', compact), priv: r.getPrivate().toArray() };
|
keyPair = { pub: r.getPublic('array', compact), priv: r.getPrivate().toArray() };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new KeyPair(this.curve, keyPair);
|
return new KeyPair(this, keyPair);
|
||||||
};
|
};
|
||||||
|
|
||||||
async function generate(curve) {
|
async function generate(curve) {
|
||||||
curve = new Curve(curve);
|
curve = new Curve(curve);
|
||||||
const keyPair = await curve.genKeyPair();
|
const keyPair = await curve.genKeyPair();
|
||||||
return {
|
return {
|
||||||
oid: curve.oid,
|
oid: new OID(curve.oid.slice(2)),
|
||||||
Q: new BN(keyPair.getPublic()),
|
Q: new BN(keyPair.getPublic()),
|
||||||
d: new BN(keyPair.getPrivate()),
|
d: new BN(keyPair.getPrivate()),
|
||||||
hash: curve.hash,
|
hash: curve.hash,
|
||||||
|
@ -230,6 +235,7 @@ async function webGenKeyPair(name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function nodeGenKeyPair(name) {
|
async function nodeGenKeyPair(name) {
|
||||||
|
// Note: ECDSA and ECDH key generation is structurally equivalent
|
||||||
const ecdh = nodeCrypto.createECDH(nodeCurves[name]);
|
const ecdh = nodeCrypto.createECDH(nodeCurves[name]);
|
||||||
await ecdh.generateKeys();
|
await ecdh.generateKeys();
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import BN from 'bn.js';
|
import BN from 'bn.js';
|
||||||
import { webCurves, nodeCurves } from './curves';
|
import { curves, webCurves, nodeCurves } from './curves';
|
||||||
import hash from '../../hash';
|
import hash from '../../hash';
|
||||||
import util from '../../../util';
|
import util from '../../../util';
|
||||||
import enums from '../../../enums';
|
import enums from '../../../enums';
|
||||||
|
@ -37,19 +37,10 @@ import enums from '../../../enums';
|
||||||
const webCrypto = util.getWebCrypto();
|
const webCrypto = util.getWebCrypto();
|
||||||
const nodeCrypto = util.getNodeCrypto();
|
const nodeCrypto = util.getNodeCrypto();
|
||||||
|
|
||||||
const jwkToPem = nodeCrypto ? require('jwk-to-pem') : undefined;
|
|
||||||
const ECDSASignature = nodeCrypto ?
|
|
||||||
require('asn1.js').define('ECDSASignature', function() {
|
|
||||||
this.seq().obj(
|
|
||||||
this.key('r').int(),
|
|
||||||
this.key('s').int()
|
|
||||||
);
|
|
||||||
}) : undefined;
|
|
||||||
|
|
||||||
export default function KeyPair(curve, options) {
|
export default function KeyPair(curve, options) {
|
||||||
this.curve = curve;
|
this.curve = curve;
|
||||||
this.keyType = curve.curve.type === 'edwards' ? enums.publicKey.eddsa : enums.publicKey.ecdsa;
|
this.keyType = curve.curve.type === 'edwards' ? enums.publicKey.eddsa : enums.publicKey.ecdsa;
|
||||||
this.keyPair = this.curve.keyPair(options);
|
this.keyPair = this.curve.curve.keyPair(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyPair.prototype.sign = async function (message, hash_algo) {
|
KeyPair.prototype.sign = async function (message, hash_algo) {
|
||||||
|
@ -90,12 +81,13 @@ KeyPair.prototype.derive = function (pub) {
|
||||||
};
|
};
|
||||||
|
|
||||||
KeyPair.prototype.getPublic = function () {
|
KeyPair.prototype.getPublic = function () {
|
||||||
const compact = (this.curve.curve.type === 'edwards' || this.curve.curve.type === 'mont');
|
const compact = this.curve.curve.curve.type === 'edwards' ||
|
||||||
|
this.curve.curve.curve.type === 'mont';
|
||||||
return this.keyPair.getPublic('array', compact);
|
return this.keyPair.getPublic('array', compact);
|
||||||
};
|
};
|
||||||
|
|
||||||
KeyPair.prototype.getPrivate = function () {
|
KeyPair.prototype.getPrivate = function () {
|
||||||
if (this.keyType === enums.publicKey.eddsa) {
|
if (this.curve.keyType === enums.publicKey.eddsa) {
|
||||||
return this.keyPair.getSecret();
|
return this.keyPair.getSecret();
|
||||||
}
|
}
|
||||||
return this.keyPair.getPrivate().toArray();
|
return this.keyPair.getPrivate().toArray();
|
||||||
|
@ -110,15 +102,15 @@ KeyPair.prototype.getPrivate = function () {
|
||||||
|
|
||||||
|
|
||||||
async function webSign(curve, hash_algo, message, keyPair) {
|
async function webSign(curve, hash_algo, message, keyPair) {
|
||||||
const l = curve.payloadSize;
|
const len = curve.payloadSize;
|
||||||
const key = await webCrypto.importKey(
|
const key = await webCrypto.importKey(
|
||||||
"jwk",
|
"jwk",
|
||||||
{
|
{
|
||||||
"kty": "EC",
|
"kty": "EC",
|
||||||
"crv": webCurves[curve.name],
|
"crv": webCurves[curve.name],
|
||||||
"x": util.Uint8Array_to_b64(new Uint8Array(keyPair.getPublic().getX().toArray('be', l)), true),
|
"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', l)), 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', l)), true),
|
"d": util.Uint8Array_to_b64(new Uint8Array(keyPair.getPrivate().toArray('be', len)), true),
|
||||||
"use": "sig",
|
"use": "sig",
|
||||||
"kid": "ECDSA Private Key"
|
"kid": "ECDSA Private Key"
|
||||||
},
|
},
|
||||||
|
@ -141,23 +133,20 @@ async function webSign(curve, hash_algo, message, keyPair) {
|
||||||
message
|
message
|
||||||
));
|
));
|
||||||
return {
|
return {
|
||||||
r: signature.slice(0, l),
|
r: signature.slice(0, len),
|
||||||
s: signature.slice(l, 2 * l)
|
s: signature.slice(len, len << 1)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function webVerify(curve, hash_algo, { r, s }, message, publicKey) {
|
async function webVerify(curve, hash_algo, { r, s }, message, publicKey) {
|
||||||
const l = curve.payloadSize;
|
const len = curve.payloadSize;
|
||||||
r = Array(l - r.length).fill(0).concat(r);
|
|
||||||
s = Array(l - s.length).fill(0).concat(s);
|
|
||||||
const signature = new Uint8Array(r.concat(s)).buffer;
|
|
||||||
const key = await webCrypto.importKey(
|
const key = await webCrypto.importKey(
|
||||||
"jwk",
|
"jwk",
|
||||||
{
|
{
|
||||||
"kty": "EC",
|
"kty": "EC",
|
||||||
"crv": webCurves[curve.name],
|
"crv": webCurves[curve.name],
|
||||||
"x": util.Uint8Array_to_b64(new Uint8Array(publicKey.getX().toArray('be', l)), true),
|
"x": util.Uint8Array_to_b64(new Uint8Array(publicKey.getX().toArray('be', len)), true),
|
||||||
"y": util.Uint8Array_to_b64(new Uint8Array(publicKey.getY().toArray('be', l)), true),
|
"y": util.Uint8Array_to_b64(new Uint8Array(publicKey.getY().toArray('be', len)), true),
|
||||||
"use": "sig",
|
"use": "sig",
|
||||||
"kid": "ECDSA Public Key"
|
"kid": "ECDSA Public Key"
|
||||||
},
|
},
|
||||||
|
@ -170,6 +159,10 @@ async function webVerify(curve, hash_algo, { r, s }, message, publicKey) {
|
||||||
["verify"]
|
["verify"]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
r = [].concat(Array(len - r.length).fill(0), r);
|
||||||
|
s = [].concat(Array(len - s.length).fill(0), s);
|
||||||
|
const signature = new Uint8Array([].concat(r, s)).buffer;
|
||||||
|
|
||||||
return webCrypto.verify(
|
return webCrypto.verify(
|
||||||
{
|
{
|
||||||
"name": 'ECDSA',
|
"name": 'ECDSA',
|
||||||
|
@ -182,58 +175,88 @@ async function webVerify(curve, hash_algo, { r, s }, message, publicKey) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function nodeSign(curve, hash_algo, message, keyPair) {
|
async function nodeSign(curve, hash_algo, message, keyPair) {
|
||||||
console.log({
|
|
||||||
"kty": "EC",
|
|
||||||
"crv": webCurves[curve.name],
|
|
||||||
"x": util.Uint8Array_to_b64(new Uint8Array(keyPair.getPublic().getX().toArray())),
|
|
||||||
"y": util.Uint8Array_to_b64(new Uint8Array(keyPair.getPublic().getY().toArray())),
|
|
||||||
"d": util.Uint8Array_to_b64(new Uint8Array(keyPair.getPrivate().toArray())),
|
|
||||||
"use": "sig",
|
|
||||||
"kid": "ECDSA Private Key"
|
|
||||||
});
|
|
||||||
|
|
||||||
const key = jwkToPem(
|
|
||||||
{
|
|
||||||
"kty": "EC",
|
|
||||||
"crv": webCurves[curve.name],
|
|
||||||
"x": util.Uint8Array_to_b64(new Uint8Array(keyPair.getPublic().getX().toArray())),
|
|
||||||
"y": util.Uint8Array_to_b64(new Uint8Array(keyPair.getPublic().getY().toArray())),
|
|
||||||
"d": util.Uint8Array_to_b64(new Uint8Array(keyPair.getPrivate().toArray())),
|
|
||||||
"use": "sig",
|
|
||||||
"kid": "ECDSA Private Key"
|
|
||||||
},
|
|
||||||
{ private: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
const sign = nodeCrypto.createSign(enums.read(enums.hash, hash_algo));
|
const sign = nodeCrypto.createSign(enums.read(enums.hash, hash_algo));
|
||||||
sign.write(message);
|
sign.write(message);
|
||||||
sign.end();
|
sign.end();
|
||||||
const signature = await ECDSASignature.decode(sign.sign(key), 'der');
|
|
||||||
return {
|
const key = ECPrivateKey.encode({
|
||||||
r: signature.r.toArray(),
|
version: 1,
|
||||||
s: signature.s.toArray()
|
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) {
|
async function nodeVerify(curve, hash_algo, { r, s }, message, publicKey) {
|
||||||
const signature = ECDSASignature.encode({ r: new BN(r), s: new BN(s) }, 'der');
|
|
||||||
const key = jwkToPem(
|
|
||||||
{
|
|
||||||
"kty": "EC",
|
|
||||||
"crv": webCurves[curve.name],
|
|
||||||
"x": util.Uint8Array_to_b64(new Uint8Array(publicKey.getX().toArray())),
|
|
||||||
"y": util.Uint8Array_to_b64(new Uint8Array(publicKey.getY().toArray())),
|
|
||||||
"use": "sig",
|
|
||||||
"kid": "ECDSA Public Key"
|
|
||||||
},
|
|
||||||
{ private: false }
|
|
||||||
);
|
|
||||||
|
|
||||||
const verify = nodeCrypto.createVerify(enums.read(enums.hash, hash_algo));
|
const verify = nodeCrypto.createVerify(enums.read(enums.hash, hash_algo));
|
||||||
verify.write(message);
|
verify.write(message);
|
||||||
verify.end();
|
verify.end();
|
||||||
const result = await verify.verify(key, signature);
|
|
||||||
return result;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 ECParameters = nodeCrypto ?
|
||||||
|
asn1.define('ECParameters', function() {
|
||||||
|
this.choice({
|
||||||
|
namedCurve: this.objid()
|
||||||
|
});
|
||||||
|
}) : 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;
|
||||||
|
|
|
@ -41,7 +41,9 @@ import util from '../util';
|
||||||
*/
|
*/
|
||||||
export default function MPI(data) {
|
export default function MPI(data) {
|
||||||
/** An implementation dependent integer */
|
/** An implementation dependent integer */
|
||||||
if (BN.isBN(data)) {
|
if (data instanceof MPI) {
|
||||||
|
this.data = data.data;
|
||||||
|
} else if (BN.isBN(data)) {
|
||||||
this.fromBN(data);
|
this.fromBN(data);
|
||||||
} else if (util.isUint8Array(data)) {
|
} else if (util.isUint8Array(data)) {
|
||||||
this.fromUint8Array(data);
|
this.fromUint8Array(data);
|
||||||
|
|
|
@ -18,8 +18,17 @@
|
||||||
/**
|
/**
|
||||||
* Wrapper to an OID value
|
* Wrapper to an OID value
|
||||||
*
|
*
|
||||||
* An object identifier type from
|
* {@link https://tools.ietf.org/html/rfc6637#section-11|RFC6637, section 11}:
|
||||||
* {@link https://tools.ietf.org/html/rfc6637#section-11|RFC6637, section 11}.
|
* The sequence of octets in the third column is the result of applying
|
||||||
|
* the Distinguished Encoding Rules (DER) to the ASN.1 Object Identifier
|
||||||
|
* with subsequent truncation. The truncation removes the two fields of
|
||||||
|
* encoded Object Identifier. The first omitted field is one octet
|
||||||
|
* representing the Object Identifier tag, and the second omitted field
|
||||||
|
* is the length of the Object Identifier body. For example, the
|
||||||
|
* complete ASN.1 DER encoding for the NIST P-256 curve OID is "06 08 2A
|
||||||
|
* 86 48 CE 3D 03 01 07", from which the first entry in the table above
|
||||||
|
* is constructed by omitting the first two octets. Only the truncated
|
||||||
|
* sequence of octets is the valid representation of a curve OID.
|
||||||
* @requires util
|
* @requires util
|
||||||
* @requires enums
|
* @requires enums
|
||||||
* @module type/oid
|
* @module type/oid
|
||||||
|
@ -33,15 +42,16 @@ import enums from '../enums';
|
||||||
*/
|
*/
|
||||||
function OID(oid) {
|
function OID(oid) {
|
||||||
if (oid instanceof OID) {
|
if (oid instanceof OID) {
|
||||||
oid = oid.oid;
|
this.oid = oid.oid;
|
||||||
} else if (typeof oid === 'undefined') {
|
} else if (util.isArray(oid) ||
|
||||||
oid = '';
|
util.isUint8Array(oid)) {
|
||||||
} else if (util.isArray(oid)) {
|
if (oid[0] === 0x06) { // DER encoded oid byte array
|
||||||
oid = util.Uint8Array_to_str(oid);
|
oid = oid.slice(2);
|
||||||
} else if (util.isUint8Array(oid)) {
|
}
|
||||||
oid = util.Uint8Array_to_str(oid);
|
this.oid = util.Uint8Array_to_str(oid);
|
||||||
|
} else {
|
||||||
|
this.oid = '';
|
||||||
}
|
}
|
||||||
this.oid = oid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
10
src/util.js
10
src/util.js
|
@ -18,10 +18,12 @@
|
||||||
/**
|
/**
|
||||||
* This object contains utility functions
|
* This object contains utility functions
|
||||||
* @requires config
|
* @requires config
|
||||||
|
* @requires encoding/base64
|
||||||
* @module util
|
* @module util
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import config from './config';
|
import config from './config';
|
||||||
|
import b64 from './encoding/base64';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
||||||
|
@ -157,8 +159,8 @@ export default {
|
||||||
* @return {Uint8Array} An array of 8-bit integers
|
* @return {Uint8Array} An array of 8-bit integers
|
||||||
*/
|
*/
|
||||||
b64_to_Uint8Array: function (base64) {
|
b64_to_Uint8Array: function (base64) {
|
||||||
const str = atob(base64.replace(/\-/g, '+').replace(/_/g, '/'));
|
// atob(base64.replace(/\-/g, '+').replace(/_/g, '/'));
|
||||||
return this.str_to_Uint8Array(str);
|
return b64.decode(base64.replace(/\-/g, '+').replace(/_/g, '/'));
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -168,8 +170,8 @@ export default {
|
||||||
* @return {String} Base-64 encoded string
|
* @return {String} Base-64 encoded string
|
||||||
*/
|
*/
|
||||||
Uint8Array_to_b64: function (bytes, url) {
|
Uint8Array_to_b64: function (bytes, url) {
|
||||||
const base64 = btoa(this.Uint8Array_to_str(bytes));
|
// btoa(this.Uint8Array_to_str(bytes)).replace(/\+/g, '-').replace(/\//g, '_');
|
||||||
return url ? base64.replace(/\+/g, '-').replace(/\//g, '_') : base64;
|
return b64.encode(bytes, url).replace('\n', '');
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -144,7 +144,7 @@ describe('Elliptic Curve Cryptography', function () {
|
||||||
it('Creating curve from oid', function (done) {
|
it('Creating curve from oid', function (done) {
|
||||||
const oids = ['2A8648CE3D030107', '2B81040022', '2B81040023', '2B8104000A'];
|
const oids = ['2A8648CE3D030107', '2B81040022', '2B81040023', '2B8104000A'];
|
||||||
oids.forEach(function (oid) {
|
oids.forEach(function (oid) {
|
||||||
expect(new elliptic_curves.Curve(openpgp.util.hex_to_str(oid))).to.exist;
|
expect(new elliptic_curves.Curve(oid)).to.exist;
|
||||||
});
|
});
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -173,21 +173,25 @@ describe('Elliptic Curve Cryptography', function () {
|
||||||
it('Signature verification', function (done) {
|
it('Signature verification', function (done) {
|
||||||
const curve = new elliptic_curves.Curve('p256');
|
const curve = new elliptic_curves.Curve('p256');
|
||||||
const key = curve.keyFromPublic(signature_data.pub);
|
const key = curve.keyFromPublic(signature_data.pub);
|
||||||
expect(key.verify(signature_data.message, signature_data.signature, 8)).to.eventually.be.true;
|
expect(
|
||||||
done();
|
key.verify(signature_data.message, signature_data.signature, 8)
|
||||||
|
).to.eventually.be.true.notify(done);
|
||||||
});
|
});
|
||||||
it('Invalid signature', function (done) {
|
it('Invalid signature', function (done) {
|
||||||
const curve = new elliptic_curves.Curve('p256');
|
const curve = new elliptic_curves.Curve('p256');
|
||||||
const key = curve.keyFromPublic(key_data.p256.pub);
|
const key = curve.keyFromPublic(key_data.p256.pub);
|
||||||
expect(key.verify(signature_data.message, signature_data.signature, 8)).to.eventually.be.false;
|
expect(
|
||||||
done();
|
key.verify(signature_data.message, signature_data.signature, 8)
|
||||||
|
).to.eventually.be.false.notify(done);
|
||||||
});
|
});
|
||||||
it('Signature generation', function () {
|
it('Signature generation', function () {
|
||||||
const curve = new elliptic_curves.Curve('p256');
|
const curve = new elliptic_curves.Curve('p256');
|
||||||
let key = curve.keyFromPrivate(key_data.p256.priv);
|
let key = curve.keyFromPrivate(key_data.p256.priv);
|
||||||
return key.sign(signature_data.message, 8).then(signature => {
|
return key.sign(signature_data.message, 8).then(signature => {
|
||||||
key = curve.keyFromPublic(key_data.p256.pub);
|
key = curve.keyFromPublic(key_data.p256.pub);
|
||||||
expect(key.verify(signature_data.message, signature, 8)).to.eventually.be.true;
|
expect(
|
||||||
|
key.verify(signature_data.message, signature, 8)
|
||||||
|
).to.eventually.be.true;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('Shared secret generation', function (done) {
|
it('Shared secret generation', function (done) {
|
||||||
|
@ -312,7 +316,7 @@ describe('Elliptic Curve Cryptography', function () {
|
||||||
return Promise.resolve().then(() => {
|
return Promise.resolve().then(() => {
|
||||||
const curve = new elliptic_curves.Curve(oid);
|
const curve = new elliptic_curves.Curve(oid);
|
||||||
return elliptic_curves.ecdh.decrypt(
|
return elliptic_curves.ecdh.decrypt(
|
||||||
curve.oid,
|
new openpgp.OID(curve.oid),
|
||||||
cipher,
|
cipher,
|
||||||
hash,
|
hash,
|
||||||
new Uint8Array(ephemeral),
|
new Uint8Array(ephemeral),
|
||||||
|
|
|
@ -524,7 +524,7 @@ describe('X25519 Cryptography', function () {
|
||||||
|
|
||||||
/* TODO how does GPG2 accept this?
|
/* TODO how does GPG2 accept this?
|
||||||
it('Should handle little-endian parameters in EdDSA', function () {
|
it('Should handle little-endian parameters in EdDSA', function () {
|
||||||
var pubKey = [
|
const pubKey = [
|
||||||
'-----BEGIN PGP PUBLIC KEY BLOCK-----',
|
'-----BEGIN PGP PUBLIC KEY BLOCK-----',
|
||||||
'Version: OpenPGP.js v3.0.0',
|
'Version: OpenPGP.js v3.0.0',
|
||||||
'Comment: https://openpgpjs.org',
|
'Comment: https://openpgpjs.org',
|
||||||
|
@ -539,12 +539,12 @@ describe('X25519 Cryptography', function () {
|
||||||
'Gbm1oe83ZB+0aSp5m34YkpHQNb80y8PGFy7nIexiAA==',
|
'Gbm1oe83ZB+0aSp5m34YkpHQNb80y8PGFy7nIexiAA==',
|
||||||
'=xeG/',
|
'=xeG/',
|
||||||
'-----END PGP PUBLIC KEY BLOCK-----'].join('\n');
|
'-----END PGP PUBLIC KEY BLOCK-----'].join('\n');
|
||||||
var hi = openpgp.key.readArmored(pubKey).keys[0];
|
const hi = openpgp.key.readArmored(pubKey).keys[0];
|
||||||
return hi.verifyPrimaryUser().then(() => {
|
return hi.verifyPrimaryUser().then(() => {
|
||||||
var results = hi.getPrimaryUser();
|
const results = hi.getPrimaryUser();
|
||||||
expect(results).to.exist;
|
expect(results).to.exist;
|
||||||
expect(results.user).to.exist;
|
expect(results.user).to.exist;
|
||||||
var user = results.user;
|
const user = results.user;
|
||||||
expect(user.selfCertifications[0].verify(
|
expect(user.selfCertifications[0].verify(
|
||||||
hi.primaryKey, {userid: user.userId, key: hi.primaryKey}
|
hi.primaryKey, {userid: user.userId, key: hi.primaryKey}
|
||||||
)).to.eventually.be.true;
|
)).to.eventually.be.true;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user