Skip to content
File

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

12.5 KB
1#include "dh.h"
2 
3#include "impl.h"
4 
5#include <workerd/io/io-context.h>
6 
7#include <ncrypto.h>
8#include <openssl/bn.h>
9#include <openssl/dh.h>
10 
11#include <kj/one-of.h>
12#include <kj/string.h>
13 
14namespace workerd::api {
15 
16namespace {
17 
18// Maximum DH prime size, adapted from BoringSSL. This is already defined in more recent versions.
19#ifndef OPENSSL_DH_MAX_MODULUS_BITS
20#define OPENSSL_DH_MAX_MODULUS_BITS 10000
21#endif
22 
23// Returns a function that can be used to create an instance of a standardized
24// Diffie-Hellman group.
25BIGNUM* (*findDiffieHellmanGroup(const char* name))(BIGNUM*) {
26#if _WIN32
27#define V(n, p) \
28 if (_strnicmp(name, n, 7) == 0) { \
29 return p; \
30 }
31#else
32#define V(n, p) \
33 if (strncasecmp(name, n, 7) == 0) { \
34 return p; \
35 }
36#endif
37 // Only the following primes are supported based on security concerns about the smaller prime
38 // groups (https://www.rfc-editor.org/rfc/rfc8247#section-2.4).
39 V("modp14", BN_get_rfc3526_prime_2048);
40 V("modp15", BN_get_rfc3526_prime_3072);
41 V("modp16", BN_get_rfc3526_prime_4096);
42 V("modp17", BN_get_rfc3526_prime_6144);
43 V("modp18", BN_get_rfc3526_prime_8192);
44#undef V
45 
46 return nullptr;
47}
48 
49kj::Own<DH> initDhGroup(kj::StringPtr name) {
50 auto group = findDiffieHellmanGroup(name.begin());
51 JSG_REQUIRE(group != nullptr, Error,
52 "Failed to init DiffieHellmanGroup: invalid group. Only "
53 "groups {modp14, modp15, modp16, modp17, modp18} are supported.");
54 auto groupKey = group(nullptr);
55 KJ_ASSERT(groupKey != nullptr);
56 
57 const int kStandardizedGenerator = 2;
58 auto dh = OSSL_NEW(DH);
59 
60 // Note: We're deliberately not using kj::Own/OSSL_NEW() here as DH_set0_pqg() takes ownership
61 // of the key, so there is no need to free it if the operation succeeds.
62 UniqueBignum bn_g(BN_new(), &BN_clear_free);
63 if (!BN_set_word(bn_g.get(), kStandardizedGenerator) ||
64 !DH_set0_pqg(dh, groupKey, nullptr, bn_g.get())) {
65 JSG_FAIL_REQUIRE(Error, "DiffieHellmanGroup init failed: could not set keys");
66 }
67 bn_g.release();
68 return kj::mv(dh);
69}
70 
71kj::Own<DH> initDh(kj::OneOf<kj::Array<kj::byte>, int>& sizeOrKey,
72 kj::OneOf<kj::Array<kj::byte>, int>& generator) {
73 KJ_SWITCH_ONEOF(sizeOrKey) {
74 KJ_CASE_ONEOF(size, int) {
75 KJ_SWITCH_ONEOF(generator) {
76 KJ_CASE_ONEOF(gen, int) {
77 // Generating a DH key with a reasonable size can be expensive.
78 // We will only allow it if there is an active IoContext so that
79 // we can enforce a timeout associate with the limit enforcer.
80 auto& ioContext = JSG_REQUIRE_NONNULL(IoContext::tryCurrent(), Error,
81 "DiffieHellman key generation requires an active request");
82 
83 struct Status {
84 IoContext& context;
85 kj::Maybe<EventOutcome> status;
86 } status{.context = ioContext};
87 
88 auto dh = OSSL_NEW(DH);
89 BN_GENCB cb;
90 cb.arg = &status;
91 // This callback is called many times during the key generation process.
92 // We use it because key generation is expensive and may run over the
93 // CPU limits for the request. As this method can itself contribute to
94 // running over the CPU limit, it is important to do as little as possible.
95 cb.callback = [](int a, int b, BN_GENCB* cb) -> int {
96 Status& status = *static_cast<Status*>(cb->arg);
97 KJ_IF_SOME(outcome, status.context.getLimitEnforcer().getLimitsExceeded()) {
98 status.status = outcome;
99 return 0;
100 }
101 return 1;
102 };
103 // Operations on an "egregiously large" prime will throw with recent BoringSSL.
104 JSG_REQUIRE(size <= OPENSSL_DH_MAX_MODULUS_BITS, RangeError,
105 "DiffieHellman init failed: requested prime size too large");
106 if (!DH_generate_parameters_ex(dh.get(), size, gen, &cb)) {
107 KJ_IF_SOME(outcome, status.status) {
108 if (outcome == EventOutcome::EXCEEDED_CPU) {
109 JSG_FAIL_REQUIRE(
110 Error, "DiffieHellman init failed: key generation exceeded CPU limit");
111 } else if (outcome == EventOutcome::EXCEEDED_MEMORY) {
112 JSG_FAIL_REQUIRE(
113 Error, "DiffieHellman init failed: key generation exceeded memory limit");
114 }
115 }
116 JSG_FAIL_REQUIRE(Error, "DiffieHellman init failed: could not generate parameters");
117 }
118 // Boringssl throws on DH with g >= p or p | 2 since g can't be an element of p's
119 // multiplicative group in that case.
120 JSG_REQUIRE(BN_is_odd(DH_get0_p(dh)) && BN_ucmp(DH_get0_g(dh), DH_get0_p(dh)) < 0, Error,
121 "DiffieHellman init failed: Invalid DH prime generated");
122 return kj::mv(dh);
123 }
124 KJ_CASE_ONEOF(gen, kj::Array<kj::byte>) {
125 // Node.js does not support generating Diffie-Hellman keys from an int prime
126 // and byte-array generator. This could change in the future.
127 JSG_FAIL_REQUIRE(Error, "DiffieHellman init failed: invalid parameters");
128 }
129 }
130 }
131 KJ_CASE_ONEOF(key, kj::Array<kj::byte>) {
132 // Operations on an "egregiously large" prime will throw with BoringSSL.
133 JSG_REQUIRE(key.size() <= OPENSSL_DH_MAX_MODULUS_BITS / CHAR_BIT, RangeError,
134 "DiffieHellman init failed: key is too large");
135 JSG_REQUIRE(key.size() > 0, Error, "DiffieHellman init failed: invalid key");
136 auto dh = OSSL_NEW(DH);
137 
138 // We use a std::unique_ptr here instead of a kj::Own because DH_set0_pqg takes ownership
139 // and we need to be able to release ownership if the operation succeeds but want the
140 // BIGNUMs to be appropriately freed if the operations fail.
141 using UniqueBignum = std::unique_ptr<BIGNUM, void (*)(BIGNUM*)>;
142 UniqueBignum bn_g(nullptr, &BN_clear_free);
143 
144 KJ_SWITCH_ONEOF(generator) {
145 KJ_CASE_ONEOF(gen, int) {
146 JSG_REQUIRE(gen >= 2, RangeError, "DiffieHellman init failed: generator too small");
147 bn_g.reset(BN_new());
148 if (!BN_set_word(bn_g.get(), gen)) {
149 JSG_FAIL_REQUIRE(Error, "DiffieHellman init failed: could not set keys");
150 }
151 }
152 KJ_CASE_ONEOF(gen, kj::Array<kj::byte>) {
153 JSG_REQUIRE(gen.size() <= OPENSSL_DH_MAX_MODULUS_BITS / CHAR_BIT, RangeError,
154 "DiffieHellman init failed: generator is too large");
155 JSG_REQUIRE(gen.size() > 0, Error, "DiffieHellman init failed: invalid generator");
156 
157 bn_g.reset(toBignumUnowned(gen));
158 if (BN_is_zero(bn_g.get()) || BN_is_one(bn_g.get())) {
159 JSG_FAIL_REQUIRE(Error, "DiffieHellman init failed: invalid generator");
160 }
161 }
162 }
163 UniqueBignum bn_p(toBignumUnowned(key), &BN_clear_free);
164 JSG_REQUIRE(bn_p != nullptr, Error,
165 "DiffieHellman init failed: could not convert key representation");
166 // Boringssl throws on DH with g >= p or p | 2 since g can't be an element of p's
167 // multiplicative group in that case.
168 JSG_REQUIRE(BN_is_odd(bn_p.get()) && BN_ucmp(bn_g.get(), bn_p.get()) < 0, Error,
169 "DiffieHellman init failed: Invalid DH prime generated");
170 JSG_REQUIRE(DH_set0_pqg(dh, bn_p.get(), nullptr, bn_g.get()), Error,
171 "DiffieHellman init failed: could not set keys");
172 bn_g.release();
173 bn_p.release();
174 return kj::mv(dh);
175 }
176 }
177 KJ_UNREACHABLE;
178}
179 
180void zeroPadDiffieHellmanSecret(size_t remainder_size, unsigned char* data, size_t prime_size) {
181 // DH_size returns number of bytes in a prime number.
182 // DH_compute_key returns number of bytes in a remainder of exponent, which
183 // may have less bytes than a prime number. Therefore add 0-padding to the
184 // allocated buffer.
185 if (remainder_size != prime_size) {
186 KJ_ASSERT(remainder_size < prime_size);
187 const size_t padding = prime_size - remainder_size;
188 memmove(data + padding, data, remainder_size);
189 kj::arrayPtr(data, padding).fill(0);
190 }
191}
192} // namespace
193 
194DiffieHellman::DiffieHellman(kj::StringPtr group): dh(initDhGroup(group)) {}
195 
196DiffieHellman::DiffieHellman(
197 kj::OneOf<kj::Array<kj::byte>, int>& sizeOrKey, kj::OneOf<kj::Array<kj::byte>, int>& generator)
198 : dh(initDh(sizeOrKey, generator)) {}
199 
200kj::Maybe<int> DiffieHellman::check() {
201 ClearErrorOnReturn clearErrorOnReturn;
202 int codes;
203 if (!DH_check(dh.get(), &codes)) {
204 return kj::none;
205 }
206 return codes;
207}
208 
209void DiffieHellman::setPrivateKey(kj::ArrayPtr<kj::byte> key) {
210 auto bn = toBignumOwned(key);
211 OSSLCALL(DH_set0_key(dh, nullptr, bn.get()));
212 bn.release();
213}
214 
215void DiffieHellman::setPublicKey(kj::ArrayPtr<kj::byte> key) {
216 auto bn = toBignumOwned(key);
217 
218 int checkResult;
219 JSG_REQUIRE(DH_check_pub_key(dh, bn.get(), &checkResult), Error,
220 "DiffieHellman setPublicKey() failed: could not validate public key");
221 if (checkResult) {
222 JSG_REQUIRE(!(checkResult & DH_CHECK_PUBKEY_TOO_SMALL), RangeError,
223 "DiffieHellman setPublicKey() failed: key is too small");
224 JSG_REQUIRE(!(checkResult & DH_CHECK_PUBKEY_TOO_LARGE), RangeError,
225 "DiffieHellman setPublicKey() failed: key is too large");
226 JSG_FAIL_REQUIRE(Error, "DiffieHellman setPublicKey() failed: invalid public key");
227 }
228 
229 OSSLCALL(DH_set0_key(dh, bn.get(), nullptr));
230 bn.release();
231}
232 
233jsg::JsUint8Array DiffieHellman::getPublicKey(jsg::Lock& js) {
234 const BIGNUM* pub_key = DH_get0_pub_key(dh);
235 JSG_REQUIRE(pub_key != nullptr, Error, "No public key");
236 return JSG_REQUIRE_NONNULL(
237 bignumToArrayPadded(js, *pub_key), Error, "Error while retrieving DiffieHellman public key");
238}
239 
240jsg::JsUint8Array DiffieHellman::getPrivateKey(jsg::Lock& js) {
241 const BIGNUM* priv_key = DH_get0_priv_key(dh);
242 JSG_REQUIRE(priv_key != nullptr, Error, "No private key");
243 return JSG_REQUIRE_NONNULL(bignumToArrayPadded(js, *priv_key), Error,
244 "Error while retrieving DiffieHellman private key");
245}
246 
247jsg::JsUint8Array DiffieHellman::getGenerator(jsg::Lock& js) {
248 const BIGNUM* g = DH_get0_g(dh);
249 JSG_REQUIRE(g != nullptr, Error, "No DiffieHellman generator");
250 return JSG_REQUIRE_NONNULL(
251 bignumToArrayPadded(js, *g), Error, "Error while retrieving DiffieHellman generator");
252}
253 
254jsg::JsUint8Array DiffieHellman::getPrime(jsg::Lock& js) {
255 const BIGNUM* p = DH_get0_p(dh);
256 JSG_REQUIRE(p != nullptr, Error, "No DiffieHellman prime");
257 return JSG_REQUIRE_NONNULL(
258 bignumToArrayPadded(js, *p), Error, "Error while retrieving DiffieHellman prime");
259}
260 
261jsg::JsUint8Array DiffieHellman::computeSecret(jsg::Lock& js, kj::ArrayPtr<kj::byte> key) {
262 JSG_REQUIRE(key.size() <= INT32_MAX, RangeError,
263 "DiffieHellman computeSecret() failed: key is too large");
264 JSG_REQUIRE(key.size() > 0, Error, "DiffieHellman computeSecret() failed: invalid key");
265 
266 ClearErrorOnReturn clear_error_on_return;
267 auto k = JSG_REQUIRE_NONNULL(
268 toBignum(key), Error, "Error getting key while computing DiffieHellman secret");
269 
270 // Validate the peer's public key before computing the shared secret.
271 int checkResult;
272 JSG_REQUIRE(DH_check_pub_key(dh, k, &checkResult), Error,
273 "DiffieHellman computeSecret() failed: could not validate peer public key");
274 if (checkResult) {
275 JSG_REQUIRE(!(checkResult & DH_CHECK_PUBKEY_TOO_SMALL), RangeError,
276 "DiffieHellman computeSecret() failed: Supplied key is too small");
277 JSG_REQUIRE(!(checkResult & DH_CHECK_PUBKEY_TOO_LARGE), RangeError,
278 "DiffieHellman computeSecret() failed: Supplied key is too large");
279 JSG_FAIL_REQUIRE(Error, "DiffieHellman computeSecret() failed: invalid peer public key");
280 }
281 
282 size_t prime_size = DH_size(dh);
283 auto buf = jsg::JsUint8Array::create(js, prime_size);
284 
285 int size = DH_compute_key(buf.asArrayPtr().begin(), k.get(), dh);
286 JSG_REQUIRE(
287 size != -1, Error, "DiffieHellman computeSecret() failed: error computing shared secret");
288 
289 KJ_ASSERT(size >= 0);
290 zeroPadDiffieHellmanSecret(size, buf.asArrayPtr().begin(), prime_size);
291 return buf;
292}
293 
294jsg::JsUint8Array DiffieHellman::generateKeys(jsg::Lock& js) {
295 ClearErrorOnReturn clear_error_on_return;
296 OSSLCALL(DH_generate_key(dh));
297 const BIGNUM* pub_key = DH_get0_pub_key(dh);
298 return JSG_REQUIRE_NONNULL(
299 bignumToArrayPadded(js, *pub_key), Error, "Error while generating DiffieHellman keys");
300}
301 
302} // namespace workerd::api