Addresses various review comments by @sanjanarajan

* Various FIXME tags are removed
 * In curve.js:
  - webCrypto/nodeCrypto fallback bug is fixed
  - Curve25519 has keyType ecdsa (won't be used for signing, but technically can be)
  - webGenKeyPair is simplifed
 * In base64.js:
  - documentation added and arguments simplified
 * In ecdsa.js and eddsa.js:
  - hash_algo is now at least as strong as the default curve hash
  - simplified the code by moving webSign/nodeSign and webVerify/nodeVerify to live in key.js (ht @ismaelbej)
 * In message.js:
  - in decryptSessionKey, loops break once a key packet is decrypted
 * In key.js:
  - getPreferredHashAlgorithm returns the best hash algorithm
  - enums are used for curve selection
This commit is contained in:
Mahrud Sayrafi 2018-01-18 00:34:03 -08:00 committed by Sanjana Rajan
parent 3129e7c4e3
commit 5cb89f4f25
11 changed files with 278 additions and 259 deletions

View File

@ -34,19 +34,19 @@ import random from '../../random';
import config from '../../../config'; import config from '../../../config';
import enums from '../../../enums'; import enums from '../../../enums';
import util from '../../../util'; import util from '../../../util';
import OID from '../../../type/oid';
import base64 from '../../../encoding/base64'; import base64 from '../../../encoding/base64';
const webCrypto = util.getWebCrypto(); const webCrypto = util.getWebCrypto();
const nodeCrypto = util.getNodeCrypto(); const nodeCrypto = util.getNodeCrypto();
var webCurves = {}, nodeCurves = {}; var webCurves = {}, nodeCurves = {};
if (webCrypto && config.use_native) { webCurves = {
webCurves = { 'p256': 'P-256',
'p256': 'P-256', 'p384': 'P-384',
'p384': 'P-384', 'p521': 'P-521'
'p521': 'P-521' };
}; if (nodeCrypto && config.use_native) {
} else if (nodeCrypto && config.use_native) {
var knownCurves = nodeCrypto.getCurves(); var knownCurves = nodeCrypto.getCurves();
nodeCurves = { nodeCurves = {
'secp256k1': knownCurves.includes('secp256k1') ? 'secp256k1' : undefined, 'secp256k1': knownCurves.includes('secp256k1') ? 'secp256k1' : undefined,
@ -63,8 +63,8 @@ const curves = {
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: nodeCurves.secp256r1, node: nodeCurves.p256,
web: webCurves.secp256r1, web: webCurves.p256,
payloadSize: 32 payloadSize: 32
}, },
p384: { p384: {
@ -72,8 +72,8 @@ const curves = {
keyType: enums.publicKey.ecdsa, keyType: enums.publicKey.ecdsa,
hash: enums.hash.sha384, hash: enums.hash.sha384,
cipher: enums.symmetric.aes192, cipher: enums.symmetric.aes192,
node: nodeCurves.secp384r1, node: nodeCurves.p384,
web: webCurves.secp384r1, web: webCurves.p384,
payloadSize: 48 payloadSize: 48
}, },
p521: { p521: {
@ -81,8 +81,8 @@ const curves = {
keyType: enums.publicKey.ecdsa, keyType: enums.publicKey.ecdsa,
hash: enums.hash.sha512, hash: enums.hash.sha512,
cipher: enums.symmetric.aes256, cipher: enums.symmetric.aes256,
node: nodeCurves.secp521r1, node: nodeCurves.p521,
web: webCurves.secp521r1, web: webCurves.p521,
payloadSize: 66 payloadSize: 66
}, },
secp256k1: { secp256k1: {
@ -99,13 +99,16 @@ const curves = {
}, },
curve25519: { curve25519: {
oid: util.bin2str([0x2B, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01]), oid: util.bin2str([0x2B, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01]),
keyType: enums.publicKey.ecdh, 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.bin2str([0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07]) oid: util.bin2str([0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07])
}, },
brainpoolP384r1: { // TODO 1.3.36.3.3.2.8.1.1.11
oid: util.bin2str([0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B])
},
brainpoolP512r1: { // TODO 1.3.36.3.3.2.8.1.1.13 brainpoolP512r1: { // TODO 1.3.36.3.3.2.8.1.1.13
oid: util.bin2str([0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D]) oid: util.bin2str([0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D])
} }
@ -118,12 +121,12 @@ function Curve(name, params) {
this.curve = new EdDSA(name); this.curve = new EdDSA(name);
break; break;
case enums.publicKey.ecdsa: case enums.publicKey.ecdsa:
case enums.publicKey.ecdh:
this.curve = new EC(name); this.curve = new EC(name);
break; break;
default: default:
throw new Error('Unknown elliptic key type;'); throw new Error('Unknown elliptic key type;');
} }
this.name = name;
this.oid = curves[name].oid; this.oid = curves[name].oid;
this.hash = params.hash; this.hash = params.hash;
this.cipher = params.cipher; this.cipher = params.cipher;
@ -145,14 +148,14 @@ Curve.prototype.keyFromPublic = function (pub) {
}; };
Curve.prototype.genKeyPair = async function () { Curve.prototype.genKeyPair = async function () {
var r, keyPair; var keyPair;
if (webCrypto && config.use_native && this.web) { if (webCrypto && config.use_native && this.web) {
keyPair = await webGenKeyPair(this.name, "ECDSA"); // FIXME is ECDH different? keyPair = await webGenKeyPair(this.name);
} else if (nodeCrypto && config.use_native && this.node) { } else if (nodeCrypto && config.use_native && this.node) {
keyPair = await nodeGenKeyPair(this.name); keyPair = await nodeGenKeyPair(this.name);
} else { } else {
var compact = this.curve.curve.type === 'edwards' || this.curve.curve.type === 'mont'; var compact = this.curve.curve.type === 'edwards' || this.curve.curve.type === 'mont';
r = await this.curve.genKeyPair(); var r = await this.curve.genKeyPair();
if (this.keyType === enums.publicKey.eddsa) { if (this.keyType === enums.publicKey.eddsa) {
keyPair = { secret: r.getSecret() }; keyPair = { secret: r.getSecret() };
} else { } else {
@ -162,17 +165,18 @@ Curve.prototype.genKeyPair = async function () {
return new KeyPair(this.curve, keyPair); return new KeyPair(this.curve, keyPair);
}; };
function get(oid_or_name) { function get(oid_or_name) {
var name; var name;
if (enums.curve[oid_or_name]) { if (OID.prototype.isPrototypeOf(oid_or_name) &&
name = enums.write(enums.curve, oid_or_name); enums.curve[oid_or_name.toHex()]) {
name = enums.write(enums.curve, oid_or_name.toHex()); // by curve OID
return new Curve(name, curves[name]);
} else if (enums.curve[oid_or_name]) {
name = enums.write(enums.curve, oid_or_name); // by curve name
return new Curve(name, curves[name]);
} else if (enums.curve[util.hexstrdump(oid_or_name)]) {
name = enums.write(enums.curve, util.hexstrdump(oid_or_name)); // by oid string
return new Curve(name, curves[name]); return new Curve(name, curves[name]);
}
for (name in curves) {
if (curves[name].oid === oid_or_name) {
return new Curve(name, curves[name]);
}
} }
throw new Error('Not valid curve'); throw new Error('Not valid curve');
} }
@ -189,8 +193,16 @@ async function generate(curve) {
}; };
} }
function getPreferredHashAlgorithm(oid) {
return curves[enums.write(enums.curve, oid.toHex())].hash;
}
module.exports = { module.exports = {
Curve: Curve, Curve: Curve,
curves: curves,
webCurves: webCurves,
nodeCurves: nodeCurves,
getPreferredHashAlgorithm: getPreferredHashAlgorithm,
generate: generate, generate: generate,
get: get get: get
}; };
@ -203,14 +215,10 @@ module.exports = {
////////////////////////// //////////////////////////
async function webGenKeyPair(name, algorithm) { async function webGenKeyPair(name) {
// Note: keys generated with ECDSA and ECDH are structurally equivalent
var webCryptoKey = await webCrypto.generateKey( var webCryptoKey = await webCrypto.generateKey(
{ { name: "ECDSA", namedCurve: webCurves[name] }, true, ["sign", "verify"]
name: algorithm === "ECDH" ? "ECDH" : "ECDSA",
namedCurve: webCurves[name]
},
true,
algorithm === "ECDH" ? ["deriveKey", "deriveBits"] : ["sign", "verify"]
); );
var privateKey = await webCrypto.exportKey("jwk", webCryptoKey.privateKey); var privateKey = await webCrypto.exportKey("jwk", webCryptoKey.privateKey);
@ -218,15 +226,15 @@ async function webGenKeyPair(name, algorithm) {
return { return {
pub: { pub: {
x: base64.decode(publicKey.x, 'base64url'), x: base64.decode(publicKey.x, true),
y: base64.decode(publicKey.y, 'base64url') y: base64.decode(publicKey.y, true)
}, },
priv: base64.decode(privateKey.d, 'base64url') priv: base64.decode(privateKey.d, true)
}; };
} }
async function nodeGenKeyPair(name) { async function nodeGenKeyPair(name) {
var ecdh = nodeCrypto.createECDH(name === "secp256r1" ? "prime256v1" : name); var ecdh = nodeCrypto.createECDH(nodeCurves[name]);
await ecdh.generateKeys(); await ecdh.generateKeys();
return { return {

View File

@ -25,28 +25,9 @@
'use strict'; 'use strict';
import BN from 'bn.js'; import hash from '../../hash';
import ASN1 from 'asn1.js';
import jwkToPem from 'jwk-to-pem';
import curves from './curves.js'; import curves from './curves.js';
import BigInteger from '../jsbn.js'; import BigInteger from '../jsbn.js';
import config from '../../../config';
import enums from '../../../enums.js';
import util from '../../../util.js';
import base64 from '../../../encoding/base64.js';
const webCrypto = util.getWebCrypto();
const webCurves = curves.webCurves;
const nodeCrypto = util.getNodeCrypto();
const nodeCurves = curves.nodeCurves;
var ECDSASignature = ASN1.define('ECDSASignature', function() {
this.seq().obj(
this.key('r').int(), // FIXME int or BN?
this.key('s').int() // FIXME int or BN?
);
});
/** /**
* Sign a message using the provided key * Sign a message using the provided key
@ -57,17 +38,9 @@ var ECDSASignature = ASN1.define('ECDSASignature', function() {
* @return {{r: BigInteger, s: BigInteger}} Signature of the message * @return {{r: BigInteger, s: BigInteger}} Signature of the message
*/ */
async function sign(oid, hash_algo, m, d) { async function sign(oid, hash_algo, m, d) {
var signature;
const curve = curves.get(oid); const curve = curves.get(oid);
hash_algo = hash_algo ? hash_algo : curve.hash;
const key = curve.keyFromPrivate(d.toByteArray()); const key = curve.keyFromPrivate(d.toByteArray());
if (webCrypto && config.use_native && curve.web) { const signature = await key.sign(m, hash_algo);
signature = await webSign(curve, hash_algo, m, key.keyPair);
} else if (nodeCrypto && config.use_native && curve.node) {
signature = await nodeSign(curve, hash_algo, m, key.keyPair);
} else {
signature = await key.sign(m, hash_algo);
}
return { return {
r: new BigInteger(signature.r.toArray()), r: new BigInteger(signature.r.toArray()),
s: new BigInteger(signature.s.toArray()) s: new BigInteger(signature.s.toArray())
@ -84,168 +57,14 @@ async function sign(oid, hash_algo, m, d) {
* @return {Boolean} * @return {Boolean}
*/ */
async function verify(oid, hash_algo, signature, m, Q) { async function verify(oid, hash_algo, signature, m, Q) {
var result;
const curve = curves.get(oid); const curve = curves.get(oid);
hash_algo = hash_algo ? hash_algo : curve.hash; // FIXME is this according to the RFC?
const key = curve.keyFromPublic(Q.toByteArray()); const key = curve.keyFromPublic(Q.toByteArray());
if (webCrypto && config.use_native && curve.web) { return key.verify(
result = await webVerify(curve, hash_algo, signature, m, key.keyPair.getPublic()); m, { r: signature.r.toByteArray(), s: signature.s.toByteArray() }, hash_algo
} else if (nodeCrypto && config.use_native && curve.node) { );
result = await nodeVerify(curve, hash_algo, signature, m, key.keyPair.getPublic());
} else {
result = await key.verify(
m, {r: signature.r.toByteArray(), s: signature.s.toByteArray()}, hash_algo
);
}
return result;
} }
module.exports = { module.exports = {
sign: sign, sign: sign,
verify: verify verify: verify
}; };
//////////////////////////
// //
// Helper functions //
// //
//////////////////////////
async function webSign(curve, hash_algo, message, keyPair) {
var l = curve.payloadSize;
if (typeof message === 'string') {
message = util.str2Uint8Array(message);
}
const key = await webCrypto.importKey(
"jwk",
{
"kty": "EC",
"crv": webCurves[curve.name],
"x": base64.encode(new Uint8Array(keyPair.getPublic().getX().toArray('be', l)), null, 'base64url'),
"y": base64.encode(new Uint8Array(keyPair.getPublic().getY().toArray('be', l)), null, 'base64url'),
"d": base64.encode(new Uint8Array(keyPair.getPrivate().toArray('be', l)), null, 'base64url'),
"use": "sig",
"kid": "ECDSA Private Key"
},
{
"name": "ECDSA",
"namedCurve": webCurves[curve.name],
"hash": { name: enums.read(enums.webHash, curve.hash) }
},
false,
["sign"]
);
const signature = new Uint8Array(await webCrypto.sign(
{
"name": 'ECDSA',
"namedCurve": webCurves[curve.name],
"hash": { name: enums.read(enums.webHash, hash_algo) }
},
key,
message
));
return {
r: signature.slice(0, l),
s: signature.slice(l, 2 * l)
};
}
async function webVerify(curve, hash_algo, signature, message, publicKey) {
var r = signature.r.toByteArray(), s = signature.s.toByteArray(), l = curve.payloadSize;
r = (r.length === l) ? r : [0].concat(r);
s = (s.length === l) ? s : [0].concat(s);
signature = new Uint8Array(r.concat(s)).buffer;
if (typeof message === 'string') {
message = util.str2Uint8Array(message);
}
const key = await webCrypto.importKey(
"jwk",
{
"kty": "EC",
"crv": webCurves[curve.name],
"x": base64.encode(new Uint8Array(publicKey.getX().toArray('be', l)), null, 'base64url'),
"y": base64.encode(new Uint8Array(publicKey.getY().toArray('be', l)), null, 'base64url'),
"use": "sig",
"kid": "ECDSA Public Key"
},
{
"name": "ECDSA",
"namedCurve": webCurves[curve.name],
"hash": { name: enums.read(enums.webHash, curve.hash) }
},
false,
["verify"]
);
return webCrypto.verify(
{
"name": 'ECDSA',
"namedCurve": webCurves[curve.name],
"hash": { name: enums.read(enums.webHash, hash_algo) }
},
key,
signature,
message
);
}
async function nodeSign(curve, hash_algo, message, keyPair) {
if (typeof message === 'string') {
message = util.str2Uint8Array(message);
}
const key = jwkToPem(
{
"kty": "EC",
"crv": nodeCurves[curve.name],
"x": base64.encode(new Uint8Array(keyPair.getPublic().getX().toArray())),
"y": base64.encode(new Uint8Array(keyPair.getPublic().getY().toArray())),
"d": base64.encode(new Uint8Array(keyPair.getPrivate().toArray())),
"use": "sig",
"kid": "ECDSA Private Key"
},
{ private: true }
);
const sign = nodeCrypto.createSign(enums.read(enums.hash, hash_algo));
sign.write(message);
sign.end();
const signature = await ECDSASignature.decode(sign.sign(key), 'der');
return {
r: signature.r.toArray(),
s: signature.s.toArray()
};
}
async function nodeVerify(curve, hash_algo, signature, message, publicKey) {
signature = ECDSASignature.encode(
{
r: new BN(signature.r.toByteArray()),
s: new BN(signature.s.toByteArray())
},
'der');
if (typeof message === 'string') {
message = util.str2Uint8Array(message);
}
const key = jwkToPem(
{
"kty": "EC",
"crv": nodeCurves[curve.name],
"x": base64.encode(new Uint8Array(publicKey.getX().toArray())),
"y": base64.encode(new Uint8Array(publicKey.getY().toArray())),
"use": "sig",
"kid": "ECDSA Public Key"
},
{ private: false }
);
// FIXME what happens when hash_algo = undefined?
const verify = nodeCrypto.createVerify(enums.read(enums.hash, hash_algo));
verify.write(message);
verify.end();
const result = await verify.verify(key, signature);
return result;
}

View File

@ -2,6 +2,7 @@
'use strict'; 'use strict';
import hash from '../../hash';
import curves from './curves.js'; import curves from './curves.js';
import BigInteger from '../jsbn.js'; import BigInteger from '../jsbn.js';
@ -14,11 +15,9 @@ import BigInteger from '../jsbn.js';
* @return {{r: BigInteger, s: BigInteger}} Signature of the message * @return {{r: BigInteger, s: BigInteger}} Signature of the message
*/ */
async function sign(oid, hash_algo, m, d) { async function sign(oid, hash_algo, m, d) {
var signature;
const curve = curves.get(oid); const curve = curves.get(oid);
hash_algo = hash_algo ? hash_algo : curve.hash;
const key = curve.keyFromSecret(d.toByteArray()); const key = curve.keyFromSecret(d.toByteArray());
signature = await key.sign(m, hash_algo); const signature = await key.sign(m, hash_algo);
return { return {
r: new BigInteger(signature.Rencoded()), r: new BigInteger(signature.Rencoded()),
s: new BigInteger(signature.Sencoded()) s: new BigInteger(signature.Sencoded())
@ -35,12 +34,10 @@ async function sign(oid, hash_algo, m, d) {
* @return {Boolean} * @return {Boolean}
*/ */
async function verify(oid, hash_algo, signature, m, Q) { async function verify(oid, hash_algo, signature, m, Q) {
var result;
const curve = curves.get(oid); const curve = curves.get(oid);
hash_algo = hash_algo ? hash_algo : curve.hash; // FIXME is this according to the RFC?
const key = curve.keyFromPublic(Q.toByteArray()); const key = curve.keyFromPublic(Q.toByteArray());
return key.verify( return key.verify(
m, {R: signature.r.toByteArray(), S: signature.s.toByteArray()}, hash_algo m, { R: signature.r.toByteArray(), S: signature.s.toByteArray() }, hash_algo
); );
} }

View File

@ -27,7 +27,7 @@
'use strict'; 'use strict';
import {get, generate} from './curves'; import {get, generate, getPreferredHashAlgorithm} from './curves';
import ecdsa from './ecdsa'; import ecdsa from './ecdsa';
import eddsa from './eddsa'; import eddsa from './eddsa';
import ecdh from './ecdh'; import ecdh from './ecdh';
@ -37,5 +37,6 @@ module.exports = {
eddsa: eddsa, eddsa: eddsa,
ecdh: ecdh, ecdh: ecdh,
get: get, get: get,
generate: generate generate: generate,
getPreferredHashAlgorithm: getPreferredHashAlgorithm
}; };

View File

@ -25,9 +25,28 @@
'use strict'; 'use strict';
import BN from 'bn.js';
import ASN1 from 'asn1.js';
import jwkToPem from 'jwk-to-pem';
import curves 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';
import config from '../../../config';
import base64 from '../../../encoding/base64';
const webCrypto = util.getWebCrypto();
const webCurves = curves.webCurves;
const nodeCrypto = util.getNodeCrypto();
const nodeCurves = curves.nodeCurves;
var ECDSASignature = ASN1.define('ECDSASignature', function() {
this.seq().obj(
this.key('r').int(),
this.key('s').int()
);
});
function KeyPair(curve, options) { function KeyPair(curve, options) {
this.curve = curve; this.curve = curve;
@ -35,20 +54,32 @@ function KeyPair(curve, options) {
this.keyPair = this.curve.keyPair(options); this.keyPair = this.curve.keyPair(options);
} }
KeyPair.prototype.sign = function (message, hash_algo) { KeyPair.prototype.sign = async function (message, hash_algo) {
if (typeof message === 'string') { if (typeof message === 'string') {
message = util.str2Uint8Array(message); message = util.str2Uint8Array(message);
} }
const digest = (typeof hash_algo === 'undefined') ? message : hash.digest(hash_algo, message); if (webCrypto && config.use_native && this.curve.web) {
return this.keyPair.sign(digest); return webSign(this.curve, hash_algo, message, this.keyPair);
} else if (nodeCrypto && config.use_native && this.curve.node) {
return nodeSign(this.curve, hash_algo, message, this.keyPair);
} else {
const digest = (typeof hash_algo === 'undefined') ? message : hash.digest(hash_algo, message);
return this.keyPair.sign(digest);
}
}; };
KeyPair.prototype.verify = function (message, signature, hash_algo) { KeyPair.prototype.verify = async function (message, signature, hash_algo) {
if (typeof message === 'string') { if (typeof message === 'string') {
message = util.str2Uint8Array(message); message = util.str2Uint8Array(message);
} }
const digest = (typeof hash_algo === 'undefined') ? message : hash.digest(hash_algo, message); if (webCrypto && config.use_native && this.curve.web) {
return this.keyPair.verify(digest, signature); return webVerify(this.curve, hash_algo, signature, message, this.keyPair.getPublic());
} else if (nodeCrypto && config.use_native && this.curve.node) {
return nodeVerify(this.curve, hash_algo, signature, message, this.keyPair.getPublic());
} else {
const digest = (typeof hash_algo === 'undefined') ? message : hash.digest(hash_algo, message);
return this.keyPair.verify(digest, signature);
}
}; };
KeyPair.prototype.derive = function (pub) { KeyPair.prototype.derive = function (pub) {
@ -81,3 +112,131 @@ KeyPair.prototype.isValid = function () {
module.exports = { module.exports = {
KeyPair: KeyPair KeyPair: KeyPair
}; };
//////////////////////////
// //
// Helper functions //
// //
//////////////////////////
async function webSign(curve, hash_algo, message, keyPair) {
var l = curve.payloadSize;
const key = await webCrypto.importKey(
"jwk",
{
"kty": "EC",
"crv": webCurves[curve.name],
"x": base64.encode(new Uint8Array(keyPair.getPublic().getX().toArray('be', l)), true),
"y": base64.encode(new Uint8Array(keyPair.getPublic().getY().toArray('be', l)), true),
"d": base64.encode(new Uint8Array(keyPair.getPrivate().toArray('be', l)), true),
"use": "sig",
"kid": "ECDSA Private Key"
},
{
"name": "ECDSA",
"namedCurve": webCurves[curve.name],
"hash": { name: enums.read(enums.webHash, curve.hash) }
},
false,
["sign"]
);
const signature = new Uint8Array(await webCrypto.sign(
{
"name": 'ECDSA',
"namedCurve": webCurves[curve.name],
"hash": { name: enums.read(enums.webHash, hash_algo) }
},
key,
message
));
return {
r: signature.slice(0, l),
s: signature.slice(l, 2 * l)
};
}
async function webVerify(curve, hash_algo, {r, s}, message, publicKey) {
var l = curve.payloadSize;
r = (r.length === l) ? r : [0].concat(r);
s = (s.length === l) ? s : [0].concat(s);
var signature = new Uint8Array(r.concat(s)).buffer;
const key = await webCrypto.importKey(
"jwk",
{
"kty": "EC",
"crv": webCurves[curve.name],
"x": base64.encode(new Uint8Array(publicKey.getX().toArray('be', l)), true),
"y": base64.encode(new Uint8Array(publicKey.getY().toArray('be', l)), true),
"use": "sig",
"kid": "ECDSA Public Key"
},
{
"name": "ECDSA",
"namedCurve": webCurves[curve.name],
"hash": { name: enums.read(enums.webHash, curve.hash) }
},
false,
["verify"]
);
return webCrypto.verify(
{
"name": 'ECDSA',
"namedCurve": webCurves[curve.name],
"hash": { name: enums.read(enums.webHash, hash_algo) }
},
key,
signature,
message
);
}
async function nodeSign(curve, hash_algo, message, keyPair) {
const key = jwkToPem(
{
"kty": "EC",
"crv": webCurves[curve.name],
"x": base64.encode(new Uint8Array(keyPair.getPublic().getX().toArray())),
"y": base64.encode(new Uint8Array(keyPair.getPublic().getY().toArray())),
"d": base64.encode(new Uint8Array(keyPair.getPrivate().toArray())),
"use": "sig",
"kid": "ECDSA Private Key"
},
{ private: true }
);
const sign = nodeCrypto.createSign(enums.read(enums.hash, hash_algo));
sign.write(message);
sign.end();
const signature = await ECDSASignature.decode(sign.sign(key), 'der');
return {
r: signature.r.toArray(),
s: signature.s.toArray()
};
}
async function nodeVerify(curve, hash_algo, {r, s}, message, publicKey) {
var signature = ECDSASignature.encode({ r: new BN(r), s: new BN(s) }, 'der');
const key = jwkToPem(
{
"kty": "EC",
"crv": webCurves[curve.name],
"x": base64.encode(new Uint8Array(publicKey.getX().toArray())),
"y": base64.encode(new Uint8Array(publicKey.getY().toArray())),
"use": "sig",
"kid": "ECDSA Public Key"
},
{ private: false }
);
// FIXME what happens when hash_algo = undefined?
const verify = nodeCrypto.createVerify(enums.read(enums.hash, hash_algo));
verify.write(message);
verify.end();
const result = await verify.verify(key, signature);
return result;
}

View File

@ -17,20 +17,21 @@
'use strict'; 'use strict';
var b64s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; var b64s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; // Standard radix-64
var b64u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; var b64u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; // URL-safe radix-64
/** /**
* Convert binary array to radix-64 * Convert binary array to radix-64
* @param {Uint8Array} t Uint8Array to convert * @param {Uint8Array} t Uint8Array to convert
* @param {bool} u if true, output is URL-safe
* @returns {string} radix-64 version of input string * @returns {string} radix-64 version of input string
* @static * @static
*/ */
function s2r(t, o, u) { function s2r(t, u = false) {
// TODO check btoa alternative // TODO check btoa alternative
var b64 = (u === "base64url") ? b64u : b64s; var b64 = u ? b64u : b64s;
var a, c, n; var a, c, n;
var r = o ? o : [], var r = [],
l = 0, l = 0,
s = 0; s = 0;
var tl = t.length; var tl = t.length;
@ -67,33 +68,30 @@ function s2r(t, o, u) {
if ((l % 60) === 0 && !u) { if ((l % 60) === 0 && !u) {
r.push("\n"); r.push("\n");
} }
if (u !== 'base64url') { if (!u) {
r.push('='); r.push('=');
l += 1; l += 1;
} }
} }
if (s === 1 && u !== 'base64url') { if (s === 1 && !u) {
if ((l % 60) === 0 && !u) { if ((l % 60) === 0 && !u) {
r.push("\n"); r.push("\n");
} }
r.push('='); r.push('=');
} }
if (o)
{
return;
}
return r.join(''); return r.join('');
} }
/** /**
* Convert radix-64 to binary array * Convert radix-64 to binary array
* @param {String} t radix-64 string to convert * @param {String} t radix-64 string to convert
* @param {bool} u if true, input is interpreted as URL-safe
* @returns {Uint8Array} binary array version of input string * @returns {Uint8Array} binary array version of input string
* @static * @static
*/ */
function r2s(t, u) { function r2s(t, u) {
// TODO check atob alternative // TODO check atob alternative
var b64 = (u === "base64url") ? b64u : b64s; var b64 = u ? b64u : b64s;
var c, n; var c, n;
var r = [], var r = [],
s = 0, s = 0,

View File

@ -16,25 +16,40 @@ export default {
"secp256r1": "p256", "secp256r1": "p256",
"prime256v1": "p256", "prime256v1": "p256",
"1.2.840.10045.3.1.7": "p256", "1.2.840.10045.3.1.7": "p256",
"2a8648ce3d030107": "p256",
"2A8648CE3D030107": "p256",
"p384": "p384", "p384": "p384",
"P-384": "p384", "P-384": "p384",
"secp384r1": "p384", "secp384r1": "p384",
"1.3.132.0.34": "p384", "1.3.132.0.34": "p384",
"2b81040022": "p384",
"2B81040022": "p384",
"p521": "p521", "p521": "p521",
"P-521": "p521", "P-521": "p521",
"secp521r1": "p521", "secp521r1": "p521",
"1.3.132.0.35": "p521", "1.3.132.0.35": "p521",
"2b81040023": "p521",
"2B81040023": "p521",
"secp256k1": "secp256k1", "secp256k1": "secp256k1",
"1.3.132.0.10": "secp256k1", "1.3.132.0.10": "secp256k1",
"2b8104000a": "secp256k1",
"2B8104000A": "secp256k1",
"ed25519": "ed25519", "ed25519": "ed25519",
"Ed25519": "ed25519",
"1.3.6.1.4.1.11591.15.1": "ed25519", "1.3.6.1.4.1.11591.15.1": "ed25519",
"2b06010401da470f01": "ed25519",
"2B06010401DA470F01": "ed25519",
"cv25519": "curve25519",
"curve25519": "curve25519", "curve25519": "curve25519",
"1.3.6.1.4.1.3029.1.5.1": "curve25519" "Curve25519": "curve25519",
"1.3.6.1.4.1.3029.1.5.1": "curve25519",
"2b060104019755010501": "curve25519",
"2B060104019755010501": "curve25519"
}, },
/** A string to key specifier type /** A string to key specifier type

View File

@ -145,7 +145,7 @@ Packetlist.prototype.filterByTag = function () {
*/ */
Packetlist.prototype.forEach = function (callback) { Packetlist.prototype.forEach = function (callback) {
for (var i = 0; i < this.length; i++) { for (var i = 0; i < this.length; i++) {
callback(this[i]); callback(this[i], i, this);
} }
}; };
@ -163,6 +163,20 @@ Packetlist.prototype.map = function (callback) {
return packetArray; return packetArray;
}; };
/**
* Executes the callback function once for each element
* until it finds one where callback returns a truthy value
*/
Packetlist.prototype.some = async function (callback) {
for (var i = 0; i < this.length; i++) {
// eslint-disable-next-line no-await-in-loop
if (await callback(this[i], i, this)) {
return true;
}
}
return false;
};
/** /**
* Traverses packet tree and returns first matching packet * Traverses packet tree and returns first matching packet
* @param {module:enums.packet} type The packet type * @param {module:enums.packet} type The packet type

View File

@ -68,6 +68,14 @@ OID.prototype.write = function () {
String.fromCharCode(this.oid.length)+this.oid); String.fromCharCode(this.oid.length)+this.oid);
}; };
/**
* Serialize an OID object as a hex string
* @return {string} String with the hex value of the OID
*/
OID.prototype.toHex = function() {
return util.hexstrdump(this.oid);
};
OID.fromClone = function (clone) { OID.fromClone = function (clone) {
var oid = new OID(clone.oid); var oid = new OID(clone.oid);
return oid; return oid;

View File

@ -175,22 +175,22 @@ describe('Elliptic Curve Cryptography', function () {
it('Signature verification', function (done) { it('Signature verification', function (done) {
var curve = elliptic_curves.get('p256'); var curve = elliptic_curves.get('p256');
var key = curve.keyFromPublic(signature_data.pub); var key = curve.keyFromPublic(signature_data.pub);
expect(key.verify(signature_data.message, signature_data.signature, 8)).to.be.true; expect(key.verify(signature_data.message, signature_data.signature, 8)).to.eventually.be.true;
done(); done();
}); });
it('Invalid signature', function (done) { it('Invalid signature', function (done) {
var curve = elliptic_curves.get('p256'); var curve = elliptic_curves.get('p256');
var key = curve.keyFromPublic(key_data.p256.pub); var key = curve.keyFromPublic(key_data.p256.pub);
expect(key.verify(signature_data.message, signature_data.signature, 8)).to.be.false; expect(key.verify(signature_data.message, signature_data.signature, 8)).to.eventually.be.false;
done(); done();
}); });
it('Signature generation', function (done) { it('Signature generation', function () {
var curve = elliptic_curves.get('p256'); var curve = elliptic_curves.get('p256');
var key = curve.keyFromPrivate(key_data.p256.priv); var key = curve.keyFromPrivate(key_data.p256.priv);
var signature = key.sign(signature_data.message, 8); 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.be.true; expect(key.verify(signature_data.message, signature, 8)).to.eventually.be.true;
done(); });
}); });
it('Shared secret generation', function (done) { it('Shared secret generation', function (done) {
var curve = elliptic_curves.get('p256'); var curve = elliptic_curves.get('p256');

View File

@ -200,7 +200,7 @@ describe('Elliptic Curve Cryptography', function () {
var romeo = load_priv_key('romeo'); var romeo = load_priv_key('romeo');
var juliet = load_pub_key('juliet'); var juliet = load_pub_key('juliet');
expect(romeo.decrypt(data['romeo'].pass)).to.be.true; expect(romeo.decrypt(data['romeo'].pass)).to.be.true;
openpgp.encrypt( return openpgp.encrypt(
{publicKeys: [juliet], privateKeys: [romeo], data: data.romeo.message + "\n"} {publicKeys: [juliet], privateKeys: [romeo], data: data.romeo.message + "\n"}
).then(function (encrypted) { ).then(function (encrypted) {
var message = openpgp.message.readArmored(encrypted.data); var message = openpgp.message.readArmored(encrypted.data);