Replace indutny-elliptic lib with noble-curves

Unlike elliptic, noble-curves targets algorithmic constant time, and
it relies on the native BigInts when available, resulting in a smaller bundle
and improved performance.

Also, expand testing of fallback elliptic implementation.
This commit is contained in:
larabr 2023-10-06 16:14:59 +02:00
parent 01df8ca889
commit a9fae5ff12
10 changed files with 204 additions and 345 deletions

97
package-lock.json generated
View File

@ -13,7 +13,6 @@
}, },
"devDependencies": { "devDependencies": {
"@openpgp/asmcrypto.js": "^3.0.0", "@openpgp/asmcrypto.js": "^3.0.0",
"@openpgp/elliptic": "^6.5.1",
"@openpgp/jsdoc": "^3.6.11", "@openpgp/jsdoc": "^3.6.11",
"@openpgp/noble-curves": "^1.2.1-0", "@openpgp/noble-curves": "^1.2.1-0",
"@openpgp/noble-hashes": "^1.3.3-0", "@openpgp/noble-hashes": "^1.3.3-0",
@ -572,21 +571,6 @@
"integrity": "sha512-X/DPYy7uHe+dlY2Botb99uXwb2kXR6HTv0hQOnnI0TVEqOIMQyzCDWAzlX00AacsYryDAphuOndg6mk6wtJCNg==", "integrity": "sha512-X/DPYy7uHe+dlY2Botb99uXwb2kXR6HTv0hQOnnI0TVEqOIMQyzCDWAzlX00AacsYryDAphuOndg6mk6wtJCNg==",
"dev": true "dev": true
}, },
"node_modules/@openpgp/elliptic": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/@openpgp/elliptic/-/elliptic-6.5.1.tgz",
"integrity": "sha512-VR20QWndMXoZTAzCUqauDT4dLrHO4RTnyVV3szuRHllQSU/JZToLvWtFxpEQth4XWyqlxHPwq7tljE5V97+n1g==",
"dev": true,
"dependencies": {
"bn.js": "^4.4.0",
"brorand": "^1.0.1",
"hash.js": "^1.0.0",
"hmac-drbg": "^1.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0",
"minimalistic-crypto-utils": "^1.0.0"
}
},
"node_modules/@openpgp/jsdoc": { "node_modules/@openpgp/jsdoc": {
"version": "3.6.11", "version": "3.6.11",
"resolved": "https://registry.npmjs.org/@openpgp/jsdoc/-/jsdoc-3.6.11.tgz", "resolved": "https://registry.npmjs.org/@openpgp/jsdoc/-/jsdoc-3.6.11.tgz",
@ -1489,12 +1473,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/brorand": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
"dev": true
},
"node_modules/browser-stdout": { "node_modules/browser-stdout": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
@ -3465,16 +3443,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/hash.js": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz",
"integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==",
"dev": true,
"dependencies": {
"inherits": "^2.0.3",
"minimalistic-assert": "^1.0.0"
}
},
"node_modules/hasha": { "node_modules/hasha": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz", "resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz",
@ -3496,17 +3464,6 @@
"he": "bin/he" "he": "bin/he"
} }
}, },
"node_modules/hmac-drbg": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
"dev": true,
"dependencies": {
"hash.js": "^1.0.3",
"minimalistic-assert": "^1.0.0",
"minimalistic-crypto-utils": "^1.0.1"
}
},
"node_modules/hosted-git-info": { "node_modules/hosted-git-info": {
"version": "2.8.9", "version": "2.8.9",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
@ -4975,12 +4932,6 @@
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz",
"integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M=" "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M="
}, },
"node_modules/minimalistic-crypto-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
"dev": true
},
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@ -7993,21 +7944,6 @@
"integrity": "sha512-X/DPYy7uHe+dlY2Botb99uXwb2kXR6HTv0hQOnnI0TVEqOIMQyzCDWAzlX00AacsYryDAphuOndg6mk6wtJCNg==", "integrity": "sha512-X/DPYy7uHe+dlY2Botb99uXwb2kXR6HTv0hQOnnI0TVEqOIMQyzCDWAzlX00AacsYryDAphuOndg6mk6wtJCNg==",
"dev": true "dev": true
}, },
"@openpgp/elliptic": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/@openpgp/elliptic/-/elliptic-6.5.1.tgz",
"integrity": "sha512-VR20QWndMXoZTAzCUqauDT4dLrHO4RTnyVV3szuRHllQSU/JZToLvWtFxpEQth4XWyqlxHPwq7tljE5V97+n1g==",
"dev": true,
"requires": {
"bn.js": "^4.4.0",
"brorand": "^1.0.1",
"hash.js": "^1.0.0",
"hmac-drbg": "^1.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0",
"minimalistic-crypto-utils": "^1.0.0"
}
},
"@openpgp/jsdoc": { "@openpgp/jsdoc": {
"version": "3.6.11", "version": "3.6.11",
"resolved": "https://registry.npmjs.org/@openpgp/jsdoc/-/jsdoc-3.6.11.tgz", "resolved": "https://registry.npmjs.org/@openpgp/jsdoc/-/jsdoc-3.6.11.tgz",
@ -8699,12 +8635,6 @@
"fill-range": "^7.0.1" "fill-range": "^7.0.1"
} }
}, },
"brorand": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
"dev": true
},
"browser-stdout": { "browser-stdout": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
@ -10238,16 +10168,6 @@
"has-symbols": "^1.0.2" "has-symbols": "^1.0.2"
} }
}, },
"hash.js": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz",
"integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==",
"dev": true,
"requires": {
"inherits": "^2.0.3",
"minimalistic-assert": "^1.0.0"
}
},
"hasha": { "hasha": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz", "resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz",
@ -10263,17 +10183,6 @@
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true "dev": true
}, },
"hmac-drbg": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
"integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
"dev": true,
"requires": {
"hash.js": "^1.0.3",
"minimalistic-assert": "^1.0.0",
"minimalistic-crypto-utils": "^1.0.1"
}
},
"hosted-git-info": { "hosted-git-info": {
"version": "2.8.9", "version": "2.8.9",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
@ -11425,12 +11334,6 @@
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz",
"integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M=" "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M="
}, },
"minimalistic-crypto-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
"integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
"dev": true
},
"minimatch": { "minimatch": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",

View File

@ -64,7 +64,6 @@
"devDependencies": { "devDependencies": {
"@openpgp/asmcrypto.js": "^3.0.0", "@openpgp/asmcrypto.js": "^3.0.0",
"@openpgp/noble-curves": "^1.2.1-0", "@openpgp/noble-curves": "^1.2.1-0",
"@openpgp/elliptic": "^6.5.1",
"@openpgp/jsdoc": "^3.6.11", "@openpgp/jsdoc": "^3.6.11",
"@openpgp/noble-hashes": "^1.3.3-0", "@openpgp/noble-hashes": "^1.3.3-0",
"@openpgp/seek-bzip": "^1.0.5-git", "@openpgp/seek-bzip": "^1.0.5-git",

View File

@ -21,7 +21,7 @@
*/ */
import nacl from '@openpgp/tweetnacl/nacl-fast-light'; import nacl from '@openpgp/tweetnacl/nacl-fast-light';
import { CurveWithOID, jwkToRawPublic, rawPublicToJWK, privateToJWK, validateStandardParams } from './oid_curves'; import { CurveWithOID, jwkToRawPublic, rawPublicToJWK, privateToJWK, validateStandardParams, getNobleCurve } from './oid_curves';
import * as aesKW from '../../aes_kw'; import * as aesKW from '../../aes_kw';
import { getRandomBytes } from '../../random'; import { getRandomBytes } from '../../random';
import hash from '../../hash'; import hash from '../../hash';
@ -29,7 +29,6 @@ import enums from '../../../enums';
import util from '../../../util'; import util from '../../../util';
import { b64ToUint8Array } from '../../../encoding/base64'; import { b64ToUint8Array } from '../../../encoding/base64';
import * as pkcs5 from '../../pkcs5'; import * as pkcs5 from '../../pkcs5';
import { keyFromPublic, keyFromPrivate, getIndutnyCurve } from './indutnyKey';
import getCipher from '../../cipher/getCipher'; import getCipher from '../../cipher/getCipher';
const webCrypto = util.getWebCrypto(); const webCrypto = util.getWebCrypto();
@ -105,13 +104,16 @@ async function genPublicEphemeralKey(curve, Q) {
return await webPublicEphemeralKey(curve, Q); return await webPublicEphemeralKey(curve, Q);
} catch (err) { } catch (err) {
util.printDebugError(err); util.printDebugError(err);
return jsPublicEphemeralKey(curve, Q);
} }
} }
break; break;
case 'node': case 'node':
return nodePublicEphemeralKey(curve, Q); return nodePublicEphemeralKey(curve, Q);
default: {
return jsPublicEphemeralKey(curve, Q);
}
} }
return ellipticPublicEphemeralKey(curve, Q);
} }
/** /**
@ -165,13 +167,16 @@ async function genPrivateEphemeralKey(curve, V, Q, d) {
return await webPrivateEphemeralKey(curve, V, Q, d); return await webPrivateEphemeralKey(curve, V, Q, d);
} catch (err) { } catch (err) {
util.printDebugError(err); util.printDebugError(err);
return jsPrivateEphemeralKey(curve, V, d);
} }
} }
break; break;
case 'node': case 'node':
return nodePrivateEphemeralKey(curve, V, d); return nodePrivateEphemeralKey(curve, V, d);
default: {
return jsPrivateEphemeralKey(curve, V, d);
}
} }
return ellipticPrivateEphemeralKey(curve, V, d);
} }
/** /**
@ -205,6 +210,24 @@ export async function decrypt(oid, kdfParams, V, C, Q, d, fingerprint) {
throw err; throw err;
} }
function jsPrivateEphemeralKey(curve, V, d) {
const nobleCurve = getNobleCurve(curve.name);
// The output includes parity byte
const sharedSecretWithParity = nobleCurve.getSharedSecret(d, V);
const sharedKey = sharedSecretWithParity.subarray(1);
return { secretKey: d, sharedKey };
}
async function jsPublicEphemeralKey(curve, Q) {
const nobleCurve = getNobleCurve(curve.name);
const { publicKey: V, privateKey: v } = await curve.genKeyPair();
// The output includes parity byte
const sharedSecretWithParity = nobleCurve.getSharedSecret(v, Q);
const sharedKey = sharedSecretWithParity.subarray(1);
return { publicKey: V, sharedKey };
}
/** /**
* Generate ECDHE secret from private key and public part of ephemeral key using webCrypto * Generate ECDHE secret from private key and public part of ephemeral key using webCrypto
* *
@ -306,46 +329,6 @@ async function webPublicEphemeralKey(curve, Q) {
return { publicKey, sharedKey }; return { publicKey, sharedKey };
} }
/**
* Generate ECDHE secret from private key and public part of ephemeral key using indutny/elliptic
*
* @param {CurveWithOID} curve - Elliptic curve object
* @param {Uint8Array} V - Public part of ephemeral key
* @param {Uint8Array} d - Recipient private key
* @returns {Promise<{secretKey: Uint8Array, sharedKey: Uint8Array}>}
* @async
*/
async function ellipticPrivateEphemeralKey(curve, V, 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.getPublic());
const len = indutnyCurve.curve.p.byteLength();
const sharedKey = S.toArrayLike(Uint8Array, 'be', len);
return { secretKey, sharedKey };
}
/**
* Generate ECDHE ephemeral key and secret from public key using indutny/elliptic
*
* @param {CurveWithOID} curve - Elliptic curve object
* @param {Uint8Array} Q - Recipient public key
* @returns {Promise<{publicKey: Uint8Array, sharedKey: Uint8Array}>}
* @async
*/
async function ellipticPublicEphemeralKey(curve, Q) {
const indutnyCurve = await getIndutnyCurve(curve.name);
const v = await curve.genKeyPair();
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 };
}
/** /**
* Generate ECDHE secret from private key and public part of ephemeral key using nodeCrypto * Generate ECDHE secret from private key and public part of ephemeral key using nodeCrypto
* *

View File

@ -24,8 +24,7 @@ import enums from '../../../enums';
import util from '../../../util'; import util from '../../../util';
import { getRandomBytes } from '../../random'; import { getRandomBytes } from '../../random';
import hash from '../../hash'; import hash from '../../hash';
import { CurveWithOID, webCurves, privateToJWK, rawPublicToJWK, validateStandardParams } from './oid_curves'; import { CurveWithOID, webCurves, privateToJWK, rawPublicToJWK, validateStandardParams, getNobleCurve } from './oid_curves';
import { getIndutnyCurve, keyFromPrivate, keyFromPublic } from './indutnyKey';
const webCrypto = util.getWebCrypto(); const webCrypto = util.getWebCrypto();
const nodeCrypto = util.getNodeCrypto(); const nodeCrypto = util.getNodeCrypto();
@ -74,7 +73,14 @@ export async function sign(oid, hashAlgo, message, publicKey, privateKey, hashed
} }
} }
} }
return ellipticSign(curve, hashed, privateKey);
const nobleCurve = getNobleCurve(curve.name);
// lowS: non-canonical sig: https://stackoverflow.com/questions/74338846/ecdsa-signature-verification-mismatch
const signature = nobleCurve.sign(hashed, privateKey, { lowS: false });
return {
r: signature.r.toUint8Array('be', curve.payloadSize),
s: signature.s.toUint8Array('be', curve.payloadSize)
};
} }
/** /**
@ -111,8 +117,10 @@ export async function verify(oid, hashAlgo, signature, message, publicKey, hashe
return nodeVerify(curve, hashAlgo, signature, message, publicKey); return nodeVerify(curve, hashAlgo, signature, message, publicKey);
} }
} }
const digest = (typeof hashAlgo === 'undefined') ? message : hashed;
return ellipticVerify(curve, signature, digest, publicKey); const nobleCurve = getNobleCurve(curve.name);
// lowS: non-canonical sig: https://stackoverflow.com/questions/74338846/ecdsa-signature-verification-mismatch
return nobleCurve.verify(util.concatUint8Array([signature.r, signature.s]), hashed, publicKey, { lowS: false });
} }
/** /**
@ -156,23 +164,6 @@ export async function validateParams(oid, Q, d) {
// Helper functions // // 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, hashAlgo, message, keyPair) { async function webSign(curve, hashAlgo, message, keyPair) {
const len = curve.payloadSize; const len = curve.payloadSize;
const jwk = privateToJWK(curve.payloadSize, webCurves[curve.name], keyPair.publicKey, keyPair.privateKey); const jwk = privateToJWK(curve.payloadSize, webCurves[curve.name], keyPair.publicKey, keyPair.privateKey);

View File

@ -1,44 +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 curve from indutny/elliptic library
* @module crypto/public_key/elliptic/indutnyKey
*/
import config from '../../../config';
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;
}
export async function getIndutnyCurve(name) {
if (!config.useIndutnyElliptic) {
throw new Error('This curve is only supported in the full build of OpenPGP.js');
}
const { default: elliptic } = await import('@openpgp/elliptic');
return new elliptic.ec(name);
}

