Skip to content
File

Blob: src/workerd/api/crypto/prime.c++

3.9 KB
1#include "prime.h"
2 
3#include "impl.h"
4 
5#include <workerd/jsg/jsg.h>
6 
7#include <ncrypto.h>
8 
9namespace workerd::api {
10 
11jsg::JsArrayBuffer randomPrime(jsg::Lock& js,
12 uint32_t size,
13 bool safe,
14 kj::Maybe<kj::ArrayPtr<kj::byte>> add_buf,
15 kj::Maybe<kj::ArrayPtr<kj::byte>> rem_buf) {
16 ncrypto::ClearErrorOnReturn clearErrorOnReturn;
17 
18 // Use mapping to have kj::Own work with optional buffer
19 static const auto toBignum =
20 [](kj::Maybe<kj::ArrayPtr<kj::byte>>& maybeBignum) -> ncrypto::BignumPointer {
21 KJ_IF_SOME(a, maybeBignum) {
22 if (auto bn = ncrypto::BignumPointer(a.begin(), a.size())) {
23 return bn;
24 }
25 JSG_FAIL_REQUIRE(
26 RangeError, "Error importing add parameter", internalDescribeOpensslErrors());
27 };
28 return {};
29 };
30 
31 auto add = toBignum(add_buf);
32 auto rem = toBignum(rem_buf);
33 // The JS interface already ensures that the (positive) size fits into an int.
34 int bits = static_cast<int>(size);
35 
36 if (add) {
37 // Currently, we only allow certain values for add and rem due to a bug in
38 // the BN_generate_prime_ex that allows invalid values to enter an infinite
39 // loop. This diverges from the Node.js implementation a bit but that's OK.
40 // The key use case for this function is generating DH parameters and those
41 // have pretty specific values for various generators anyway.
42 // Specifically, we limit the values of add and rem to match the specific
43 // pairings: add: 12, rem 11, add 24, rem 23, and add 60, rem 59.
44 // If users complain about this, we can always remove this check and try
45 // to get the infinite loop bug fixed.
46 
47 auto addCheck = ncrypto::BignumPointer::New();
48 auto remCheck = ncrypto::BignumPointer::New();
49 const auto checkAddRem = [&](auto anum, auto bnum) {
50 addCheck.setWord(anum);
51 remCheck.setWord(bnum);
52 return BN_cmp(add.get(), addCheck.get()) == 0 && BN_cmp(rem.get(), remCheck.get()) == 0;
53 };
54 
55 JSG_REQUIRE(rem && (checkAddRem(12, 11) || checkAddRem(24, 23) || checkAddRem(60, 59)),
56 RangeError, "Invalid values for add and rem");
57 
58 // This would definitely lead to an infinite loop if allowed since
59 // OpenSSL does not check this condition.
60 JSG_REQUIRE(add > rem, RangeError, "options.rem must be smaller than options.add");
61 
62 // If we allowed this, the best case would be returning a static prime
63 // that wasn't generated randomly. The worst case would be an infinite
64 // loop within OpenSSL, blocking the main thread or one of the threads
65 // in the thread pool.
66 JSG_REQUIRE(add.bitLength() <= bits, RangeError,
67 "options.add must not be bigger than size of the requested prime");
68 }
69 
70 // Generating random primes uses the PRNG internally.
71 // Make sure the CSPRNG is properly seeded.
72 JSG_REQUIRE(
73 workerd::api::CSPRNG(nullptr), Error, "Error while generating prime (bad random state)");
74 
75 if (auto prime = ncrypto::BignumPointer::NewPrime({
76 .bits = bits,
77 .safe = safe,
78 .add = kj::mv(add),
79 .rem = kj::mv(rem),
80 })) {
81 auto buf = JSG_REQUIRE_NONNULL(
82 bignumToArrayPadded(js, *prime.get()), Error, "Error while generating prime");
83 return jsg::JsArrayBuffer::create(js, buf.asArrayPtr());
84 }
85 
86 JSG_FAIL_REQUIRE(Error, "Error while generating prime");
87}
88 
89bool checkPrime(kj::ArrayPtr<kj::byte> bufferView, uint32_t num_checks) {
90 ncrypto::ClearErrorOnReturn clearErrorOnReturn;
91 static constexpr int32_t kMaxChecks = kj::maxValue;
92 // Strictly upper bound the number of checks. If this proves to be too expensive
93 // then we may need to consider lowering this limit further.
94 JSG_REQUIRE(num_checks <= kMaxChecks, RangeError, "Invalid number of checks");
95 
96 auto candidate = ncrypto::BignumPointer(bufferView.begin(), bufferView.size());
97 JSG_REQUIRE(candidate, Error, "Error while checking prime");
98 return candidate.isPrime(num_checks);
99}
100 
101} // namespace workerd::api