diff --git a/src/biginteger.js b/src/biginteger.js new file mode 100644 index 00000000..b869e50e --- /dev/null +++ b/src/biginteger.js @@ -0,0 +1,39 @@ +/** + * This is a vanilla JS copy of @openpgp/noble-hashes/esm/biginteger/interface.ts . + * We need to duplicate the file, instead of importing it, since in that case the BigIntegerInterface instance + * would be shared with noble-hashes, which separately calls `setImplementation()` on load, causing it to throw due to + * duplicate initialization. + */ +class BigInteger { + static setImplementation(Implementation, replace = false) { + if (BigInteger.Implementation && !replace) { + throw new Error('Implementation already set'); + } + BigInteger.Implementation = Implementation; + } + + static new(n) { + return new BigInteger.Implementation(n); + } +} + +const detectBigInt = () => typeof BigInt !== 'undefined'; +export async function getBigInteger() { + if (BigInteger.Implementation) { + return BigInteger; + } + + // TODOOOOO replace = true needed in case of concurrent class loading, how to fix without removing wrapper class? + + if (detectBigInt()) { + // NativeBigInteger is small, so it's imported in isolation (it could also be imported at the top level) + const { default: NativeBigInteger } = await import('@openpgp/noble-hashes/esm/biginteger/native.interface'); + BigInteger.setImplementation(NativeBigInteger, true); + } else { + // FallbackBigInteger relies on large BN.js lib, which is also used by noble-hashes and noble-curves + const { default: FallbackBigInteger } = await import('@openpgp/noble-hashes/esm/biginteger/bn.interface'); + BigInteger.setImplementation(FallbackBigInteger, true); + } + + return BigInteger; +} diff --git a/src/crypto/hash/index.js b/src/crypto/hash/index.js index 0943b82f..3332a1ce 100644 --- a/src/crypto/hash/index.js +++ b/src/crypto/hash/index.js @@ -5,11 +5,6 @@ * @module crypto/hash */ -import { sha1 } from '@openpgp/noble-hashes/sha1'; -import { sha224, sha256 } from '@openpgp/noble-hashes/sha256'; -import { sha384, sha512 } from '@openpgp/noble-hashes/sha512'; -import { sha3_256, sha3_512 } from '@openpgp/noble-hashes/sha3'; -import { ripemd160 } from '@openpgp/noble-hashes/ripemd160'; import * as stream from '@openpgp/web-stream-tools'; import md5 from './md5'; import util from '../../util'; @@ -31,48 +26,47 @@ function nodeHash(type) { }; } -function nobleHash(hash, webCryptoHash) { +function nobleHash(nobleHashName, webCryptoHashName) { + const getNobleHash = async () => { + const { nobleHashes } = await import('./noble_hashes'); + const hash = nobleHashes.get(nobleHashName); + if (!hash) throw new Error('Unsupported hash'); + return hash; + }; + return async function(data) { if (stream.isArrayStream(data)) { data = await stream.readToEnd(data); } if (util.isStream(data)) { + const hash = await getNobleHash(); + const hashInstance = hash.create(); return stream.transform(data, value => { hashInstance.update(value); }, () => hashInstance.digest()); - } else if (webCrypto && webCryptoHash) { - return new Uint8Array(await webCrypto.digest(webCryptoHash, data)); + } else if (webCrypto && webCryptoHashName) { + return new Uint8Array(await webCrypto.digest(webCryptoHashName, data)); } else { + const hash = await getNobleHash(); + return hash(data); } }; } -const hashFunctions = { - md5: nodeHash('md5') || md5, - sha1: nodeHash('sha1') || nobleHash(sha1, 'SHA-1'), - sha224: nodeHash('sha224') || nobleHash(sha224), - sha256: nodeHash('sha256') || nobleHash(sha256, 'SHA-256'), - sha384: nodeHash('sha384') || nobleHash(sha384, 'SHA-384'), - sha512: nodeHash('sha512') || nobleHash(sha512, 'SHA-512'), - ripemd: nodeHash('ripemd160') || nobleHash(ripemd160), - sha3_256: nodeHash('sha3-256') || nobleHash(sha3_256), - sha3_512: nodeHash('sha3-512') || nobleHash(sha3_512) -}; - export default { /** @see module:md5 */ - md5: hashFunctions.md5, - sha1: hashFunctions.sha1, - sha224: hashFunctions.sha224, - sha256: hashFunctions.sha256, - sha384: hashFunctions.sha384, - sha512: hashFunctions.sha512, - ripemd: hashFunctions.ripemd, - sha3_256: hashFunctions.sha3_256, - sha3_512: hashFunctions.sha3_512, + md5: nodeHash('md5') || md5, + sha1: nodeHash('sha1') || nobleHash('sha1', 'SHA-1'), + sha224: nodeHash('sha224') || nobleHash('sha224'), + sha256: nodeHash('sha256') || nobleHash('sha256', 'SHA-256'), + sha384: nodeHash('sha384') || nobleHash('sha384', 'SHA-384'), + sha512: nodeHash('sha512') || nobleHash('sha512', 'SHA-512'), + ripemd: nodeHash('ripemd160') || nobleHash('ripemd160'), + sha3_256: nodeHash('sha3-256') || nobleHash('sha3_256'), + sha3_512: nodeHash('sha3-512') || nobleHash('sha3_512'), /** * Create a hash on the specified data using the specified algorithm diff --git a/src/crypto/hash/noble_hashes.js b/src/crypto/hash/noble_hashes.js new file mode 100644 index 00000000..0d589a78 --- /dev/null +++ b/src/crypto/hash/noble_hashes.js @@ -0,0 +1,22 @@ +/** + * This file is needed to dynamic import the noble-hashes. + * Separate dynamic imports are not convenient as they result in too many chunks, + * which share a lot of code anyway. + */ + +import { sha1 } from '@openpgp/noble-hashes/sha1'; +import { sha224, sha256 } from '@openpgp/noble-hashes/sha256'; +import { sha384, sha512 } from '@openpgp/noble-hashes/sha512'; +import { sha3_256, sha3_512 } from '@openpgp/noble-hashes/sha3'; +import { ripemd160 } from '@openpgp/noble-hashes/ripemd160'; + +export const nobleHashes = new Map(Object.entries({ + sha1, + sha224, + sha256, + sha384, + sha512, + sha3_256, + sha3_512, + ripemd160 +})); diff --git a/src/crypto/public_key/dsa.js b/src/crypto/public_key/dsa.js index b48a4d97..b315da5f 100644 --- a/src/crypto/public_key/dsa.js +++ b/src/crypto/public_key/dsa.js @@ -19,7 +19,6 @@ * @fileoverview A Digital signature algorithm implementation * @module crypto/public_key/dsa */ -import { BigInteger } from '@openpgp/noble-hashes/biginteger'; import { getRandomBigInteger } from '../random'; import util from '../../util'; import { isProbablePrime } from './prime'; @@ -42,6 +41,8 @@ import { isProbablePrime } from './prime'; * @async */ export async function sign(hashAlgo, hashed, g, p, q, x) { + const BigInteger = await util.getBigInteger(); + const one = BigInteger.new(1); p = BigInteger.new(p); q = BigInteger.new(q); @@ -100,6 +101,8 @@ export async function sign(hashAlgo, hashed, g, p, q, x) { * @async */ export async function verify(hashAlgo, r, s, hashed, g, p, q, y) { + const BigInteger = await util.getBigInteger(); + const zero = BigInteger.new(0); r = BigInteger.new(r); s = BigInteger.new(s); @@ -142,6 +145,8 @@ export async function verify(hashAlgo, r, s, hashed, g, p, q, y) { * @async */ export async function validateParams(p, q, g, y, x) { + const BigInteger = await util.getBigInteger(); + p = BigInteger.new(p); q = BigInteger.new(q); g = BigInteger.new(g); diff --git a/src/crypto/public_key/elgamal.js b/src/crypto/public_key/elgamal.js index e9d66865..7e6790d6 100644 --- a/src/crypto/public_key/elgamal.js +++ b/src/crypto/public_key/elgamal.js @@ -19,9 +19,9 @@ * @fileoverview ElGamal implementation * @module crypto/public_key/elgamal */ -import { BigInteger } from '@openpgp/noble-hashes/biginteger'; import { getRandomBigInteger } from '../random'; import { emeEncode, emeDecode } from '../pkcs1'; +import util from '../../util'; /** * ElGamal Encryption function @@ -34,6 +34,8 @@ import { emeEncode, emeDecode } from '../pkcs1'; * @async */ export async function encrypt(data, p, g, y) { + const BigInteger = await util.getBigInteger(); + p = BigInteger.new(p); g = BigInteger.new(g); y = BigInteger.new(y); @@ -63,6 +65,8 @@ export async function encrypt(data, p, g, y) { * @async */ export async function decrypt(c1, c2, p, x, randomPayload) { + const BigInteger = await util.getBigInteger(); + c1 = BigInteger.new(c1); c2 = BigInteger.new(c2); p = BigInteger.new(p); @@ -82,6 +86,8 @@ export async function decrypt(c1, c2, p, x, randomPayload) { * @async */ export async function validateParams(p, g, y, x) { + const BigInteger = await util.getBigInteger(); + p = BigInteger.new(p); g = BigInteger.new(g); y = BigInteger.new(y); diff --git a/src/crypto/public_key/elliptic/ecdh.js b/src/crypto/public_key/elliptic/ecdh.js index 9039833a..3ea593d7 100644 --- a/src/crypto/public_key/elliptic/ecdh.js +++ b/src/crypto/public_key/elliptic/ecdh.js @@ -21,7 +21,7 @@ */ import nacl from '@openpgp/tweetnacl'; -import { CurveWithOID, jwkToRawPublic, rawPublicToJWK, privateToJWK, validateStandardParams, getNobleCurve } from './oid_curves'; +import { CurveWithOID, jwkToRawPublic, rawPublicToJWK, privateToJWK, validateStandardParams } from './oid_curves'; import * as aesKW from '../../aes_kw'; import { getRandomBytes } from '../../random'; import hash from '../../hash'; @@ -210,8 +210,8 @@ export async function decrypt(oid, kdfParams, V, C, Q, d, fingerprint) { throw err; } -function jsPrivateEphemeralKey(curve, V, d) { - const nobleCurve = getNobleCurve(curve.name); +async function jsPrivateEphemeralKey(curve, V, d) { + const nobleCurve = await util.getNobleCurve(enums.publicKey.ecdh, curve.name); // The output includes parity byte const sharedSecretWithParity = nobleCurve.getSharedSecret(d, V); const sharedKey = sharedSecretWithParity.subarray(1); @@ -219,7 +219,7 @@ function jsPrivateEphemeralKey(curve, V, d) { } async function jsPublicEphemeralKey(curve, Q) { - const nobleCurve = getNobleCurve(curve.name); + const nobleCurve = await util.getNobleCurve(enums.publicKey.ecdh, curve.name); const { publicKey: V, privateKey: v } = await curve.genKeyPair(); // The output includes parity byte diff --git a/src/crypto/public_key/elliptic/ecdh_x.js b/src/crypto/public_key/elliptic/ecdh_x.js index 2d9ffe3e..a517e88a 100644 --- a/src/crypto/public_key/elliptic/ecdh_x.js +++ b/src/crypto/public_key/elliptic/ecdh_x.js @@ -4,7 +4,6 @@ */ import x25519 from '@openpgp/tweetnacl'; -import { x448 } from '@openpgp/noble-curves/ed448'; import * as aesKW from '../../aes_kw'; import { getRandomBytes } from '../../random'; @@ -32,6 +31,7 @@ export async function generate(algo) { return { A, k }; } case enums.publicKey.x448: { + const x448 = await util.getNobleCurve(enums.publicKey.x448); const k = x448.utils.randomPrivateKey(); const A = x448.getPublicKey(k); return { A, k }; @@ -60,6 +60,7 @@ export async function validateParams(algo, A, k) { return util.equalsUint8Array(A, publicKey); } case enums.publicKey.x448: { + const x448 = await util.getNobleCurve(enums.publicKey.x448); /** * Derive public point A' from private key * and expect A == A' @@ -102,6 +103,7 @@ export async function encrypt(algo, data, recipientA) { return { ephemeralPublicKey, wrappedKey }; } case enums.publicKey.x448: { + const x448 = await util.getNobleCurve(enums.publicKey.x448); const ephemeralSecretKey = x448.utils.randomPrivateKey(); const sharedSecret = x448.getSharedSecret(ephemeralSecretKey, recipientA); const ephemeralPublicKey = x448.getPublicKey(ephemeralSecretKey); @@ -146,6 +148,7 @@ export async function decrypt(algo, ephemeralPublicKey, wrappedKey, A, k) { return aesKW.unwrap(encryptionKey, wrappedKey); } case enums.publicKey.x448: { + const x448 = await util.getNobleCurve(enums.publicKey.x448); const sharedSecret = x448.getSharedSecret(k, ephemeralPublicKey); const hkdfInput = util.concatUint8Array([ ephemeralPublicKey, diff --git a/src/crypto/public_key/elliptic/ecdsa.js b/src/crypto/public_key/elliptic/ecdsa.js index 33f22232..b90bed2b 100644 --- a/src/crypto/public_key/elliptic/ecdsa.js +++ b/src/crypto/public_key/elliptic/ecdsa.js @@ -24,7 +24,7 @@ import enums from '../../../enums'; import util from '../../../util'; import { getRandomBytes } from '../../random'; import hash from '../../hash'; -import { CurveWithOID, webCurves, privateToJWK, rawPublicToJWK, validateStandardParams, getNobleCurve } from './oid_curves'; +import { CurveWithOID, webCurves, privateToJWK, rawPublicToJWK, validateStandardParams } from './oid_curves'; const webCrypto = util.getWebCrypto(); const nodeCrypto = util.getNodeCrypto(); @@ -74,7 +74,7 @@ export async function sign(oid, hashAlgo, message, publicKey, privateKey, hashed } } - const nobleCurve = getNobleCurve(curve.name); + const nobleCurve = await util.getNobleCurve(enums.publicKey.ecdsa, curve.name); // lowS: non-canonical sig: https://stackoverflow.com/questions/74338846/ecdsa-signature-verification-mismatch const signature = nobleCurve.sign(hashed, privateKey, { lowS: false }); return { @@ -103,7 +103,7 @@ export async function verify(oid, hashAlgo, signature, message, publicKey, hashe // Similarly, secp256k1 should have been used rarely enough. // However, we implement the fix for all curves, since it's only needed in case of // verification failure, which is unexpected, hence a minor slowdown is acceptable. - const tryFallbackVerificationForOldBug = () => ( + const tryFallbackVerificationForOldBug = async () => ( hashed[0] === 0 ? jsVerify(curve, signature, hashed.subarray(1), publicKey) : false @@ -133,7 +133,7 @@ export async function verify(oid, hashAlgo, signature, message, publicKey, hashe } } - const verified = jsVerify(curve, signature, hashed, publicKey); + const verified = await jsVerify(curve, signature, hashed, publicKey); return verified || tryFallbackVerificationForOldBug(); } @@ -183,8 +183,8 @@ export async function validateParams(oid, Q, d) { * Fallback javascript implementation of ECDSA verification. * To be used if no native implementation is available for the given curve/operation. */ -function jsVerify(curve, signature, hashed, publicKey) { - const nobleCurve = getNobleCurve(curve.name); +async function jsVerify(curve, signature, hashed, publicKey) { + const nobleCurve = await util.getNobleCurve(enums.publicKey.ecdsa, 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 }); } diff --git a/src/crypto/public_key/elliptic/eddsa.js b/src/crypto/public_key/elliptic/eddsa.js index 28c33f0c..c13665ea 100644 --- a/src/crypto/public_key/elliptic/eddsa.js +++ b/src/crypto/public_key/elliptic/eddsa.js @@ -20,15 +20,12 @@ * @module crypto/public_key/elliptic/eddsa */ -import { sha512 } from '@openpgp/noble-hashes/sha512'; import ed25519 from '@openpgp/tweetnacl'; -import { ed448 } from '@openpgp/noble-curves/ed448'; import util from '../../../util'; import enums from '../../../enums'; import hash from '../../hash'; import { getRandomBytes } from '../../random'; -ed25519.hash = bytes => sha512(bytes); /** * Generate (non-legacy) EdDSA key @@ -43,6 +40,7 @@ export async function generate(algo) { return { A, seed }; } case enums.publicKey.ed448: { + const ed448 = await util.getNobleCurve(enums.publicKey.ed448); const seed = ed448.utils.randomPrivateKey(); const A = ed448.getPublicKey(seed); return { A, seed }; @@ -76,6 +74,7 @@ export async function sign(algo, hashAlgo, message, publicKey, privateKey, hashe return { RS: signature }; } case enums.publicKey.ed448: { + const ed448 = await util.getNobleCurve(enums.publicKey.ed448); const signature = ed448.sign(hashed, privateKey); return { RS: signature }; } @@ -104,8 +103,10 @@ export async function verify(algo, hashAlgo, { RS }, m, publicKey, hashed) { case enums.publicKey.ed25519: { return ed25519.sign.detached.verify(hashed, RS, publicKey); } - case enums.publicKey.ed448: + case enums.publicKey.ed448: { + const ed448 = await util.getNobleCurve(enums.publicKey.ed448); return ed448.verify(RS, hashed, publicKey); + } default: throw new Error('Unsupported EdDSA algorithm'); } @@ -131,6 +132,8 @@ export async function validateParams(algo, A, seed) { } case enums.publicKey.ed448: { + const ed448 = await util.getNobleCurve(enums.publicKey.ed448); + const publicKey = ed448.getPublicKey(seed); return util.equalsUint8Array(A, publicKey); } diff --git a/src/crypto/public_key/elliptic/noble_curves.js b/src/crypto/public_key/elliptic/noble_curves.js new file mode 100644 index 00000000..22a80fba --- /dev/null +++ b/src/crypto/public_key/elliptic/noble_curves.js @@ -0,0 +1,27 @@ +/** + * This file is needed to dynamic import the noble-curves. + * Separate dynamic imports are not convenient as they result in too many chunks, + * which share a lot of code anyway. + */ + +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 { x448, ed448 } from '@openpgp/noble-curves/ed448'; +import { secp256k1 } from '@openpgp/noble-curves/secp256k1'; + +export const nobleCurves = new Map(Object.entries({ + p256, + p384, + p521, + brainpoolP256r1, + brainpoolP384r1, + brainpoolP512r1, + secp256k1, + x448, + ed448 +})); + diff --git a/src/crypto/public_key/elliptic/oid_curves.js b/src/crypto/public_key/elliptic/oid_curves.js index f5716d02..75c23827 100644 --- a/src/crypto/public_key/elliptic/oid_curves.js +++ b/src/crypto/public_key/elliptic/oid_curves.js @@ -20,20 +20,12 @@ * @module crypto/public_key/elliptic/curve */ import nacl from '@openpgp/tweetnacl'; -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 enums from '../../../enums'; import util from '../../../util'; import { uint8ArrayToB64, b64ToUint8Array } from '../../../encoding/base64'; import OID from '../../../type/oid'; import { UnsupportedError } from '../../../packet/packet'; -import defaultConfig from '../../../config'; const webCrypto = util.getWebCrypto(); const nodeCrypto = util.getNodeCrypto(); @@ -56,26 +48,6 @@ const nodeCurves = nodeCrypto ? { [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 => { - if (!defaultConfig.useEllipticFallback) { - // TODO make import dynamic - throw new Error('This curve is only supported in the full build of OpenPGP.js'); - } - const curve = nobleCurvess[curveName]; - if (!curve) throw new Error('Unsupported curve'); - return curve; -}; - - const curves = { p256: { oid: [0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07], @@ -291,7 +263,7 @@ async function validateStandardParams(algo, oid, Q, d) { return true; } - const nobleCurve = getNobleCurve(enums.write(enums.curve, oid.toHex())); + const nobleCurve = await util.getNobleCurve(enums.publicKey.ecdsa, enums.write(enums.curve, oid.toHex())); // excluding curve25519Legacy, ecdh and ecdsa use the same curves /* * Re-derive public point Q' = dG from private key * Expect Q == Q' @@ -313,8 +285,8 @@ export { // Helper functions // // // ////////////////////////// -function jsGenKeyPair(name) { - const nobleCurve = getNobleCurve(name); +async function jsGenKeyPair(name) { + const nobleCurve = await util.getNobleCurve(enums.publicKey.ecdsa, name); // excluding curve25519Legacy, ecdh and ecdsa use the same curves const privateKey = nobleCurve.utils.randomPrivateKey(); const publicKey = nobleCurve.getPublicKey(privateKey, false); return { publicKey, privateKey }; diff --git a/src/crypto/public_key/prime.js b/src/crypto/public_key/prime.js index 8e0bd13d..18d3250e 100644 --- a/src/crypto/public_key/prime.js +++ b/src/crypto/public_key/prime.js @@ -19,7 +19,7 @@ * @fileoverview Algorithms for probabilistic random prime generation * @module crypto/public_key/prime */ -import { BigInteger } from '@openpgp/noble-hashes/biginteger'; +import util from '../../util'; import { getRandomBigInteger } from '../random'; /** @@ -31,6 +31,8 @@ import { getRandomBigInteger } from '../random'; * @async */ export async function randomProbablePrime(bits, e, k) { + const BigInteger = await util.getBigInteger(); + const one = BigInteger.new(1); const min = one.leftShift(BigInteger.new(bits - 1)); const thirty = BigInteger.new(30); @@ -91,11 +93,15 @@ export async function isProbablePrime(n, e, k) { * @returns {boolean} */ export async function fermat(n, b) { + const BigInteger = await util.getBigInteger(); + b = b || BigInteger.new(2); return b.modExp(n.dec(), n).isOne(); } export async function divisionTest(n) { + const BigInteger = await util.getBigInteger(); + return smallPrimes.every(m => { return n.mod(BigInteger.new(m)) !== 0; }); @@ -224,6 +230,8 @@ const smallPrimes = [ * @async */ export async function millerRabin(n, k, rand) { + const BigInteger = await util.getBigInteger(); + const len = n.bitLength(); if (!k) { diff --git a/src/crypto/public_key/rsa.js b/src/crypto/public_key/rsa.js index 180a3326..9050edcc 100644 --- a/src/crypto/public_key/rsa.js +++ b/src/crypto/public_key/rsa.js @@ -19,7 +19,6 @@ * @fileoverview RSA implementation * @module crypto/public_key/rsa */ -import { BigInteger } from '@openpgp/noble-hashes/biginteger'; import { randomProbablePrime } from './prime'; import { getRandomBigInteger } from '../random'; import util from '../../util'; @@ -159,6 +158,8 @@ export async function decrypt(data, n, e, d, p, q, u, randomPayload) { * @async */ export async function generate(bits, e) { + const BigInteger = await util.getBigInteger(); + e = BigInteger.new(e); // Native RSA keygen using Web Crypto @@ -262,6 +263,8 @@ export async function generate(bits, e) { * @async */ export async function validateParams(n, e, d, p, q, u) { + const BigInteger = await util.getBigInteger(); + n = BigInteger.new(n); p = BigInteger.new(p); q = BigInteger.new(q); @@ -300,6 +303,8 @@ export async function validateParams(n, e, d, p, q, u) { } async function bnSign(hashAlgo, n, d, hashed) { + const BigInteger = await util.getBigInteger(); + n = BigInteger.new(n); const m = BigInteger.new(await emsaEncode(hashAlgo, hashed, n.byteLength())); d = BigInteger.new(d); @@ -360,6 +365,8 @@ async function nodeSign(hashAlgo, data, n, e, d, p, q, u) { } async function bnVerify(hashAlgo, s, n, e, hashed) { + const BigInteger = await util.getBigInteger(); + n = BigInteger.new(n); s = BigInteger.new(s); e = BigInteger.new(e); @@ -427,6 +434,8 @@ async function nodeEncrypt(data, n, e) { } async function bnEncrypt(data, n, e) { + const BigInteger = await util.getBigInteger(); + n = BigInteger.new(n); data = BigInteger.new(emeEncode(data, n.byteLength())); e = BigInteger.new(e); @@ -478,6 +487,8 @@ async function nodeDecrypt(data, n, e, d, p, q, u, randomPayload) { } async function bnDecrypt(data, n, e, d, p, q, u, randomPayload) { + const BigInteger = await util.getBigInteger(); + data = BigInteger.new(data); n = BigInteger.new(n); e = BigInteger.new(e); @@ -519,6 +530,8 @@ async function bnDecrypt(data, n, e, d, p, q, u, randomPayload) { * @param {Uint8Array} u */ async function privateToJWK(n, e, d, p, q, u) { + const BigInteger = await util.getBigInteger(); + const pNum = BigInteger.new(p); const qNum = BigInteger.new(q); const dNum = BigInteger.new(d); diff --git a/src/crypto/random.js b/src/crypto/random.js index 8c7ad418..1f5f8142 100644 --- a/src/crypto/random.js +++ b/src/crypto/random.js @@ -21,7 +21,6 @@ * @fileoverview Provides tools for retrieving secure randomness from browsers or Node.js * @module crypto/random */ -import { BigInteger } from '@openpgp/noble-hashes/biginteger'; import util from '../util'; const nodeCrypto = util.getNodeCrypto(); @@ -52,6 +51,8 @@ export function getRandomBytes(length) { * @async */ export async function getRandomBigInteger(min, max) { + const BigInteger = await util.getBigInteger(); + if (max.lt(min)) { throw new Error('Illegal parameter value: max <= min'); } diff --git a/src/util.js b/src/util.js index f0ee7f63..0a1c721d 100644 --- a/src/util.js +++ b/src/util.js @@ -25,6 +25,8 @@ import * as stream from '@openpgp/web-stream-tools'; import { createRequire } from 'module'; // Must be stripped in browser built import enums from './enums'; +import defaultConfig from './config'; +import { getBigInteger } from './biginteger'; const debugMode = (() => { try { @@ -48,6 +50,37 @@ const util = { isStream: stream.isStream, + getBigInteger, + + /** + * Load noble-curves lib on demand and return the requested curve function + * @param {enums.publicKey} publicKeyAlgo + * @param {enums.curve} [curveName] - for algos supporting different curves (e.g. ECDSA) + * @returns curve implementation + * @throws on unrecognized curve, or curve not implemented by noble-curve + */ + getNobleCurve: async (publicKeyAlgo, curveName) => { + if (!defaultConfig.useEllipticFallback) { + throw new Error('This curve is only supported in the full build of OpenPGP.js'); + } + + const { nobleCurves } = await import('./crypto/public_key/elliptic/noble_curves'); + switch (publicKeyAlgo) { + case enums.publicKey.ecdh: + case enums.publicKey.ecdsa: { + const curve = nobleCurves.get(curveName); + if (!curve) throw new Error('Unsupported curve'); + return curve; + } + case enums.publicKey.x448: + return nobleCurves.get('x448'); + case enums.publicKey.ed448: + return nobleCurves.get('ed448'); + default: + throw new Error('Unsupported curve'); + } + }, + readNumber: function (bytes) { let n = 0; for (let i = 0; i < bytes.length; i++) {