View File

@ -19,37 +19,58 @@
* @fileoverview Wrapper of an instance of an Elliptic Curve * @fileoverview Wrapper of an instance of an Elliptic Curve
* @module crypto/public_key/elliptic/curve * @module crypto/public_key/elliptic/curve
*/ */
import { BigInteger } from '@openpgp/noble-hashes/biginteger';
import nacl from '@openpgp/tweetnacl/nacl-fast-light'; import nacl from '@openpgp/tweetnacl/nacl-fast-light';
import { p256 } from '@openpgp/noble-curves/p256';
import { p384 } from '@openpgp/noble-curves/p384';
import { p521 } from '@openpgp/noble-curves/p521';
import { brainpoolP256r1 } from '@openpgp/noble-curves/brainpoolP256r1';
import { brainpoolP384r1 } from '@openpgp/noble-curves/brainpoolP384r1';
import { brainpoolP512r1 } from '@openpgp/noble-curves/brainpoolP512r1';
import { secp256k1 } from '@openpgp/noble-curves/secp256k1';
import { getRandomBytes } from '../../random'; import { getRandomBytes } from '../../random';
import enums from '../../../enums'; import enums from '../../../enums';
import util from '../../../util'; import util from '../../../util';
import { uint8ArrayToB64, b64ToUint8Array } from '../../../encoding/base64'; import { uint8ArrayToB64, b64ToUint8Array } from '../../../encoding/base64';
import OID from '../../../type/oid'; import OID from '../../../type/oid';
import { keyFromPublic, keyFromPrivate, getIndutnyCurve } from './indutnyKey';
import { UnsupportedError } from '../../../packet/packet'; import { UnsupportedError } from '../../../packet/packet';
const webCrypto = util.getWebCrypto(); const webCrypto = util.getWebCrypto();
const nodeCrypto = util.getNodeCrypto(); const nodeCrypto = util.getNodeCrypto();
const webCurves = { const webCurves = {
'p256': 'P-256', [enums.curve.p256]: 'P-256',
'p384': 'P-384', [enums.curve.p384]: 'P-384',
'p521': 'P-521' [enums.curve.p521]: 'P-521'
}; };
const knownCurves = nodeCrypto ? nodeCrypto.getCurves() : []; const knownCurves = nodeCrypto ? nodeCrypto.getCurves() : [];
const nodeCurves = nodeCrypto ? { const nodeCurves = nodeCrypto ? {
secp256k1: knownCurves.includes('secp256k1') ? 'secp256k1' : undefined, [enums.curve.secp256k1]: knownCurves.includes('secp256k1') ? 'secp256k1' : undefined,
p256: knownCurves.includes('prime256v1') ? 'prime256v1' : undefined, [enums.curve.p256]: knownCurves.includes('prime256v1') ? 'prime256v1' : undefined,
p384: knownCurves.includes('secp384r1') ? 'secp384r1' : undefined, [enums.curve.p384]: knownCurves.includes('secp384r1') ? 'secp384r1' : undefined,
p521: knownCurves.includes('secp521r1') ? 'secp521r1' : undefined, [enums.curve.p521]: knownCurves.includes('secp521r1') ? 'secp521r1' : undefined,
ed25519: knownCurves.includes('ED25519') ? 'ED25519' : undefined, [enums.curve.ed25519Legacy]: knownCurves.includes('ED25519') ? 'ED25519' : undefined,
curve25519: knownCurves.includes('X25519') ? 'X25519' : undefined, [enums.curve.curve25519Legacy]: knownCurves.includes('X25519') ? 'X25519' : undefined,
brainpoolP256r1: knownCurves.includes('brainpoolP256r1') ? 'brainpoolP256r1' : undefined, [enums.curve.brainpoolP256r1]: knownCurves.includes('brainpoolP256r1') ? 'brainpoolP256r1' : undefined,
brainpoolP384r1: knownCurves.includes('brainpoolP384r1') ? 'brainpoolP384r1' : undefined, [enums.curve.brainpoolP384r1]: knownCurves.includes('brainpoolP384r1') ? 'brainpoolP384r1' : undefined,
brainpoolP512r1: knownCurves.includes('brainpoolP512r1') ? 'brainpoolP512r1' : undefined [enums.curve.brainpoolP512r1]: knownCurves.includes('brainpoolP512r1') ? 'brainpoolP512r1' : undefined
} : {}; } : {};
const nobleCurvess = {
[enums.curve.p256]: p256,
[enums.curve.p384]: p384,
[enums.curve.p521]: p521,
[enums.curve.secp256k1]: secp256k1,
[enums.curve.brainpoolP256r1]: brainpoolP256r1,
[enums.curve.brainpoolP384r1]: brainpoolP384r1,
[enums.curve.brainpoolP512r1]: brainpoolP512r1
};
export const getNobleCurve = curveName => {
const curve = nobleCurvess[curveName];
if (!curve) throw new Error('Unsupported curve');
return curve;
};
const curves = { const curves = {
p256: { p256: {
oid: [0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07], oid: [0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07],
@ -169,14 +190,13 @@ class CurveWithOID {
} }
async genKeyPair() { async genKeyPair() {
let keyPair;
switch (this.type) { switch (this.type) {
case 'web': case 'web':
try { try {
return await webGenKeyPair(this.name); return await webGenKeyPair(this.name);
} catch (err) { } catch (err) {
util.printDebugError('Browser did not support generating ec key ' + err.message); util.printDebugError('Browser did not support generating ec key ' + err.message);
break; return jsGenKeyPair(this.name);
} }
case 'node': case 'node':
return nodeGenKeyPair(this.name); return nodeGenKeyPair(this.name);
@ -185,8 +205,8 @@ class CurveWithOID {
privateKey[0] = (privateKey[0] & 127) | 64; privateKey[0] = (privateKey[0] & 127) | 64;
privateKey[31] &= 248; privateKey[31] &= 248;
const secretKey = privateKey.slice().reverse(); const secretKey = privateKey.slice().reverse();
keyPair = nacl.box.keyPair.fromSecretKey(secretKey); const { publicKey: rawPublicKey } = nacl.box.keyPair.fromSecretKey(secretKey);
const publicKey = util.concatUint8Array([new Uint8Array([0x40]), keyPair.publicKey]); const publicKey = util.concatUint8Array([new Uint8Array([0x40]), rawPublicKey]);
return { publicKey, privateKey }; return { publicKey, privateKey };
} }
case 'ed25519Legacy': { case 'ed25519Legacy': {
@ -195,26 +215,23 @@ class CurveWithOID {
const publicKey = util.concatUint8Array([new Uint8Array([0x40]), keyPair.publicKey]); const publicKey = util.concatUint8Array([new Uint8Array([0x40]), keyPair.publicKey]);
return { publicKey, privateKey }; return { publicKey, privateKey };
} }
default: {
return jsGenKeyPair(this.name);
}
} }
const indutnyCurve = await getIndutnyCurve(this.name);
keyPair = await indutnyCurve.genKeyPair({
entropy: util.uint8ArrayToString(getRandomBytes(32))
});
return { publicKey: new Uint8Array(keyPair.getPublic('array', false)), privateKey: keyPair.getPrivate().toArrayLike(Uint8Array) };
} }
} }
async function generate(curve) { async function generate(curveName) {
curve = new CurveWithOID(curve); const curve = new CurveWithOID(curveName);
const { oid, hash, cipher } = curve;
const keyPair = await curve.genKeyPair(); const keyPair = await curve.genKeyPair();
const Q = BigInteger.new(keyPair.publicKey).toUint8Array();
const secret = BigInteger.new(keyPair.privateKey).toUint8Array('be', curve.payloadSize);
return { return {
oid: curve.oid, oid,
Q, Q: keyPair.publicKey,
secret, secret: util.leftPad(keyPair.privateKey, curve.payloadSize),
hash: curve.hash, hash,
cipher: curve.cipher cipher
}; };
} }
@ -269,20 +286,13 @@ async function validateStandardParams(algo, oid, Q, d) {
return true; return true;
} }
const curve = await getIndutnyCurve(curveName); const nobleCurve = getNobleCurve(enums.write(enums.curve, oid.toHex()));
try { /*
// Parse Q and check that it is on the curve but not at infinity
Q = keyFromPublic(curve, Q).getPublic();
} catch (validationErrors) {
return false;
}
/**
* Re-derive public point Q' = dG from private key * Re-derive public point Q' = dG from private key
* Expect Q == Q' * Expect Q == Q'
*/ */
const dG = keyFromPrivate(curve, d).getPublic(); const dG = nobleCurve.getPublicKey(d, false);
if (!dG.eq(Q)) { if (!util.equalsUint8Array(dG, Q)) {
return false; return false;
} }
@ -298,7 +308,12 @@ export {
// Helper functions // // Helper functions //
// // // //
////////////////////////// //////////////////////////
function jsGenKeyPair(name) {
const nobleCurve = getNobleCurve(name);
const privateKey = nobleCurve.utils.randomPrivateKey();
const publicKey = nobleCurve.getPublicKey(privateKey, false);
return { publicKey, privateKey };
}
async function webGenKeyPair(name) { async function webGenKeyPair(name) {
// Note: keys generated with ECDSA and ECDH are structurally equivalent // Note: keys generated with ECDSA and ECDH are structurally equivalent

View File

@ -77,7 +77,7 @@ export default () => describe('ECDH key exchange @lightweight', function () {
} }
expect(decrypt_message( expect(decrypt_message(
'secp256k1', 2, 7, [], [], [], [], [] 'secp256k1', 2, 7, [], [], [], [], []
)).to.be.rejectedWith(Error, /Private key is not valid for specified curve|Unknown point format/).notify(done); )).to.be.rejectedWith(Error, /Private key is not valid for specified curve|second arg must be public key/).notify(done);
}); });
it('Invalid elliptic public key', function (done) { it('Invalid elliptic public key', function (done) {
if (!openpgp.config.useIndutnyElliptic && !util.getNodeCrypto()) { if (!openpgp.config.useIndutnyElliptic && !util.getNodeCrypto()) {
@ -85,7 +85,7 @@ export default () => describe('ECDH key exchange @lightweight', function () {
} }
expect(decrypt_message( expect(decrypt_message(
'secp256k1', 2, 7, secp256k1_value, secp256k1_point, secp256k1_invalid_point, secp256k1_data, [] 'secp256k1', 2, 7, secp256k1_value, secp256k1_point, secp256k1_invalid_point, secp256k1_data, []
)).to.be.rejectedWith(Error, /Public key is not valid for specified curve|Failed to translate Buffer to a EC_POINT|Invalid elliptic public key/).notify(done); )).to.be.rejectedWith(/Public key is not valid for specified curve|Failed to translate Buffer to a EC_POINT|bad point/).notify(done);
}); });
it('Invalid key data integrity', function (done) { it('Invalid key data integrity', function (done) {
if (!openpgp.config.useIndutnyElliptic && !util.getNodeCrypto()) { if (!openpgp.config.useIndutnyElliptic && !util.getNodeCrypto()) {
@ -93,7 +93,7 @@ export default () => describe('ECDH key exchange @lightweight', function () {
} }
expect(decrypt_message( expect(decrypt_message(
'secp256k1', 2, 7, secp256k1_value, secp256k1_point, secp256k1_point, secp256k1_data, [] 'secp256k1', 2, 7, secp256k1_value, secp256k1_point, secp256k1_point, secp256k1_data, []
)).to.be.rejectedWith(Error, /Key Data Integrity failed/).notify(done); )).to.be.rejectedWith(/Key Data Integrity failed/).notify(done);
}); });
const Q1 = new Uint8Array([ const Q1 = new Uint8Array([
@ -143,9 +143,9 @@ export default () => describe('ECDH key exchange @lightweight', function () {
const oid = new OID(curve.oid); const oid = new OID(curve.oid);
const kdfParams = new KDFParams({ hash: curve.hash, cipher: curve.cipher }); const kdfParams = new KDFParams({ hash: curve.hash, cipher: curve.cipher });
const data = util.stringToUint8Array('test'); const data = util.stringToUint8Array('test');
expect( await expect(
ecdh.encrypt(oid, kdfParams, data, Q1, fingerprint1) ecdh.encrypt(oid, kdfParams, data, Q1.subarray(1), fingerprint1)
).to.be.rejectedWith(Error, /Public key is not valid for specified curve|Failed to translate Buffer to a EC_POINT|Unknown point format/); ).to.be.rejectedWith(/Public key is not valid for specified curve|Failed to translate Buffer to a EC_POINT|second arg must be public key/);
}); });
it('Different keys', async function () { it('Different keys', async function () {
@ -199,8 +199,9 @@ export default () => describe('ECDH key exchange @lightweight', function () {
expect(await ecdhX.decrypt(openpgp.enums.publicKey.x448, ephemeralPublicKey, wrappedKey, K_B, b)).to.deep.equal(data); expect(await ecdhX.decrypt(openpgp.enums.publicKey.x448, ephemeralPublicKey, wrappedKey, K_B, b)).to.deep.equal(data);
}); });
['p256', 'p384', 'p521'].forEach(curveName => { const allCurves = ['secp256k1', 'p256', 'p384', 'p521', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1'];
it(`NIST ${curveName} - Successful exchange`, async function () { allCurves.forEach(curveName => {
it(`${curveName} - Successful exchange`, async function () {
const curve = new elliptic_curves.CurveWithOID(curveName); const curve = new elliptic_curves.CurveWithOID(curveName);
const oid = new OID(curve.oid); const oid = new OID(curve.oid);
const kdfParams = new KDFParams({ hash: curve.hash, cipher: curve.cipher }); const kdfParams = new KDFParams({ hash: curve.hash, cipher: curve.cipher });
@ -236,14 +237,16 @@ export default () => describe('ECDH key exchange @lightweight', function () {
getNodeCryptoStub && getNodeCryptoStub.restore(); getNodeCryptoStub && getNodeCryptoStub.restore();
}; };
['p256', 'p384', 'p521'].forEach(curveName => { allCurves.forEach(curveName => {
it(`NIST ${curveName}`, async function () { it(`${curveName}`, async function () {
const nodeCrypto = util.getNodeCrypto(); const nodeCrypto = util.getNodeCrypto();
const webCrypto = util.getWebCrypto(); const webCrypto = util.getWebCrypto();
if (!nodeCrypto && !webCrypto) { if (!nodeCrypto && !webCrypto) {
this.skip(); this.skip();
} }
const expectNativeWeb = new Set(['p256', 'p384']); // older versions of safari do not implement p521
const curve = new elliptic_curves.CurveWithOID(curveName); const curve = new elliptic_curves.CurveWithOID(curveName);
const oid = new OID(curve.oid); const oid = new OID(curve.oid);
const kdfParams = new KDFParams({ hash: curve.hash, cipher: curve.cipher }); const kdfParams = new KDFParams({ hash: curve.hash, cipher: curve.cipher });
@ -254,9 +257,11 @@ export default () => describe('ECDH key exchange @lightweight', function () {
const nativeDecryptSpy = webCrypto ? sinonSandbox.spy(webCrypto, 'deriveBits') : sinonSandbox.spy(nodeCrypto, 'createECDH'); const nativeDecryptSpy = webCrypto ? sinonSandbox.spy(webCrypto, 'deriveBits') : sinonSandbox.spy(nodeCrypto, 'createECDH');
expect(await ecdh.decrypt(oid, kdfParams, V, C, Q, d, fingerprint1)).to.deep.equal(data); expect(await ecdh.decrypt(oid, kdfParams, V, C, Q, d, fingerprint1)).to.deep.equal(data);
const expectedNativeCallCount = nativeDecryptSpy.callCount;
disableNative(); disableNative();
expect(await ecdh.decrypt(oid, kdfParams, V, C, Q, d, fingerprint1)).to.deep.equal(data); expect(await ecdh.decrypt(oid, kdfParams, V, C, Q, d, fingerprint1)).to.deep.equal(data);
if (curveName !== 'p521') { // safari does not implement p521 in webcrypto expect(nativeDecryptSpy.callCount).to.equal(expectedNativeCallCount); // assert that fallback implementation was called
if (expectNativeWeb.has(curveName)) {
expect(nativeDecryptSpy.calledOnce).to.be.true; expect(nativeDecryptSpy.calledOnce).to.be.true;
} }
}); });

View File

@ -122,6 +122,21 @@ export default () => describe('Elliptic Curve Cryptography @lightweight', functi
getNodeCryptoStub && getNodeCryptoStub.restore(); getNodeCryptoStub && getNodeCryptoStub.restore();
}; };
const testNativeAndFallback = async fn => {
const webCrypto = util.getWebCrypto();
const nodeCrypto = util.getNodeCrypto();
const nativeSpy = webCrypto ? sinonSandbox.spy(webCrypto, 'importKey') : sinonSandbox.spy(nodeCrypto, 'createVerify'); // spy on function used on verification, since that's used by all tests calling `testNativeAndFallback`
// if native not available, fallback will be tested twice (not possible to automatically check native algo availability)
enableNative();
await fn();
const expectedNativeCallCount = nativeSpy.callCount;
disableNative();
await fn();
expect(nativeSpy.callCount).to.equal(expectedNativeCallCount);
enableNative();
};
const verify_signature = async function (oid, hash, r, s, message, pub) { const verify_signature = async function (oid, hash, r, s, message, pub) {
if (util.isString(message)) { if (util.isString(message)) {
message = util.stringToUint8Array(message); message = util.stringToUint8Array(message);
@ -162,99 +177,81 @@ export default () => describe('Elliptic Curve Cryptography @lightweight', functi
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]); ]);
it('Invalid curve oid', function () { it('Invalid curve oid', async function () {
return Promise.all([ await expect(verify_signature(
expect(verify_signature( 'invalid oid', 8, [], [], [], []
'invalid oid', 8, [], [], [], [] )).to.be.rejectedWith(Error, /Unknown curve/);
)).to.be.rejectedWith(Error, /Unknown curve/), await expect(verify_signature(
expect(verify_signature( '\x00', 8, [], [], [], []
'\x00', 8, [], [], [], [] )).to.be.rejectedWith(Error, /Unknown curve/);
)).to.be.rejectedWith(Error, /Unknown curve/)
]);
}); });
it('Invalid public key', async function () { it('secp256k1 - Invalid public key', async function () {
if (!config.useIndutnyElliptic && !util.getNodeCrypto()) { if (!config.useIndutnyElliptic && !util.getNodeCrypto()) {
this.skip(); // webcrypto does not implement secp256k1 this.skip(); // webcrypto does not implement secp256k1: JS fallback tested instead
}
if (util.getNodeCrypto()) {
await expect(verify_signature(
'secp256k1', 8, [], [], [], []
)).to.eventually.be.false;
await expect(verify_signature(
'secp256k1', 8, [], [], [], secp256k1_invalid_point_format
)).to.eventually.be.false;
}
if (config.useIndutnyElliptic) {
disableNative();
await expect(verify_signature(
'secp256k1', 8, [], [], [], []
)).to.be.rejectedWith(Error, /Unknown point format/);
await expect(verify_signature(
'secp256k1', 8, [], [], [], secp256k1_invalid_point_format
)).to.be.rejectedWith(Error, /Unknown point format/);
} }
await expect(verify_signature(
'secp256k1', 8, [], [], [], []
)).to.eventually.be.false;
await expect(verify_signature(
'secp256k1', 8, [], [], [], secp256k1_invalid_point_format
)).to.eventually.be.false;
}); });
it('Invalid point', async function () { it('secp256k1 - Invalid point', async function () {
if (!config.useIndutnyElliptic && !util.getNodeCrypto()) { if (!config.useIndutnyElliptic && !util.getNodeCrypto()) {
this.skip(); this.skip(); // webcrypto does not implement secp256k1: JS fallback tested instead
}
if (util.getNodeCrypto()) {
await expect(verify_signature(
'secp256k1', 8, [], [], [], secp256k1_invalid_point
)).to.eventually.be.false;
}
if (config.useIndutnyElliptic) {
disableNative();
await expect(verify_signature(
'secp256k1', 8, [], [], [], secp256k1_invalid_point
)).to.be.rejectedWith(Error, /Invalid elliptic public key/);
} }
await expect(verify_signature(
'secp256k1', 8, [], [], [], secp256k1_invalid_point
)).to.eventually.be.false;
}); });
it('Invalid signature', function (done) { it('secp256k1 - Invalid signature', function (done) {
if (!config.useIndutnyElliptic && !util.getNodeCrypto()) { if (!config.useIndutnyElliptic && !util.getNodeCrypto()) {
this.skip(); this.skip(); // webcrypto does not implement secp256k1: JS fallback tested instead
} }
expect(verify_signature( expect(verify_signature(
'secp256k1', 8, [], [], [], secp256k1_point 'secp256k1', 8, [], [], [], secp256k1_point
)).to.eventually.be.false.notify(done); )).to.eventually.be.false.notify(done);
}); });
const p384_message = new Uint8Array([ it('P-384 - Valid signature', async function () {
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, const p384_r = new Uint8Array([
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF 0x9D, 0x07, 0xCA, 0xA5, 0x9F, 0xBE, 0xB8, 0x76,
]); 0xA9, 0xB9, 0x66, 0x0F, 0xA0, 0x64, 0x70, 0x5D,
const p384_r = new Uint8Array([ 0xE6, 0x37, 0x40, 0x43, 0xD0, 0x8E, 0x40, 0xA8,
0x9D, 0x07, 0xCA, 0xA5, 0x9F, 0xBE, 0xB8, 0x76, 0x8B, 0x37, 0x83, 0xE7, 0xBC, 0x1C, 0x4C, 0x86,
0xA9, 0xB9, 0x66, 0x0F, 0xA0, 0x64, 0x70, 0x5D, 0xCB, 0x3C, 0xD5, 0x9B, 0x68, 0xF0, 0x65, 0xEB,
0xE6, 0x37, 0x40, 0x43, 0xD0, 0x8E, 0x40, 0xA8, 0x3A, 0xB6, 0xD6, 0xA6, 0xCF, 0x85, 0x3D, 0xA9
0x8B, 0x37, 0x83, 0xE7, 0xBC, 0x1C, 0x4C, 0x86, ]);
0xCB, 0x3C, 0xD5, 0x9B, 0x68, 0xF0, 0x65, 0xEB, const p384_s = new Uint8Array([
0x3A, 0xB6, 0xD6, 0xA6, 0xCF, 0x85, 0x3D, 0xA9 0x32, 0x85, 0x78, 0xCC, 0xEA, 0xC5, 0x22, 0x83,
]); 0x10, 0x73, 0x1C, 0xCF, 0x10, 0x8A, 0x52, 0x11,
const p384_s = new Uint8Array([ 0x8E, 0x49, 0x9E, 0xCF, 0x7E, 0x17, 0x18, 0xC3,
0x32, 0x85, 0x78, 0xCC, 0xEA, 0xC5, 0x22, 0x83, 0x11, 0x11, 0xBC, 0x0F, 0x6D, 0x98, 0xE2, 0x16,
0x10, 0x73, 0x1C, 0xCF, 0x10, 0x8A, 0x52, 0x11, 0x68, 0x58, 0x23, 0x1D, 0x11, 0xEF, 0x3D, 0x21,
0x8E, 0x49, 0x9E, 0xCF, 0x7E, 0x17, 0x18, 0xC3, 0x30, 0x75, 0x24, 0x39, 0x48, 0x89, 0x03, 0xDC
0x11, 0x11, 0xBC, 0x0F, 0x6D, 0x98, 0xE2, 0x16, ]);
0x68, 0x58, 0x23, 0x1D, 0x11, 0xEF, 0x3D, 0x21, const p384_message = new Uint8Array([
0x30, 0x75, 0x24, 0x39, 0x48, 0x89, 0x03, 0xDC 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
]); 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
it('Valid signature', function (done) { ]);
expect(verify_signature('p384', 8, p384_r, p384_s, p384_message, key_data.p384.pub))
.to.eventually.be.true.notify(done); await testNativeAndFallback(
() => expect(verify_signature('p384', 8, p384_r, p384_s, p384_message, key_data.p384.pub)).to.eventually.be.true
);
}); });
it('Sign and verify message', function () { const curves = ['secp256k1' , 'p256', 'p384', 'p521', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1'];
const curve = new elliptic_curves.CurveWithOID('p521'); curves.forEach(curveName => it(`${curveName} - Sign and verify message`, async function () {
return curve.genKeyPair().then(async keyPair => { const curve = new elliptic_curves.CurveWithOID(curveName);
const keyPublic = new Uint8Array(keyPair.publicKey); const { publicKey: keyPublic, privateKey: keyPrivate } = await curve.genKeyPair();
const keyPrivate = new Uint8Array(keyPair.privateKey); const message = new Uint8Array([
const oid = curve.oid; 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
const message = p384_message; 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
return elliptic_curves.ecdsa.sign(oid, 10, message, keyPublic, keyPrivate, await hashMod.digest(10, message)).then(async signature => { ]);
await expect(elliptic_curves.ecdsa.verify(oid, 10, signature, message, keyPublic, await hashMod.digest(10, message))) const messageDigest = await hashMod.digest(openpgp.enums.hash.sha512, message);
.to.eventually.be.true; await testNativeAndFallback(async () => {
}); const signature = await elliptic_curves.ecdsa.sign(curve.oid, openpgp.enums.hash.sha512, message, keyPublic, keyPrivate, messageDigest);
await expect(elliptic_curves.ecdsa.verify(curve.oid, openpgp.enums.hash.sha512, signature, message, keyPublic, messageDigest)).to.eventually.be.true;
}); });
}); }));
}); });
}); });

View File

@ -1,3 +1,5 @@
import util from '../../src/util.js';
const elliptic_data = { const elliptic_data = {
key_data: { key_data: {
p256: { p256: {
@ -95,6 +97,18 @@ const elliptic_data = {
0xB8, 0xFD, 0x0B, 0xDF, 0x76, 0xCE, 0xBC, 0x95, 0xB8, 0xFD, 0x0B, 0xDF, 0x76, 0xCE, 0xBC, 0x95,
0x4B, 0x92, 0x26, 0xFC, 0xAA, 0x7A, 0x7C, 0x3F 0x4B, 0x92, 0x26, 0xFC, 0xAA, 0x7A, 0x7C, 0x3F
]) ])
},
brainpoolP256r1: {
priv: util.hexToUint8Array('8b426897130e1e5e70a4d6320c4002bb1642a5e57ade066e060464137dfd5e05'),
pub: util.hexToUint8Array('042a43d8cc20e5a3fbd75d3a5a9b17d867bba80f11334d0665f0c641d13460a52aa3373a4ccfaa7d76765a689bd9fe15a4fd107ef1ec9ac980234c31647170c81a')
},
brainpoolP384r1: {
priv: util.hexToUint8Array('7ccc97acdf4b775606c5c994a37a8b28086167046ac0d55664ede4097d8de79dec56e69dfff5776d53fcbd2147bbae9f'),
pub: util.hexToUint8Array('043809fa0c74ec9817cb73eba67db71e01663528fb9fbe6a123f8339346c37efc9ff7cd116074a80684448e44ee9204c795c88ad634ad272585c0b4e3093b11e6c99a6c0ca9c278f83ef57e2ed802502aee76f4529bcb873eef754bec894a5032f')
},
brainpoolP512r1: {
priv: util.hexToUint8Array('0a32459d1ecf8815397a66f6cdb18692c6f79a3c6059b4c344d0162416c7603a82a9a938568edafb132c7433ffeeab4cf201d9542209eb28070bea56ab6b8938'),
pub: util.hexToUint8Array('040f64473d9b3597752e3a87095c0b219dd85f56a79c3b2dc8fb2b0c95b60f4be45c41a8a7ea31d60e15fea6275eb7db93856bc2eb30cc8876513335d43812bd2c4e195e05679ac667a2f7fb05c5842779d18fa411500e43e2f291ea8348f061db15382d4db1cfcf106a29f46e1c00e7d63e635c51293f69c0dd4f6a61da589b2a')
} }
} }
}; };

View File

@ -256,10 +256,6 @@ EJ4QcD/oQ6x1M/8X/iKQCtxZP8RnlrbH7ExkNON5s5g=
expect(await result.signatures[0].verified).to.be.true; expect(await result.signatures[0].verified).to.be.true;
}); });
it('Decrypt and verify message with leading zero in hash signed with old elliptic algorithm', async function () { it('Decrypt and verify message with leading zero in hash signed with old elliptic algorithm', async function () {
//this test would not work with nodeCrypto, since message is signed with leading zero stripped from the hash
if (util.getNodeCrypto()) {
this.skip(); // eslint-disable-line no-invalid-this
}
const juliet = await load_priv_key('juliet'); const juliet = await load_priv_key('juliet');
const romeo = await load_pub_key('romeo'); const romeo = await load_pub_key('romeo');
const msg = await openpgp.readMessage({ armoredMessage: data.romeo.message_encrypted_with_leading_zero_in_hash_signed_by_elliptic_with_old_implementation }); const msg = await openpgp.readMessage({ armoredMessage: data.romeo.message_encrypted_with_leading_zero_in_hash_signed_by_elliptic_with_old_implementation });