mirror of
https://github.com/openpgpjs/openpgpjs.git
synced 2025-06-12 00:56:41 +00:00
Switch back to using standard BigInteger class instead of wrapper
Using a wrapper requires adding some handling code to fix race conditions, but it does not provide advantages until we switch to TS.
This commit is contained in:
parent
9e1962f006
commit
4ee9deae62
@ -1,39 +1,20 @@
|
||||
/**
|
||||
* 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.
|
||||
* We don't use the BigIntegerInterface wrapper from noble-hashes because:
|
||||
* - importing the instance results in it being shared with noble-hashes, which separately calls `setImplementation()`
|
||||
* on load, causing it to throw due to duplicate initialization.
|
||||
* - even duplicating the interface code here to keep a separate instance requires handing a race-conditions the first time
|
||||
* `getBigInteger` is called, when the code needs to check if the implementation is set, and initialize it if not.
|
||||
* Ultimately, the interface provides no advantages and it's only needed because of TS.
|
||||
*/
|
||||
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);
|
||||
return NativeBigInteger;
|
||||
} 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 FallbackBigInteger;
|
||||
}
|
||||
|
||||
return BigInteger;
|
||||
}
|
||||
|
@ -43,11 +43,11 @@ import { isProbablePrime } from './prime';
|
||||
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);
|
||||
g = BigInteger.new(g);
|
||||
x = BigInteger.new(x);
|
||||
const one = new BigInteger(1);
|
||||
p = new BigInteger(p);
|
||||
q = new BigInteger(q);
|
||||
g = new BigInteger(g);
|
||||
x = new BigInteger(x);
|
||||
|
||||
let k;
|
||||
let r;
|
||||
@ -60,7 +60,7 @@ export async function sign(hashAlgo, hashed, g, p, q, x) {
|
||||
// of leftmost bits equal to the number of bits of q. This (possibly
|
||||
// truncated) hash function result is treated as a number and used
|
||||
// directly in the DSA signature algorithm.
|
||||
const h = BigInteger.new(hashed.subarray(0, q.byteLength())).mod(q);
|
||||
const h = new BigInteger(hashed.subarray(0, q.byteLength())).mod(q);
|
||||
// FIPS-186-4, section 4.6:
|
||||
// The values of r and s shall be checked to determine if r = 0 or s = 0.
|
||||
// If either r = 0 or s = 0, a new value of k shall be generated, and the
|
||||
@ -103,21 +103,21 @@ export async function sign(hashAlgo, hashed, g, p, q, x) {
|
||||
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);
|
||||
const zero = new BigInteger(0);
|
||||
r = new BigInteger(r);
|
||||
s = new BigInteger(s);
|
||||
|
||||
p = BigInteger.new(p);
|
||||
q = BigInteger.new(q);
|
||||
g = BigInteger.new(g);
|
||||
y = BigInteger.new(y);
|
||||
p = new BigInteger(p);
|
||||
q = new BigInteger(q);
|
||||
g = new BigInteger(g);
|
||||
y = new BigInteger(y);
|
||||
|
||||
if (r.lte(zero) || r.gte(q) ||
|
||||
s.lte(zero) || s.gte(q)) {
|
||||
util.printDebug('invalid DSA Signature');
|
||||
return false;
|
||||
}
|
||||
const h = BigInteger.new(hashed.subarray(0, q.byteLength())).imod(q);
|
||||
const h = new BigInteger(hashed.subarray(0, q.byteLength())).imod(q);
|
||||
const w = s.modInv(q); // s**-1 mod q
|
||||
if (w.isZero()) {
|
||||
util.printDebug('invalid DSA Signature');
|
||||
@ -147,11 +147,11 @@ export async function verify(hashAlgo, r, s, hashed, g, p, q, y) {
|
||||
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);
|
||||
y = BigInteger.new(y);
|
||||
const one = BigInteger.new(1);
|
||||
p = new BigInteger(p);
|
||||
q = new BigInteger(q);
|
||||
g = new BigInteger(g);
|
||||
y = new BigInteger(y);
|
||||
const one = new BigInteger(1);
|
||||
// Check that 1 < g < p
|
||||
if (g.lte(one) || g.gte(p)) {
|
||||
return false;
|
||||
@ -175,8 +175,8 @@ export async function validateParams(p, q, g, y, x) {
|
||||
/**
|
||||
* Check q is large and probably prime (we mainly want to avoid small factors)
|
||||
*/
|
||||
const qSize = BigInteger.new(q.bitLength());
|
||||
const n150 = BigInteger.new(150);
|
||||
const qSize = new BigInteger(q.bitLength());
|
||||
const n150 = new BigInteger(150);
|
||||
if (qSize.lt(n150) || !(await isProbablePrime(q, null, 32))) {
|
||||
return false;
|
||||
}
|
||||
@ -187,8 +187,8 @@ export async function validateParams(p, q, g, y, x) {
|
||||
*
|
||||
* Blinded exponentiation computes g**{rq + x} to compare to y
|
||||
*/
|
||||
x = BigInteger.new(x);
|
||||
const two = BigInteger.new(2);
|
||||
x = new BigInteger(x);
|
||||
const two = new BigInteger(2);
|
||||
const r = await getRandomBigInteger(two.leftShift(qSize.dec()), two.leftShift(qSize)); // draw r of same size as q
|
||||
const rqx = q.mul(r).add(x);
|
||||
if (!y.equal(g.modExp(rqx, p))) {
|
||||
|
@ -36,16 +36,16 @@ import util from '../../util';
|
||||
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);
|
||||
p = new BigInteger(p);
|
||||
g = new BigInteger(g);
|
||||
y = new BigInteger(y);
|
||||
|
||||
const padded = emeEncode(data, p.byteLength());
|
||||
const m = BigInteger.new(padded);
|
||||
const m = new BigInteger(padded);
|
||||
|
||||
// OpenPGP uses a "special" version of ElGamal where g is generator of the full group Z/pZ*
|
||||
// hence g has order p-1, and to avoid that k = 0 mod p-1, we need to pick k in [1, p-2]
|
||||
const k = await getRandomBigInteger(BigInteger.new(1), p.dec());
|
||||
const k = await getRandomBigInteger(new BigInteger(1), p.dec());
|
||||
return {
|
||||
c1: g.modExp(k, p).toUint8Array(),
|
||||
c2: y.modExp(k, p).imul(m).imod(p).toUint8Array()
|
||||
@ -67,10 +67,10 @@ export async function encrypt(data, p, g, y) {
|
||||
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);
|
||||
x = BigInteger.new(x);
|
||||
c1 = new BigInteger(c1);
|
||||
c2 = new BigInteger(c2);
|
||||
p = new BigInteger(p);
|
||||
x = new BigInteger(x);
|
||||
|
||||
const padded = c1.modExp(x, p).modInv(p).imul(c2).imod(p);
|
||||
return emeDecode(padded.toUint8Array('be', p.byteLength()), randomPayload);
|
||||
@ -88,19 +88,19 @@ export async function decrypt(c1, c2, p, x, randomPayload) {
|
||||
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);
|
||||
p = new BigInteger(p);
|
||||
g = new BigInteger(g);
|
||||
y = new BigInteger(y);
|
||||
|
||||
const one = BigInteger.new(1);
|
||||
const one = new BigInteger(1);
|
||||
// Check that 1 < g < p
|
||||
if (g.lte(one) || g.gte(p)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Expect p-1 to be large
|
||||
const pSize = BigInteger.new(p.bitLength());
|
||||
const n1023 = BigInteger.new(1023);
|
||||
const pSize = new BigInteger(p.bitLength());
|
||||
const n1023 = new BigInteger(1023);
|
||||
if (pSize.lt(n1023)) {
|
||||
return false;
|
||||
}
|
||||
@ -120,8 +120,8 @@ export async function validateParams(p, g, y, x) {
|
||||
* We just check g**i != 1 for all i up to a threshold
|
||||
*/
|
||||
let res = g;
|
||||
const i = BigInteger.new(1);
|
||||
const threshold = BigInteger.new(2).leftShift(BigInteger.new(17)); // we want order > threshold
|
||||
const i = new BigInteger(1);
|
||||
const threshold = new BigInteger(2).leftShift(new BigInteger(17)); // we want order > threshold
|
||||
while (i.lt(threshold)) {
|
||||
res = res.mul(g).imod(p);
|
||||
if (res.isOne()) {
|
||||
@ -136,8 +136,8 @@ export async function validateParams(p, g, y, x) {
|
||||
*
|
||||
* Blinded exponentiation computes g**{r(p-1) + x} to compare to y
|
||||
*/
|
||||
x = BigInteger.new(x);
|
||||
const two = BigInteger.new(2);
|
||||
x = new BigInteger(x);
|
||||
const two = new BigInteger(2);
|
||||
const r = await getRandomBigInteger(two.leftShift(pSize.dec()), two.leftShift(pSize)); // draw r of same size as p-1
|
||||
const rqx = p.dec().imul(r).iadd(x);
|
||||
if (!y.equal(g.modExp(rqx, p))) {
|
||||
|
@ -33,9 +33,9 @@ import { getRandomBigInteger } from '../random';
|
||||
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);
|
||||
const one = new BigInteger(1);
|
||||
const min = one.leftShift(new BigInteger(bits - 1));
|
||||
const thirty = new BigInteger(30);
|
||||
/*
|
||||
* We can avoid any multiples of 3 and 5 by looking at n mod 30
|
||||
* n mod 30 = 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
||||
@ -48,7 +48,7 @@ export async function randomProbablePrime(bits, e, k) {
|
||||
let i = n.mod(thirty).toNumber();
|
||||
|
||||
do {
|
||||
n.iadd(BigInteger.new(adds[i]));
|
||||
n.iadd(new BigInteger(adds[i]));
|
||||
i = (i + adds[i]) % adds.length;
|
||||
// If reached the maximum, go back to the minimum.
|
||||
if (n.bitLength() > bits) {
|
||||
@ -95,7 +95,7 @@ export async function isProbablePrime(n, e, k) {
|
||||
export async function fermat(n, b) {
|
||||
const BigInteger = await util.getBigInteger();
|
||||
|
||||
b = b || BigInteger.new(2);
|
||||
b = b || new BigInteger(2);
|
||||
return b.modExp(n.dec(), n).isOne();
|
||||
}
|
||||
|
||||
@ -103,7 +103,7 @@ export async function divisionTest(n) {
|
||||
const BigInteger = await util.getBigInteger();
|
||||
|
||||
return smallPrimes.every(m => {
|
||||
return n.mod(BigInteger.new(m)) !== 0;
|
||||
return n.mod(new BigInteger(m)) !== 0;
|
||||
});
|
||||
}
|
||||
|
||||
@ -243,10 +243,10 @@ export async function millerRabin(n, k, rand) {
|
||||
// Find d and s, (n - 1) = (2 ^ s) * d;
|
||||
let s = 0;
|
||||
while (!n1.getBit(s)) { s++; }
|
||||
const d = n.rightShift(BigInteger.new(s));
|
||||
const d = n.rightShift(new BigInteger(s));
|
||||
|
||||
for (; k > 0; k--) {
|
||||
const a = rand ? rand() : await getRandomBigInteger(BigInteger.new(2), n1);
|
||||
const a = rand ? rand() : await getRandomBigInteger(new BigInteger(2), n1);
|
||||
|
||||
let x = a.modExp(d, n);
|
||||
if (x.isOne() || x.equal(n1)) {
|
||||
|
@ -160,7 +160,7 @@ export async function decrypt(data, n, e, d, p, q, u, randomPayload) {
|
||||
export async function generate(bits, e) {
|
||||
const BigInteger = await util.getBigInteger();
|
||||
|
||||
e = BigInteger.new(e);
|
||||
e = new BigInteger(e);
|
||||
|
||||
// Native RSA keygen using Web Crypto
|
||||
if (util.getWebCrypto()) {
|
||||
@ -265,24 +265,24 @@ export async function generate(bits, e) {
|
||||
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);
|
||||
n = new BigInteger(n);
|
||||
p = new BigInteger(p);
|
||||
q = new BigInteger(q);
|
||||
|
||||
// expect pq = n
|
||||
if (!p.mul(q).equal(n)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const two = BigInteger.new(2);
|
||||
const two = new BigInteger(2);
|
||||
// expect p*u = 1 mod q
|
||||
u = BigInteger.new(u);
|
||||
u = new BigInteger(u);
|
||||
if (!p.mul(u).mod(q).isOne()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
e = BigInteger.new(e);
|
||||
d = BigInteger.new(d);
|
||||
e = new BigInteger(e);
|
||||
d = new BigInteger(d);
|
||||
/**
|
||||
* In RSA pkcs#1 the exponents (d, e) are inverses modulo lcm(p-1, q-1)
|
||||
* We check that [de = 1 mod (p-1)] and [de = 1 mod (q-1)]
|
||||
@ -290,7 +290,7 @@ export async function validateParams(n, e, d, p, q, u) {
|
||||
*
|
||||
* We blind the multiplication with r, and check that rde = r mod lcm(p-1, q-1)
|
||||
*/
|
||||
const nSizeOver3 = BigInteger.new(Math.floor(n.bitLength() / 3));
|
||||
const nSizeOver3 = new BigInteger(Math.floor(n.bitLength() / 3));
|
||||
const r = await getRandomBigInteger(two, two.leftShift(nSizeOver3)); // r in [ 2, 2^{|n|/3} ) < p and q
|
||||
const rde = r.mul(d).mul(e);
|
||||
|
||||
@ -305,9 +305,9 @@ 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);
|
||||
n = new BigInteger(n);
|
||||
const m = new BigInteger(await emsaEncode(hashAlgo, hashed, n.byteLength()));
|
||||
d = new BigInteger(d);
|
||||
if (m.gte(n)) {
|
||||
throw new Error('Message size cannot exceed modulus size');
|
||||
}
|
||||
@ -367,9 +367,9 @@ 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);
|
||||
n = new BigInteger(n);
|
||||
s = new BigInteger(s);
|
||||
e = new BigInteger(e);
|
||||
if (s.gte(n)) {
|
||||
throw new Error('Signature size cannot exceed modulus size');
|
||||
}
|
||||
@ -436,9 +436,9 @@ 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);
|
||||
n = new BigInteger(n);
|
||||
data = new BigInteger(emeEncode(data, n.byteLength()));
|
||||
e = new BigInteger(e);
|
||||
if (data.gte(n)) {
|
||||
throw new Error('Message size cannot exceed modulus size');
|
||||
}
|
||||
@ -489,20 +489,20 @@ 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);
|
||||
d = BigInteger.new(d);
|
||||
p = BigInteger.new(p);
|
||||
q = BigInteger.new(q);
|
||||
u = BigInteger.new(u);
|
||||
data = new BigInteger(data);
|
||||
n = new BigInteger(n);
|
||||
e = new BigInteger(e);
|
||||
d = new BigInteger(d);
|
||||
p = new BigInteger(p);
|
||||
q = new BigInteger(q);
|
||||
u = new BigInteger(u);
|
||||
if (data.gte(n)) {
|
||||
throw new Error('Data too large.');
|
||||
}
|
||||
const dq = d.mod(q.dec()); // d mod (q-1)
|
||||
const dp = d.mod(p.dec()); // d mod (p-1)
|
||||
|
||||
const unblinder = (await getRandomBigInteger(BigInteger.new(2), n)).mod(n);
|
||||
const unblinder = (await getRandomBigInteger(new BigInteger(2), n)).mod(n);
|
||||
const blinder = unblinder.modInv(n).modExp(e, n);
|
||||
data = data.mul(blinder).mod(n);
|
||||
|
||||
@ -532,9 +532,9 @@ async function bnDecrypt(data, n, e, d, p, q, u, randomPayload) {
|
||||
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);
|
||||
const pNum = new BigInteger(p);
|
||||
const qNum = new BigInteger(q);
|
||||
const dNum = new BigInteger(d);
|
||||
|
||||
let dq = dNum.mod(qNum.dec()); // d mod (q-1)
|
||||
let dp = dNum.mod(pNum.dec()); // d mod (p-1)
|
||||
|
@ -63,6 +63,6 @@ export async function getRandomBigInteger(min, max) {
|
||||
// Using a while loop is necessary to avoid bias introduced by the mod operation.
|
||||
// However, we request 64 extra random bits so that the bias is negligible.
|
||||
// Section B.1.1 here: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
|
||||
const r = BigInteger.new(await getRandomBytes(bytes + 8));
|
||||
const r = new BigInteger(getRandomBytes(bytes + 8));
|
||||
return r.mod(modulus).add(min);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user