Skip to content

Commit

Permalink
crypto: add randomPrime/randomPrimeSync/checkPrime
Browse files Browse the repository at this point in the history
APIs for generating and checking pseudo-random primes

Signed-off-by: James M Snell <jasnell@gmail.com>
  • Loading branch information
jasnell committed Jan 24, 2021
1 parent a35b32e commit a21eb2f
Show file tree
Hide file tree
Showing 9 changed files with 724 additions and 0 deletions.
103 changes: 103 additions & 0 deletions doc/api/crypto.md
Expand Up @@ -1961,6 +1961,48 @@ is currently in use. Setting to true requires a FIPS build of Node.js.
This property is deprecated. Please use `crypto.setFips()` and
`crypto.getFips()` instead.

### `crypto.checkPrime(candidate[, options, [callback]])`
<!-- YAML
added: REPLACEME
-->

* `candidate` {ArrayBuffer|SharedArrayBuffer|TypedArray|Buffer|DataView|bigint}
A possible prime encoded as a sequence of big endian octets of arbitrary
length.
* `options` {Object}
* `checks` {number} The number of primality checks to perform. When the
value is `0` (zero), a number of checks is used that yields a false
positive rate of at most 2^-64 for random input. Care must be used
when selecting a number of checks. Refer to the OpenSSL documentation
for the [`BN_is_prime_ex`][] function `nchecks` options for more details.
**Defaults**: `0`
* `callback` {Function}
* `err` {Error} Set to an {Error} object if an error occured during check.
* `result` {boolean} `true` if the candidate is a prime with an error
probability less than `0.25^options.checks`.

Checks the primality of the `candidate`.

### `crypto.checkPrimeSync(candidate[, options])`
<!-- YAML
added: REPLACEME
-->

* `candidate` {ArrayBuffer|SharedArrayBuffer|TypedArray|Buffer|DataView|bigint}
A possible prime encoded as a sequence of big endian octets of arbitrary
length.
* `options` {Object}
* `checks` {number} The number of primality checks to perform. When the
value is `0` (zero), a number of checks is used that yields a false
positive rate of at most 2^-64 for random input. Care must be used
when selecting a number of checks. Refer to the OpenSSL documentation
for the [`BN_is_prime_ex`][] function `nchecks` options for more details.
**Defaults**: `0`
* Returns: {boolean} `true` if the candidate is a prime with an error
probability less than `0.25^options.checks`.

Checks the primality of the `candidate`.

