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 19, 2021
1 parent fef2128 commit cda64b6
Show file tree
Hide file tree
Showing 7 changed files with 511 additions and 0 deletions.
65 changes: 65 additions & 0 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -1961,6 +1961,25 @@ 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])`
<!-- YAML
added: REPLACEME
-->

* `candidate` {ArrayBuffer|SharedArrayBuffer|TypedArray|Buffer|DataView} 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.
**Defaults**: `1`
* `fast` {boolean} `true` to use the fast check algorithm.
* `trialDivision` {boolean} When using the fast check algorithm, `true`
to enable trial division.
* Returns: {boolean} `true` if the number is 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 +3451,52 @@ 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 bytes) of the prime to generate.
* `options` {Object}
* `add` {ArrayBuffer|SharedArrayBuffer|TypedArray|Buffer|DataView}
* `rem` {ArrayBuffer|SharedArrayBuffer|TypedArray|Buffer|DataView}
* `safe` {boolean}
* `callback` {Function}
* `err` {Error}
* `prime` {ArrayBuffer}

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

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 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 bytes) of the prime to generate.
* `options` {Object}
* `add` {ArrayBuffer|SharedArrayBuffer|TypedArray|Buffer|DataView}
* `rem` {ArrayBuffer|SharedArrayBuffer|TypedArray|Buffer|DataView}
* `safe` {boolean}
* Returns: {ArrayBuffer}

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

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 prime is encoded as a big-endian sequence of octets in an {ArrayBuffer}.

### `crypto.randomUUID([options])`
<!-- YAML
added: v15.6.0
Expand Down
6 changes: 6 additions & 0 deletions lib/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,13 @@ const {
timingSafeEqual,
} = internalBinding('crypto');
const {
checkPrime,
randomBytes,
randomFill,
randomFillSync,
randomInt,
randomPrime,
randomPrimeSync,
randomUUID,
} = require('internal/crypto/random');
const {
Expand Down Expand Up @@ -170,6 +173,7 @@ function createVerify(algorithm, options) {

module.exports = {
// Methods
checkPrime,
createCipheriv,
createDecipheriv,
createDiffieHellman,
Expand Down Expand Up @@ -204,6 +208,8 @@ module.exports = {
randomFill,
randomFillSync,
randomInt,
randomPrime,
randomPrimeSync,
randomUUID,
scrypt,
scryptSync,
Expand Down
117 changes: 117 additions & 0 deletions lib/internal/crypto/random.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ const {

const {
RandomBytesJob,
RandomPrimeJob,
kCryptoJobAsync,
kCryptoJobSync,
secureBuffer,
checkPrime: _checkPrime,
} = internalBinding('crypto');

const {
Expand All @@ -34,6 +36,7 @@ const {
validateBoolean,
validateCallback,
validateObject,
validateUint32,
} = require('internal/validators');

const {
Expand Down Expand Up @@ -387,11 +390,125 @@ 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,
} = options;
validateBoolean(safe, 'options.safe');

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 = callback;
job.run();
}

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

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 prime;
}

function checkPrime(candidate, options = {}) {
if (!isAnyArrayBuffer(candidate) && !isArrayBufferView(candidate)) {
throw new ERR_INVALID_ARG_TYPE(
'candidate',
[
'ArrayBuffer',
'TypedArray',
'Buffer',
'DataView'
],
candidate
);
}
validateObject(options, 'options');
const {
fast = false,
checks = 1,
trialDivision = false
} = options;
validateBoolean(fast, 'options.fast');
validateBoolean(trialDivision, 'options.trialDivision');
validateUint32(checks, 'options.checks', true);
return _checkPrime(candidate, fast, checks, trialDivision);
}

module.exports = {
checkPrime,
randomBytes,
randomFill,
randomFillSync,
randomInt,
getRandomValues,
randomUUID,
randomPrime,
randomPrimeSync,
};
120 changes: 120 additions & 0 deletions src/crypto/crypto_random.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@
#include "threadpoolwork-inl.h"
#include "v8.h"

#include <openssl/bn.h>

namespace node {

using v8::ArrayBuffer;
using v8::BackingStore;
using v8::FunctionCallbackInfo;
using v8::Just;
using v8::Local;
Expand Down Expand Up @@ -64,9 +68,125 @@ bool RandomBytesTraits::DeriveBits(
return RAND_bytes(params.buffer, params.size) != 0;
}

void RandomPrimeConfig::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackFieldWithSize("prime", prime ? bits * 8 : 0);
}

Maybe<bool> RandomPrimeTraits::EncodeOutput(
Environment* env,
const RandomPrimeConfig& params,
ByteSource* unused,
v8::Local<v8::Value>* result) {
size_t size = BN_num_bytes(params.prime.get());
std::shared_ptr<BackingStore> store =
ArrayBuffer::NewBackingStore(env->isolate(), size);
BN_bn2binpad(
params.prime.get(),
reinterpret_cast<unsigned char*>(store->Data()),
size);
*result = ArrayBuffer::New(env->isolate(), store);
return Just(true);
}

Maybe<bool> RandomPrimeTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
RandomPrimeConfig* params) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[offset]->IsUint32()); // Size
CHECK(args[offset + 1]->IsBoolean()); // Safe

const uint32_t size = args[offset].As<Uint32>()->Value();
bool safe = args[offset + 1]->IsTrue();

if (!args[offset + 2]->IsUndefined()) {
params->add.reset(BN_new());
ArrayBufferOrViewContents<unsigned char> add(args[offset + 2]);
BN_bin2bn(add.data(), add.size(), params->add.get());
if (!params->add) {
THROW_ERR_INVALID_ARG_VALUE(env, "invalid options.add");
return Nothing<bool>();
}
}

if (!args[offset + 3]->IsUndefined()) {
params->rem.reset(BN_new());
ArrayBufferOrViewContents<unsigned char> rem(args[offset + 3]);
BN_bin2bn(rem.data(), rem.size(), params->rem.get());
if (!params->rem) {
THROW_ERR_INVALID_ARG_VALUE(env, "invalid options.rem");
return Nothing<bool>();
}
}

int bits = static_cast<int>(size * 8);
if (bits < 0) {
THROW_ERR_OUT_OF_RANGE(env, "invalid size");
return Nothing<bool>();
}

params->bits = bits;
params->safe = safe;
params->prime.reset(BN_new());

return Just(true);
}

bool RandomPrimeTraits::DeriveBits(
Environment* env,
const RandomPrimeConfig& params,
ByteSource* unused) {

CheckEntropy();

if (BN_generate_prime_ex(
params.prime.get(),
params.bits,
params.safe ? 1 : 0,
params.add.get(),
params.rem.get(),
nullptr) == 0) {
return false;
}

return true;
}

void CheckPrime(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
ArrayBufferOrViewContents<unsigned char> candidate(args[0]);
bool fast = args[1]->IsTrue();
int checks = static_cast<int>(args[2].As<Uint32>()->Value());
CHECK_GE(checks, 1);
bool trial_division = args[3]->IsTrue();

BignumCtxPointer ctx(BN_CTX_new());
BignumPointer check(BN_bin2bn(candidate.data(), candidate.size(), nullptr));

int ret = fast
? BN_is_prime_fasttest_ex(
check.get(),
checks,
ctx.get(),
trial_division ? 1 : 0,
nullptr)
: BN_is_prime_ex(check.get(), checks, ctx.get(), nullptr);
switch (ret) {
case -1:
return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Error checking the prime");
case 0:
return args.GetReturnValue().Set(false);
default:
return args.GetReturnValue().Set(true);
}
}

namespace Random {
void Initialize(Environment* env, Local<Object> target) {
RandomBytesJob::Initialize(env, target);
RandomPrimeJob::Initialize(env, target);
env->SetMethod(target, "checkPrime", CheckPrime);
}
} // namespace Random
} // namespace crypto
Expand Down
Loading

0 comments on commit cda64b6

Please sign in to comment.