From 5cb89f4f257fe7bbbc88c8a6b02345b8e4b8ebe0 Mon Sep 17 00:00:00 2001 From: Mahrud Sayrafi Date: Thu, 18 Jan 2018 00:34:03 -0800 Subject: [PATCH] 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 --- src/crypto/public_key/elliptic/curves.js | 82 +++++----- src/crypto/public_key/elliptic/ecdsa.js | 191 +---------------------- src/crypto/public_key/elliptic/eddsa.js | 9 +- src/crypto/public_key/elliptic/index.js | 5 +- src/crypto/public_key/elliptic/key.js | 171 +++++++++++++++++++- src/encoding/base64.js | 22 ++- src/enums.js | 17 +- src/packet/packetlist.js | 16 +- src/type/oid.js | 8 + test/crypto/elliptic.js | 14 +- test/general/ecc.js | 2 +- 11 files changed, 278 insertions(+), 259 deletions(-) diff --git a/src/crypto/public_key/elliptic/curves.js b/src/crypto/public_key/elliptic/curves.js index d21d8121..3fa52cf5 100644 --- a/src/crypto/public_key/elliptic/curves.js +++ b/src/crypto/public_key/elliptic/curves.js @@ -34,19 +34,19 @@ import random from '../../random'; import config from '../../../config'; import enums from '../../../enums'; import util from '../../../util'; +import OID from '../../../type/oid'; import base64 from '../../../encoding/base64'; const webCrypto = util.getWebCrypto(); const nodeCrypto = util.getNodeCrypto(); var webCurves = {}, nodeCurves = {}; -if (webCrypto && config.use_native) { - webCurves = { - 'p256': 'P-256', - 'p384': 'P-384', - 'p521': 'P-521' - }; -} else if (nodeCrypto && config.use_native) { +webCurves = { + 'p256': 'P-256', + 'p384': 'P-384', + 'p521': 'P-521' +}; +if (nodeCrypto && config.use_native) { var knownCurves = nodeCrypto.getCurves(); nodeCurves = { 'secp256k1': knownCurves.includes('secp256k1') ? 'secp256k1' : undefined, @@ -63,8 +63,8 @@ const curves = { keyType: enums.publicKey.ecdsa, hash: enums.hash.sha256, cipher: enums.symmetric.aes128, - node: nodeCurves.secp256r1, - web: webCurves.secp256r1, + node: nodeCurves.p256, + web: webCurves.p256, payloadSize: 32 }, p384: { @@ -72,8 +72,8 @@ const curves = { keyType: enums.publicKey.ecdsa, hash: enums.hash.sha384, cipher: enums.symmetric.aes192, - node: nodeCurves.secp384r1, - web: webCurves.secp384r1, + node: nodeCurves.p384, + web: webCurves.p384, payloadSize: 48 }, p521: { @@ -81,8 +81,8 @@ const curves = { keyType: enums.publicKey.ecdsa, hash: enums.hash.sha512, cipher: enums.symmetric.aes256, - node: nodeCurves.secp521r1, - web: webCurves.secp521r1, + node: nodeCurves.p521, + web: webCurves.p521, payloadSize: 66 }, secp256k1: { @@ -99,13 +99,16 @@ const curves = { }, curve25519: { 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, cipher: enums.symmetric.aes128 }, brainpoolP256r1: { // TODO 1.3.36.3.3.2.8.1.1.7 oid: util.bin2str([0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07]) }, + brainpoolP384r1: { // TODO 1.3.36.3.3.2.8.1.1.11 + oid: util.bin2str([0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B]) + }, brainpoolP512r1: { // TODO 1.3.36.3.3.2.8.1.1.13 oid: util.bin2str([0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D]) } @@ -118,12 +121,12 @@ function Curve(name, params) { this.curve = new EdDSA(name); break; case enums.publicKey.ecdsa: - case enums.publicKey.ecdh: this.curve = new EC(name); break; default: throw new Error('Unknown elliptic key type;'); } + this.name = name; this.oid = curves[name].oid; this.hash = params.hash; this.cipher = params.cipher; @@ -145,14 +148,14 @@ Curve.prototype.keyFromPublic = function (pub) { }; Curve.prototype.genKeyPair = async function () { - var r, keyPair; + var keyPair; 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) { keyPair = await nodeGenKeyPair(this.name); } else { 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) { keyPair = { secret: r.getSecret() }; } else { @@ -162,17 +165,18 @@ Curve.prototype.genKeyPair = async function () { return new KeyPair(this.curve, keyPair); }; - function get(oid_or_name) { var name; - if (enums.curve[oid_or_name]) { - name = enums.write(enums.curve, oid_or_name); + if (OID.prototype.isPrototypeOf(oid_or_name) && + enums.curve[oid_or_name.toHex()]) { + name = enums.write(enums.curve, oid_or_name.toHex()); // by curve OID + return new Curve(name, curves[name]); + } else if (enums.curve[oid_or_name]) { + name = enums.write(enums.curve, oid_or_name); // by curve name + return new Curve(name, curves[name]); + } else if (enums.curve[util.hexstrdump(oid_or_name)]) { + name = enums.write(enums.curve, util.hexstrdump(oid_or_name)); // by oid string return new Curve(name, curves[name]); - } - for (name in curves) { - if (curves[name].oid === oid_or_name) { - return new Curve(name, curves[name]); - } } 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 = { Curve: Curve, + curves: curves, + webCurves: webCurves, + nodeCurves: nodeCurves, + getPreferredHashAlgorithm: getPreferredHashAlgorithm, generate: generate, 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( - { - name: algorithm === "ECDH" ? "ECDH" : "ECDSA", - namedCurve: webCurves[name] - }, - true, - algorithm === "ECDH" ? ["deriveKey", "deriveBits"] : ["sign", "verify"] + { name: "ECDSA", namedCurve: webCurves[name] }, true, ["sign", "verify"] ); var privateKey = await webCrypto.exportKey("jwk", webCryptoKey.privateKey); @@ -218,15 +226,15 @@ async function webGenKeyPair(name, algorithm) { return { pub: { - x: base64.decode(publicKey.x, 'base64url'), - y: base64.decode(publicKey.y, 'base64url') + x: base64.decode(publicKey.x, true), + y: base64.decode(publicKey.y, true) }, - priv: base64.decode(privateKey.d, 'base64url') + priv: base64.decode(privateKey.d, true) }; } async function nodeGenKeyPair(name) { - var ecdh = nodeCrypto.createECDH(name === "secp256r1" ? "prime256v1" : name); + var ecdh = nodeCrypto.createECDH(nodeCurves[name]); await ecdh.generateKeys(); return { diff --git a/src/crypto/public_key/elliptic/ecdsa.js b/src/crypto/public_key/elliptic/ecdsa.js index 23c60860..0493c55d 100644 --- a/src/crypto/public_key/elliptic/ecdsa.js +++ b/src/crypto/public_key/elliptic/ecdsa.js @@ -25,28 +25,9 @@ 'use strict'; -import BN from 'bn.js'; -import ASN1 from 'asn1.js'; -import jwkToPem from 'jwk-to-pem'; - +import hash from '../../hash'; import curves from './curves.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 @@ -57,17 +38,9 @@ var ECDSASignature = ASN1.define('ECDSASignature', function() { * @return {{r: BigInteger, s: BigInteger}} Signature of the message */ async function sign(oid, hash_algo, m, d) { - var signature; const curve = curves.get(oid); - hash_algo = hash_algo ? hash_algo : curve.hash; const key = curve.keyFromPrivate(d.toByteArray()); - if (webCrypto && config.use_native && curve.web) { - 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); - } + const signature = await key.sign(m, hash_algo); return { r: new BigInteger(signature.r.toArray()), s: new BigInteger(signature.s.toArray()) @@ -84,168 +57,14 @@ async function sign(oid, hash_algo, m, d) { * @return {Boolean} */ async function verify(oid, hash_algo, signature, m, Q) { - var result; 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()); - if (webCrypto && config.use_native && curve.web) { - result = await webVerify(curve, hash_algo, signature, m, key.keyPair.getPublic()); - } 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; + return key.verify( + m, { r: signature.r.toByteArray(), s: signature.s.toByteArray() }, hash_algo + ); } module.exports = { sign: sign, 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; -} diff --git a/src/crypto/public_key/elliptic/eddsa.js b/src/crypto/public_key/elliptic/eddsa.js index 3309979e..cccb23dd 100644 --- a/src/crypto/public_key/elliptic/eddsa.js +++ b/src/crypto/public_key/elliptic/eddsa.js @@ -2,6 +2,7 @@ 'use strict'; +import hash from '../../hash'; import curves from './curves.js'; import BigInteger from '../jsbn.js'; @@ -14,11 +15,9 @@ import BigInteger from '../jsbn.js'; * @return {{r: BigInteger, s: BigInteger}} Signature of the message */ async function sign(oid, hash_algo, m, d) { - var signature; const curve = curves.get(oid); - hash_algo = hash_algo ? hash_algo : curve.hash; const key = curve.keyFromSecret(d.toByteArray()); - signature = await key.sign(m, hash_algo); + const signature = await key.sign(m, hash_algo); return { r: new BigInteger(signature.Rencoded()), s: new BigInteger(signature.Sencoded()) @@ -35,12 +34,10 @@ async function sign(oid, hash_algo, m, d) { * @return {Boolean} */ async function verify(oid, hash_algo, signature, m, Q) { - var result; 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()); 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 ); } diff --git a/src/crypto/public_key/elliptic/index.js b/src/crypto/public_key/elliptic/index.js index f4a5cc47..a171c3cb 100644 --- a/src/crypto/public_key/elliptic/index.js +++ b/src/crypto/public_key/elliptic/index.js @@ -27,7 +27,7 @@ 'use strict'; -import {get, generate} from './curves'; +import {get, generate, getPreferredHashAlgorithm} from './curves'; import ecdsa from './ecdsa'; import eddsa from './eddsa'; import ecdh from './ecdh'; @@ -37,5 +37,6 @@ module.exports = { eddsa: eddsa, ecdh: ecdh, get: get, - generate: generate + generate: generate, + getPreferredHashAlgorithm: getPreferredHashAlgorithm }; diff --git a/src/crypto/public_key/elliptic/key.js b/src/crypto/public_key/elliptic/key.js index f8e5b0e1..22131762 100644 --- a/src/crypto/public_key/elliptic/key.js +++ b/src/crypto/public_key/elliptic/key.js @@ -25,9 +25,28 @@ '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 util from '../../../util'; import enums from '../../../enums'; +import config from '../../../config'; +import base64 from '../../../encoding/base64'; + +const webCrypto = util.getWebCrypto(); +const webCurves = curves.webCurves; +const nodeCrypto = util.getNodeCrypto(); +const nodeCurves = curves.nodeCurves; + +var ECDSASignature = ASN1.define('ECDSASignature', function() { + this.seq().obj( + this.key('r').int(), + this.key('s').int() + ); +}); function KeyPair(curve, options) { this.curve = curve; @@ -35,20 +54,32 @@ function KeyPair(curve, 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') { message = util.str2Uint8Array(message); } - const digest = (typeof hash_algo === 'undefined') ? message : hash.digest(hash_algo, message); - return this.keyPair.sign(digest); + if (webCrypto && config.use_native && this.curve.web) { + 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') { message = util.str2Uint8Array(message); } - const digest = (typeof hash_algo === 'undefined') ? message : hash.digest(hash_algo, message); - return this.keyPair.verify(digest, signature); + if (webCrypto && config.use_native && this.curve.web) { + 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) { @@ -81,3 +112,131 @@ KeyPair.prototype.isValid = function () { module.exports = { KeyPair: KeyPair }; + + +////////////////////////// +// // +// Helper functions // +// // +////////////////////////// + + +async function webSign(curve, hash_algo, message, keyPair) { + var l = curve.payloadSize; + const key = await webCrypto.importKey( + "jwk", + { + "kty": "EC", + "crv": webCurves[curve.name], + "x": base64.encode(new Uint8Array(keyPair.getPublic().getX().toArray('be', l)), true), + "y": base64.encode(new Uint8Array(keyPair.getPublic().getY().toArray('be', l)), true), + "d": base64.encode(new Uint8Array(keyPair.getPrivate().toArray('be', l)), true), + "use": "sig", + "kid": "ECDSA Private Key" + }, + { + "name": "ECDSA", + "namedCurve": webCurves[curve.name], + "hash": { name: enums.read(enums.webHash, curve.hash) } + }, + false, + ["sign"] + ); + + const signature = new Uint8Array(await webCrypto.sign( + { + "name": 'ECDSA', + "namedCurve": webCurves[curve.name], + "hash": { name: enums.read(enums.webHash, hash_algo) } + }, + key, + message + )); + return { + r: signature.slice(0, l), + s: signature.slice(l, 2 * l) + }; +} + +async function webVerify(curve, hash_algo, {r, s}, message, publicKey) { + var l = curve.payloadSize; + r = (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; +} diff --git a/src/encoding/base64.js b/src/encoding/base64.js index 403a73d5..e4d22cfe 100644 --- a/src/encoding/base64.js +++ b/src/encoding/base64.js @@ -17,20 +17,21 @@ 'use strict'; -var b64s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; -var b64u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; +var b64s = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; // Standard radix-64 +var b64u = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; // URL-safe radix-64 /** * Convert binary array to radix-64 * @param {Uint8Array} t Uint8Array to convert + * @param {bool} u if true, output is URL-safe * @returns {string} radix-64 version of input string * @static */ -function s2r(t, o, u) { +function s2r(t, u = false) { // TODO check btoa alternative - var b64 = (u === "base64url") ? b64u : b64s; + var b64 = u ? b64u : b64s; var a, c, n; - var r = o ? o : [], + var r = [], l = 0, s = 0; var tl = t.length; @@ -67,33 +68,30 @@ function s2r(t, o, u) { if ((l % 60) === 0 && !u) { r.push("\n"); } - if (u !== 'base64url') { + if (!u) { r.push('='); l += 1; } } - if (s === 1 && u !== 'base64url') { + if (s === 1 && !u) { if ((l % 60) === 0 && !u) { r.push("\n"); } r.push('='); } - if (o) - { - return; - } return r.join(''); } /** * Convert radix-64 to binary array * @param {String} t radix-64 string to convert + * @param {bool} u if true, input is interpreted as URL-safe * @returns {Uint8Array} binary array version of input string * @static */ function r2s(t, u) { // TODO check atob alternative - var b64 = (u === "base64url") ? b64u : b64s; + var b64 = u ? b64u : b64s; var c, n; var r = [], s = 0, diff --git a/src/enums.js b/src/enums.js index c4bf3651..66929a01 100644 --- a/src/enums.js +++ b/src/enums.js @@ -16,25 +16,40 @@ export default { "secp256r1": "p256", "prime256v1": "p256", "1.2.840.10045.3.1.7": "p256", + "2a8648ce3d030107": "p256", + "2A8648CE3D030107": "p256", "p384": "p384", "P-384": "p384", "secp384r1": "p384", "1.3.132.0.34": "p384", + "2b81040022": "p384", + "2B81040022": "p384", "p521": "p521", "P-521": "p521", "secp521r1": "p521", "1.3.132.0.35": "p521", + "2b81040023": "p521", + "2B81040023": "p521", "secp256k1": "secp256k1", "1.3.132.0.10": "secp256k1", + "2b8104000a": "secp256k1", + "2B8104000A": "secp256k1", "ed25519": "ed25519", + "Ed25519": "ed25519", "1.3.6.1.4.1.11591.15.1": "ed25519", + "2b06010401da470f01": "ed25519", + "2B06010401DA470F01": "ed25519", + "cv25519": "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 diff --git a/src/packet/packetlist.js b/src/packet/packetlist.js index 7f096fba..752cc1b5 100644 --- a/src/packet/packetlist.js +++ b/src/packet/packetlist.js @@ -145,7 +145,7 @@ Packetlist.prototype.filterByTag = function () { */ Packetlist.prototype.forEach = function (callback) { for (var i = 0; i < this.length; i++) { - callback(this[i]); + callback(this[i], i, this); } }; @@ -163,6 +163,20 @@ Packetlist.prototype.map = function (callback) { return packetArray; }; +/** +* Executes the callback function once for each element +* until it finds one where callback returns a truthy value +*/ +Packetlist.prototype.some = async function (callback) { + for (var i = 0; i < this.length; i++) { + // eslint-disable-next-line no-await-in-loop + if (await callback(this[i], i, this)) { + return true; + } + } + return false; +}; + /** * Traverses packet tree and returns first matching packet * @param {module:enums.packet} type The packet type diff --git a/src/type/oid.js b/src/type/oid.js index e6cbad0b..be633321 100644 --- a/src/type/oid.js +++ b/src/type/oid.js @@ -68,6 +68,14 @@ OID.prototype.write = function () { String.fromCharCode(this.oid.length)+this.oid); }; +/** + * Serialize an OID object as a hex string + * @return {string} String with the hex value of the OID + */ +OID.prototype.toHex = function() { + return util.hexstrdump(this.oid); +}; + OID.fromClone = function (clone) { var oid = new OID(clone.oid); return oid; diff --git a/test/crypto/elliptic.js b/test/crypto/elliptic.js index 6a7b63a5..48a84fc5 100644 --- a/test/crypto/elliptic.js +++ b/test/crypto/elliptic.js @@ -175,22 +175,22 @@ describe('Elliptic Curve Cryptography', function () { it('Signature verification', function (done) { var curve = elliptic_curves.get('p256'); var key = curve.keyFromPublic(signature_data.pub); - expect(key.verify(signature_data.message, signature_data.signature, 8)).to.be.true; + expect(key.verify(signature_data.message, signature_data.signature, 8)).to.eventually.be.true; done(); }); it('Invalid signature', function (done) { var curve = elliptic_curves.get('p256'); var key = curve.keyFromPublic(key_data.p256.pub); - expect(key.verify(signature_data.message, signature_data.signature, 8)).to.be.false; + expect(key.verify(signature_data.message, signature_data.signature, 8)).to.eventually.be.false; done(); }); - it('Signature generation', function (done) { + it('Signature generation', function () { var curve = elliptic_curves.get('p256'); var key = curve.keyFromPrivate(key_data.p256.priv); - var signature = key.sign(signature_data.message, 8); - key = curve.keyFromPublic(key_data.p256.pub); - expect(key.verify(signature_data.message, signature, 8)).to.be.true; - done(); + return key.sign(signature_data.message, 8).then(signature => { + key = curve.keyFromPublic(key_data.p256.pub); + expect(key.verify(signature_data.message, signature, 8)).to.eventually.be.true; + }); }); it('Shared secret generation', function (done) { var curve = elliptic_curves.get('p256'); diff --git a/test/general/ecc.js b/test/general/ecc.js index 92bb7c52..0213b349 100644 --- a/test/general/ecc.js +++ b/test/general/ecc.js @@ -200,7 +200,7 @@ describe('Elliptic Curve Cryptography', function () { var romeo = load_priv_key('romeo'); var juliet = load_pub_key('juliet'); expect(romeo.decrypt(data['romeo'].pass)).to.be.true; - openpgp.encrypt( + return openpgp.encrypt( {publicKeys: [juliet], privateKeys: [romeo], data: data.romeo.message + "\n"} ).then(function (encrypted) { var message = openpgp.message.readArmored(encrypted.data);