### `crypto.createCipher(algorithm, password[, options])`
<!-- YAML
added: v0.1.94
Expand Down Expand Up @@ -3432,6 +3474,66 @@ const n = crypto.randomInt(1, 7);
console.log(`The dice rolled: ${n}`);
```

### `crypto.randomPrime(size[, options[, callback]])`
<!-- YAML
added: REPLACEME
-->

* `size` {number} The size (in bits) of the prime to generate.
* `options` {Object}
* `add` {ArrayBuffer|SharedArrayBuffer|TypedArray|Buffer|DataView}
* `rem` {ArrayBuffer|SharedArrayBuffer|TypedArray|Buffer|DataView}
* `safe` {boolean}
* `bigint` {boolean} When `true`, the generated prime is returned
as a `bigint`.
* `callback` {Function}
* `err` {Error}
* `prime` {ArrayBuffer|bigint}

Generates a pseudo-random prime of `size` bits.

If `options.safe` is true, the prime will be a safe prime -- that is,
`(prime - 1) / 2` will also be a prime.

If `options.add` and `options.rem` are set, the prime will satisfy the
condition that prime % add = rem. The `options.rem` is ignored if
`options.add` is not given. If `options.safe` is `true`, `options.add`
is given, and `options.rem` is `undefined`, then th prime generated
will satisfy the condition `prime % add = 3`. Otherwise if `options.safe`
is `false` and `options.rem` is `undefined`, `options.add` will be
ignored.

The prime is encoded as a big-endian sequence of octets in an {ArrayBuffer}.

### `crypto.randomPrimeSync(size[, options])`
<!-- YAML
added: REPLACEME
-->

* `size` {number} The size (in bits) of the prime to generate.
* `options` {Object}
* `add` {ArrayBuffer|SharedArrayBuffer|TypedArray|Buffer|DataView}
* `rem` {ArrayBuffer|SharedArrayBuffer|TypedArray|Buffer|DataView}
* `safe` {boolean}
* `bigint` {boolean} When `true`, the generated prime is returned
as a `bigint`.
* Returns: {ArrayBuffer|bigint}

Generates a pseudo-random prime of `size` bits.

If `options.safe` is true, the prime will be a safe prime -- that is,
`(prime - 1)` / 2 will also be a prime.

If `options.add` and `options.rem` are set, the prime will satisfy the
condition that prime % add = rem. The `options.rem` is ignored if
`options.add` is not given. If `options.safe` is `true`, `options.add`
is given, and `options.rem` is `undefined`, then th prime generated
will satisfy the condition `prime % add = 3`. Otherwise if `options.safe`
is `false` and `options.rem` is `undefined`, `options.add` will be
ignored.

The prime is encoded as a big-endian sequence of octets in an {ArrayBuffer}.

### `crypto.randomUUID([options])`
<!-- YAML
added: v15.6.0
Expand Down Expand Up @@ -4234,6 +4336,7 @@ See the [list of SSL OP Flags][] for details.
[RFC 4122]: https://www.rfc-editor.org/rfc/rfc4122.txt
[RFC 5208]: https://www.rfc-editor.org/rfc/rfc5208.txt
[Web Crypto API documentation]: webcrypto.md
[`BN_is_prime_ex`]: https://www.openssl.org/docs/man1.1.1/man3/BN_is_prime_ex.html
[`Buffer`]: buffer.md
[`EVP_BytesToKey`]: https://www.openssl.org/docs/man1.1.0/crypto/EVP_BytesToKey.html
[`KeyObject`]: #crypto_class_keyobject
Expand Down
8 changes: 8 additions & 0 deletions lib/crypto.js
Expand Up @@ -50,10 +50,14 @@ const {
timingSafeEqual,
} = internalBinding('crypto');
const {
checkPrime,
checkPrimeSync,
randomBytes,
randomFill,
randomFillSync,
randomInt,
randomPrime,
randomPrimeSync,
randomUUID,
} = require('internal/crypto/random');
const {
Expand Down Expand Up @@ -170,6 +174,8 @@ function createVerify(algorithm, options) {

module.exports = {
// Methods
checkPrime,
checkPrimeSync,
createCipheriv,
createDecipheriv,
createDiffieHellman,
Expand Down Expand Up @@ -204,6 +210,8 @@ module.exports = {
randomFill,
randomFillSync,
randomInt,
randomPrime,
randomPrimeSync,
randomUUID,
scrypt,
scryptSync,
Expand Down
178 changes: 178 additions & 0 deletions lib/internal/crypto/random.js
@@ -1,6 +1,7 @@
'use strict';

const {
BigInt,
FunctionPrototypeBind,
FunctionPrototypeCall,
MathMin,
Expand All @@ -10,6 +11,8 @@ const {

const {
RandomBytesJob,
RandomPrimeJob,
CheckPrimeJob,
kCryptoJobAsync,
kCryptoJobSync,
secureBuffer,
Expand All @@ -34,6 +37,7 @@ const {
validateBoolean,
validateCallback,
validateObject,
validateUint32,
} = require('internal/validators');

const {
Expand Down Expand Up @@ -387,11 +391,185 @@ function randomUUID(options) {
return uuid.latin1Slice(0, 36);
}

function randomPrime(size, options, callback) {
validateUint32(size, 'size', true);
if (typeof options === 'function') {
callback = options;
options = {};
}
validateCallback(callback);
validateObject(options, 'options');
const {
safe = false,
add,
rem,
bigint = false,
} = options;
validateBoolean(safe, 'options.safe');
validateBoolean(bigint, 'options.bigint');

if (add !== undefined && !isAnyArrayBuffer(add) && !isArrayBufferView(add)) {
throw new ERR_INVALID_ARG_TYPE(
'options.add',
[
'ArrayBuffer',
'TypedArray',
'Buffer',
'DataView'
],
add);
}

if (rem !== undefined && !isAnyArrayBuffer(rem) && !isArrayBufferView(rem)) {
throw new ERR_INVALID_ARG_TYPE(
'options.rem',
[
'ArrayBuffer',
'TypedArray',
'Buffer',
'DataView'
],
rem);
}

const job = new RandomPrimeJob(kCryptoJobAsync, size, safe, add, rem);
job.ondone = (err, prime) => {
if (err) {
callback(err);
return;
}

callback(
undefined,
bigint ?
BigInt(`0x${Buffer.from(prime).toString('hex')}`) :
prime);
};
job.run();
}

function randomPrimeSync(size, options = {}) {
validateUint32(size, 'size', true);
validateObject(options, 'options');
const {
safe = false,
add,
rem,
bigint = false,
} = options;
validateBoolean(safe, 'options.safe');
validateBoolean(bigint, 'options.bigint');

if (add !== undefined && !isAnyArrayBuffer(add) && !isArrayBufferView(add)) {
throw new ERR_INVALID_ARG_TYPE(
'options.add',
[
'ArrayBuffer',
'TypedArray',
'Buffer',
'DataView'
],
add);
}

if (rem !== undefined && !isAnyArrayBuffer(rem) && !isArrayBufferView(rem)) {
throw new ERR_INVALID_ARG_TYPE(
'options.rem',
[
'ArrayBuffer',
'TypedArray',
'Buffer',
'DataView'
],
rem);
}

const job = new RandomPrimeJob(kCryptoJobSync, size, safe, add, rem);
const [err, prime] = job.run();
if (err)
throw err;

return bigint ?
BigInt(`0x${Buffer.from(prime).toString('hex')}`) :
prime;
}

function toHexPadded(bigint) {
const hex = bigint.toString(16);
return hex.padStart(hex.length + (hex.length % 2), 0);
}

function checkPrime(candidate, options = {}, callback) {
if (typeof candidate === 'bigint')
candidate = Buffer.from(toHexPadded(candidate), 'hex');
if (!isAnyArrayBuffer(candidate) && !isArrayBufferView(candidate)) {
throw new ERR_INVALID_ARG_TYPE(
'candidate',
[
'ArrayBuffer',
'TypedArray',
'Buffer',
'DataView'
],
candidate
);
}
if (typeof options === 'function') {
callback = options;
options = {};
}
validateCallback(callback);
validateObject(options, 'options');
const {
checks = 0,
} = options;

validateUint32(checks, 'options.checks');

const job = new CheckPrimeJob(kCryptoJobAsync, candidate, checks);
job.ondone = callback;
job.run();
}

function checkPrimeSync(candidate, options = {}) {
if (typeof candidate === 'bigint')
candidate = Buffer.from(toHexPadded(candidate), 'hex');
if (!isAnyArrayBuffer(candidate) && !isArrayBufferView(candidate)) {
throw new ERR_INVALID_ARG_TYPE(
'candidate',
[
'ArrayBuffer',
'TypedArray',
'Buffer',
'DataView'
],
candidate
);
}
validateObject(options, 'options');
const {
checks = 0,
} = options;

validateUint32(checks, 'options.checks');

const job = new CheckPrimeJob(kCryptoJobSync, candidate, checks);
const [err, result] = job.run();
if (err)
throw err;

return result;
}

module.exports = {
checkPrime,
checkPrimeSync,
randomBytes,
randomFill,
randomFillSync,
randomInt,
getRandomValues,
randomUUID,
randomPrime,
randomPrimeSync,
};
2 changes: 2 additions & 0 deletions src/async_wrap.h
Expand Up @@ -84,6 +84,7 @@ namespace node {

#if HAVE_OPENSSL
#define NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V) \
V(CHECKPRIMEREQUEST) \
V(PBKDF2REQUEST) \
V(KEYPAIRGENREQUEST) \
V(KEYGENREQUEST) \
Expand All @@ -92,6 +93,7 @@ namespace node {
V(DERIVEBITSREQUEST) \
V(HASHREQUEST) \
V(RANDOMBYTESREQUEST) \
V(RANDOMPRIMEREQUEST) \
V(SCRYPTREQUEST) \
V(SIGNREQUEST) \
V(TLSWRAP) \
Expand Down

0 comments on commit a21eb2f

Please sign in to comment.