Skip to content

Commit f072094

Browse files
authored
Merge pull request #3127 from cloudflare/jsnell/crypto-allocations-using-buffersource
2 parents f5cf6bf + 8ce6016 commit f072094

File tree

21 files changed

+440
-302
lines changed

21 files changed

+440
-302
lines changed

src/workerd/api/crypto/aes-test.c++

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,6 @@ KJ_TEST("AES-KW key wrap") {
2929
// ASAN/valgrind than using our conformance tests with test-runner.
3030
jsg::test::Evaluator<CryptoContext, CryptoIsolate> e(v8System);
3131
e.getIsolate().runInLockScope([&](CryptoIsolate::Lock& isolateLock) {
32-
auto isolate = isolateLock.v8Isolate;
33-
auto& js = jsg::Lock::from(isolate);
34-
3532
auto rawWrappingKeys = std::array<kj::Array<kj::byte>, 3>({
3633
kj::heapArray<kj::byte>({0xe6, 0x95, 0xea, 0xe3, 0xa8, 0xc0, 0x30, 0xf1, 0x76, 0xe3, 0x0e,
3734
0x8e, 0x36, 0xf8, 0xf4, 0x31}),
@@ -51,32 +48,35 @@ KJ_TEST("AES-KW key wrap") {
5148
};
5249
bool extractable = false;
5350

54-
return CryptoKey::Impl::importAes(js, "AES-KW", "raw", kj::mv(rawKey), kj::mv(algorithm),
55-
extractable, {kj::str("wrapKey"), kj::str("unwrapKey")});
51+
return CryptoKey::Impl::importAes(isolateLock, "AES-KW", "raw", kj::mv(rawKey),
52+
kj::mv(algorithm), extractable, {kj::str("wrapKey"), kj::str("unwrapKey")});
5653
};
5754

5855
auto keyMaterial = kj::heapArray<const kj::byte>(
5956
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24});
6057

61-
for (const auto& aesKey: aesKeys) {
62-
SubtleCrypto::EncryptAlgorithm params;
63-
params.name = kj::str("AES-KW");
58+
JSG_WITHIN_CONTEXT_SCOPE(isolateLock,
59+
isolateLock.newContext<CryptoContext>().getHandle(isolateLock), [&](jsg::Lock& js) {
60+
for (const auto& aesKey: aesKeys) {
61+
SubtleCrypto::EncryptAlgorithm params;
62+
params.name = kj::str("AES-KW");
6463

65-
auto wrapped = aesKey->wrapKey(kj::mv(params), keyMaterial.asPtr());
64+
auto wrapped = aesKey->wrapKey(js, kj::mv(params), keyMaterial.asPtr());
6665

67-
params = {};
68-
params.name = kj::str("AES-KW");
66+
params = {};
67+
params.name = kj::str("AES-KW");
6968

70-
auto unwrapped = aesKey->unwrapKey(kj::mv(params), wrapped);
69+
auto unwrapped = aesKey->unwrapKey(js, kj::mv(params), wrapped);
7170

72-
KJ_EXPECT(unwrapped == keyMaterial);
71+
KJ_EXPECT(unwrapped.asArrayPtr() == keyMaterial);
7372

74-
// Corruption of wrapped key material should throw.
75-
params = {};
76-
params.name = kj::str("AES-KW");
77-
wrapped[5] += 1;
78-
KJ_EXPECT_THROW_MESSAGE("[24 == -1]", aesKey->unwrapKey(kj::mv(params), wrapped));
79-
}
73+
// Corruption of wrapped key material should throw.
74+
params = {};
75+
params.name = kj::str("AES-KW");
76+
wrapped.asArrayPtr()[5] += 1;
77+
KJ_EXPECT_THROW_MESSAGE("[24 == -1]", aesKey->unwrapKey(js, kj::mv(params), wrapped));
78+
}
79+
});
8080
});
8181
}
8282

@@ -136,14 +136,15 @@ KJ_TEST("AES-CTR key wrap") {
136136
return subtle.wrapKey(js, kj::str("raw"), *toWrap, *wrappingKey, getEnc(), *jwkHandler);
137137
})
138138
.then(js,
139-
[&](jsg::Lock&, kj::Array<kj::byte> wrapped) {
140-
return subtle.unwrapKey(js, kj::str("raw"), kj::mv(wrapped), *wrappingKey, getEnc(),
139+
[&](jsg::Lock&, jsg::BufferSource wrapped) {
140+
auto data = kj::heapArray(wrapped.asArrayPtr());
141+
return subtle.unwrapKey(js, kj::str("raw"), kj::mv(data), *wrappingKey, getEnc(),
141142
getImportKeyAlg(), true, kj::arr(kj::str("encrypt")), *jwkHandler);
142143
})
143144
.then(js, [&](jsg::Lock& js, jsg::Ref<CryptoKey> unwrapped) {
144145
return subtle.exportKey(js, kj::str("raw"), *unwrapped);
145146
}).then(js, [&](jsg::Lock&, api::SubtleCrypto::ExportKeyData roundTrippedKeyMaterial) {
146-
KJ_ASSERT(roundTrippedKeyMaterial.get<kj::Array<kj::byte>>() == KEY_DATA);
147+
KJ_ASSERT(roundTrippedKeyMaterial.get<jsg::BufferSource>() == KEY_DATA);
147148
completed = true;
148149
});
149150

src/workerd/api/crypto/aes.c++

Lines changed: 66 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@ protected:
133133
CRYPTO_memcmp(keyData.begin(), other.begin(), keyData.size()) == 0;
134134
}
135135

136+
bool equals(const jsg::BufferSource& other) const override final {
137+
return keyData.size() == other.size() &&
138+
CRYPTO_memcmp(keyData.begin(), other.asArrayPtr().begin(), keyData.size()) == 0;
139+
}
140+
136141
kj::StringPtr jsgGetMemoryName() const override {
137142
return "AesKeyBase"_kjc;
138143
}
@@ -149,7 +154,7 @@ private:
149154
return keyAlgorithm;
150155
}
151156

152-
SubtleCrypto::ExportKeyData exportKey(kj::StringPtr format) const override final {
157+
SubtleCrypto::ExportKeyData exportKey(jsg::Lock& js, kj::StringPtr format) const override final {
153158
JSG_REQUIRE(format == "raw" || format == "jwk", DOMNotSupportedError, getAlgorithmName(),
154159
" key only supports exporting \"raw\" & \"jwk\", not \"", format, "\".");
155160

@@ -184,7 +189,10 @@ private:
184189
return jwk;
185190
}
186191

187-
return kj::heapArray(keyData.asPtr());
192+
// Every export should be a separate copy.
193+
auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, keyData.size());
194+
backing.asArrayPtr().copyFrom(keyData);
195+
return jsg::BufferSource(js, kj::mv(backing));
188196
}
189197

190198
protected:
@@ -201,7 +209,8 @@ public:
201209
: AesKeyBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable, usages) {}
202210

203211
private:
204-
kj::Array<kj::byte> encrypt(SubtleCrypto::EncryptAlgorithm&& algorithm,
212+
jsg::BufferSource encrypt(jsg::Lock& js,
213+
SubtleCrypto::EncryptAlgorithm&& algorithm,
205214
kj::ArrayPtr<const kj::byte> plainText) const override {
206215
kj::ArrayPtr<kj::byte> iv =
207216
JSG_REQUIRE_NONNULL(algorithm.iv, TypeError, "Missing field \"iv\" in \"algorithm\".");
@@ -242,31 +251,32 @@ private:
242251
// a stream cipher in that it does not add padding and can process partial blocks, meaning that
243252
// we know the exact ciphertext size in advance.
244253
auto tagByteSize = tagLength / 8;
245-
auto cipherText = kj::heapArray<kj::byte>(plainText.size() + tagByteSize);
254+
auto cipherText = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, plainText.size() + tagByteSize);
246255

247256
// Perform the actual encryption.
248257

249258
int cipherSize = 0;
250-
OSSLCALL(EVP_EncryptUpdate(
251-
cipherCtx.get(), cipherText.begin(), &cipherSize, plainText.begin(), plainText.size()));
259+
OSSLCALL(EVP_EncryptUpdate(cipherCtx.get(), cipherText.asArrayPtr().begin(), &cipherSize,
260+
plainText.begin(), plainText.size()));
252261
KJ_ASSERT(cipherSize == plainText.size(), "EVP_EncryptUpdate should encrypt all at once");
253262

254263
int finalCipherSize = 0;
255-
OSSLCALL(
256-
EVP_EncryptFinal_ex(cipherCtx.get(), cipherText.begin() + cipherSize, &finalCipherSize));
264+
OSSLCALL(EVP_EncryptFinal_ex(
265+
cipherCtx.get(), cipherText.asArrayPtr().begin() + cipherSize, &finalCipherSize));
257266
KJ_ASSERT(finalCipherSize == 0, "EVP_EncryptFinal_ex should not output any data");
258267

259268
// Concatenate the tag onto the cipher text.
260269
KJ_ASSERT(cipherSize + tagByteSize == cipherText.size(), "imminent buffer overrun");
261-
OSSLCALL(EVP_CIPHER_CTX_ctrl(
262-
cipherCtx.get(), EVP_CTRL_GCM_GET_TAG, tagByteSize, cipherText.begin() + cipherSize));
270+
OSSLCALL(EVP_CIPHER_CTX_ctrl(cipherCtx.get(), EVP_CTRL_GCM_GET_TAG, tagByteSize,
271+
cipherText.asArrayPtr().begin() + cipherSize));
263272
cipherSize += tagByteSize;
264273
KJ_ASSERT(cipherSize == cipherText.size(), "buffer overrun");
265274

266-
return cipherText;
275+
return jsg::BufferSource(js, kj::mv(cipherText));
267276
}
268277

269-
kj::Array<kj::byte> decrypt(SubtleCrypto::EncryptAlgorithm&& algorithm,
278+
jsg::BufferSource decrypt(jsg::Lock& js,
279+
SubtleCrypto::EncryptAlgorithm&& algorithm,
270280
kj::ArrayPtr<const kj::byte> cipherText) const override {
271281
kj::ArrayPtr<kj::byte> iv =
272282
JSG_REQUIRE_NONNULL(algorithm.iv, TypeError, "Missing field \"iv\" in \"algorithm\".");
@@ -303,10 +313,10 @@ private:
303313
auto actualCipherText = cipherText.first(cipherText.size() - tagLength / 8);
304314
auto tagText = cipherText.slice(actualCipherText.size(), cipherText.size());
305315

306-
auto plainText = kj::heapArray<kj::byte>(actualCipherText.size());
316+
auto plainText = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, actualCipherText.size());
307317

308318
// Perform the actual decryption.
309-
OSSLCALL(EVP_DecryptUpdate(cipherCtx.get(), plainText.begin(), &plainSize,
319+
OSSLCALL(EVP_DecryptUpdate(cipherCtx.get(), plainText.asArrayPtr().begin(), &plainSize,
310320
actualCipherText.begin(), actualCipherText.size()));
311321
KJ_ASSERT(plainSize == plainText.size());
312322

@@ -322,10 +332,10 @@ private:
322332
const_cast<kj::byte*>(tagText.begin())));
323333

324334
plainSize += decryptFinalHelper(getAlgorithmName(), actualCipherText.size(), plainSize,
325-
cipherCtx.get(), plainText.begin() + plainSize);
335+
cipherCtx.get(), plainText.asArrayPtr().begin() + plainSize);
326336
KJ_ASSERT(plainSize == plainText.size());
327337

328-
return plainText;
338+
return jsg::BufferSource(js, kj::mv(plainText));
329339
}
330340
};
331341

@@ -338,7 +348,8 @@ public:
338348
: AesKeyBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable, usages) {}
339349

340350
private:
341-
kj::Array<kj::byte> encrypt(SubtleCrypto::EncryptAlgorithm&& algorithm,
351+
jsg::BufferSource encrypt(jsg::Lock& js,
352+
SubtleCrypto::EncryptAlgorithm&& algorithm,
342353
kj::ArrayPtr<const kj::byte> plainText) const override {
343354
kj::ArrayPtr<kj::byte> iv =
344355
JSG_REQUIRE_NONNULL(algorithm.iv, TypeError, "Missing field \"iv\" in \"algorithm\".");
@@ -355,29 +366,30 @@ private:
355366

356367
auto blockSize = EVP_CIPHER_CTX_block_size(cipherCtx.get());
357368
size_t paddingSize = blockSize - (plainText.size() % blockSize);
358-
auto cipherText = kj::heapArray<kj::byte>(plainText.size() + paddingSize);
369+
auto cipherText = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, plainText.size() + paddingSize);
359370

360371
// Perform the actual encryption.
361372
//
362373
// Note: We don't worry about PKCS padding (see RFC2315 section 10.3 step 2) because BoringSSL
363374
// takes care of it for us by default in EVP_EncryptFinal_ex().
364375

365376
int cipherSize = 0;
366-
OSSLCALL(EVP_EncryptUpdate(
367-
cipherCtx.get(), cipherText.begin(), &cipherSize, plainText.begin(), plainText.size()));
377+
OSSLCALL(EVP_EncryptUpdate(cipherCtx.get(), cipherText.asArrayPtr().begin(), &cipherSize,
378+
plainText.begin(), plainText.size()));
368379
KJ_ASSERT(cipherSize <= cipherText.size(), "buffer overrun");
369380

370381
KJ_ASSERT(cipherSize + blockSize <= cipherText.size(), "imminent buffer overrun");
371382
int finalCipherSize = 0;
372-
OSSLCALL(
373-
EVP_EncryptFinal_ex(cipherCtx.get(), cipherText.begin() + cipherSize, &finalCipherSize));
383+
OSSLCALL(EVP_EncryptFinal_ex(
384+
cipherCtx.get(), cipherText.asArrayPtr().begin() + cipherSize, &finalCipherSize));
374385
cipherSize += finalCipherSize;
375386
KJ_ASSERT(cipherSize == cipherText.size(), "buffer overrun");
376387

377-
return cipherText;
388+
return jsg::BufferSource(js, kj::mv(cipherText));
378389
}
379390

380-
kj::Array<kj::byte> decrypt(SubtleCrypto::EncryptAlgorithm&& algorithm,
391+
jsg::BufferSource decrypt(jsg::Lock& js,
392+
SubtleCrypto::EncryptAlgorithm&& algorithm,
381393
kj::ArrayPtr<const kj::byte> cipherText) const override {
382394
kj::ArrayPtr<kj::byte> iv =
383395
JSG_REQUIRE_NONNULL(algorithm.iv, TypeError, "Missing field \"iv\" in \"algorithm\".");
@@ -408,7 +420,9 @@ private:
408420
KJ_ASSERT(plainSize <= plainText.size());
409421

410422
// TODO(perf): Avoid this copy, see comment in the encrypt implementation functions.
411-
return kj::heapArray(plainText.begin(), plainSize);
423+
auto backing = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, plainSize);
424+
backing.asArrayPtr().copyFrom(plainText.first(plainSize));
425+
return jsg::BufferSource(js, kj::mv(backing));
412426
}
413427
};
414428

@@ -422,14 +436,16 @@ public:
422436
CryptoKeyUsageSet usages)
423437
: AesKeyBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable, usages) {}
424438

425-
kj::Array<kj::byte> encrypt(SubtleCrypto::EncryptAlgorithm&& algorithm,
439+
jsg::BufferSource encrypt(jsg::Lock& js,
440+
SubtleCrypto::EncryptAlgorithm&& algorithm,
426441
kj::ArrayPtr<const kj::byte> plainText) const override {
427-
return encryptOrDecrypt(kj::mv(algorithm), plainText);
442+
return encryptOrDecrypt(js, kj::mv(algorithm), plainText);
428443
}
429444

430-
kj::Array<kj::byte> decrypt(SubtleCrypto::EncryptAlgorithm&& algorithm,
445+
jsg::BufferSource decrypt(jsg::Lock& js,
446+
SubtleCrypto::EncryptAlgorithm&& algorithm,
431447
kj::ArrayPtr<const kj::byte> cipherText) const override {
432-
return encryptOrDecrypt(kj::mv(algorithm), cipherText);
448+
return encryptOrDecrypt(js, kj::mv(algorithm), cipherText);
433449
}
434450

435451
protected:
@@ -448,8 +464,9 @@ protected:
448464
KJ_FAIL_ASSERT("CryptoKey has invalid data length");
449465
}
450466

451-
kj::Array<kj::byte> encryptOrDecrypt(
452-
SubtleCrypto::EncryptAlgorithm&& algorithm, kj::ArrayPtr<const kj::byte> data) const {
467+
jsg::BufferSource encryptOrDecrypt(jsg::Lock& js,
468+
SubtleCrypto::EncryptAlgorithm&& algorithm,
469+
kj::ArrayPtr<const kj::byte> data) const {
453470
auto& counter = JSG_REQUIRE_NONNULL(
454471
algorithm.counter, TypeError, "Missing \"counter\" member in \"algorithm\".");
455472
JSG_REQUIRE(counter.size() == expectedCounterByteSize, DOMOperationError,
@@ -472,9 +489,8 @@ protected:
472489

473490
const auto& cipher = lookupAesType(keyData.size());
474491

475-
kj::Vector<kj::byte> result;
476492
// The output of AES-CTR is the same size as the input.
477-
result.resize(data.size());
493+
auto result = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, data.size());
478494

479495
auto numCounterValues = newBignum();
480496
JSG_REQUIRE(BN_lshift(numCounterValues.get(), BN_value_one(), counterBitLength),
@@ -505,15 +521,15 @@ protected:
505521

506522
if (BN_cmp(numBlocksUntilReset.get(), numOutputBlocks.get()) >= 0) {
507523
// If the counter doesn't need any wrapping, can evaluate this as a single call.
508-
process(&cipher, data, counter, result.asPtr());
509-
return result.releaseAsArray();
524+
process(&cipher, data, counter, result.asArrayPtr());
525+
return jsg::BufferSource(js, kj::mv(result));
510526
}
511527

512528
// Need this to be done in 2 parts using the current counter block and then resetting the
513529
// counter portion of the block back to zero.
514530
auto inputSizePart1 = BN_get_word(numBlocksUntilReset.get()) * AES_BLOCK_SIZE;
515531

516-
process(&cipher, data.first(inputSizePart1), counter, result.asPtr());
532+
process(&cipher, data.first(inputSizePart1), counter, result.asArrayPtr());
517533

518534
// Zero the counter bits of the block. Chromium creates a copy but we own our buffer.
519535
{
@@ -528,9 +544,9 @@ protected:
528544
}
529545

530546
process(&cipher, data.slice(inputSizePart1, data.size()), counter,
531-
result.slice(inputSizePart1, result.size()));
547+
result.asArrayPtr().slice(inputSizePart1, result.size()));
532548

533-
return result.releaseAsArray();
549+
return jsg::BufferSource(js, kj::mv(result));
534550
}
535551

536552
private:
@@ -620,7 +636,8 @@ public:
620636
CryptoKeyUsageSet usages)
621637
: AesKeyBase(kj::mv(keyData), kj::mv(keyAlgorithm), extractable, usages) {}
622638

623-
kj::Array<kj::byte> wrapKey(SubtleCrypto::EncryptAlgorithm&& algorithm,
639+
jsg::BufferSource wrapKey(jsg::Lock& js,
640+
SubtleCrypto::EncryptAlgorithm&& algorithm,
624641
kj::ArrayPtr<const kj::byte> unwrappedKey) const override {
625642
// Resources used to implement this:
626643
// https://www.ietf.org/rfc/rfc3394.txt
@@ -636,8 +653,7 @@ public:
636653
"equal to 16 and less than or equal to ",
637654
SIZE_MAX - 8);
638655

639-
kj::Vector<kj::byte> wrapped(unwrappedKey.size() + 8);
640-
wrapped.resize(unwrappedKey.size() + 8);
656+
auto wrapped = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, unwrappedKey.size() + 8);
641657
// Wrapping adds 8 bytes of overhead for storing the IV which we check on decryption.
642658

643659
AES_KEY aesKey;
@@ -646,14 +662,15 @@ public:
646662
internalDescribeOpensslErrors());
647663

648664
JSG_REQUIRE(wrapped.size() ==
649-
AES_wrap_key(
650-
&aesKey, nullptr, wrapped.begin(), unwrappedKey.begin(), unwrappedKey.size()),
665+
AES_wrap_key(&aesKey, nullptr, wrapped.asArrayPtr().begin(), unwrappedKey.begin(),
666+
unwrappedKey.size()),
651667
DOMOperationError, getAlgorithmName(), " key wrapping failed", tryDescribeOpensslErrors());
652668

653-
return wrapped.releaseAsArray();
669+
return jsg::BufferSource(js, kj::mv(wrapped));
654670
}
655671

656-
kj::Array<kj::byte> unwrapKey(SubtleCrypto::EncryptAlgorithm&& algorithm,
672+
jsg::BufferSource unwrapKey(jsg::Lock& js,
673+
SubtleCrypto::EncryptAlgorithm&& algorithm,
657674
kj::ArrayPtr<const kj::byte> wrappedKey) const override {
658675
// Resources used to implement this:
659676
// https://www.ietf.org/rfc/rfc3394.txt
@@ -667,9 +684,7 @@ public:
667684
"Provided a wrapped key to unwrap this is ", wrappedKey.size() * 8,
668685
" bits that is less than the minimal length of 192 bits.");
669686

670-
kj::Vector<kj::byte> unwrapped(wrappedKey.size() - 8);
671-
// Key wrap adds 8 bytes of overhead because it mixes in the IV.
672-
unwrapped.resize(wrappedKey.size() - 8);
687+
auto unwrapped = jsg::BackingStore::alloc<v8::ArrayBuffer>(js, wrappedKey.size() - 8);
673688

674689
AES_KEY aesKey;
675690
JSG_REQUIRE(0 == AES_set_decrypt_key(keyData.begin(), keyData.size() * 8, &aesKey),
@@ -679,12 +694,12 @@ public:
679694
// null for the IV value here will tell OpenSSL to validate using the default IV from RFC3394.
680695
// https://github.com/openssl/openssl/blob/13a574d8bb2523181f8150de49bc041c9841f59d/crypto/modes/wrap128.c
681696
JSG_REQUIRE(unwrapped.size() ==
682-
AES_unwrap_key(
683-
&aesKey, nullptr, unwrapped.begin(), wrappedKey.begin(), wrappedKey.size()),
697+
AES_unwrap_key(&aesKey, nullptr, unwrapped.asArrayPtr().begin(), wrappedKey.begin(),
698+
wrappedKey.size()),
684699
DOMOperationError, getAlgorithmName(), " key unwrapping failed",
685700
tryDescribeOpensslErrors());
686701

687-
return unwrapped.releaseAsArray();
702+
return jsg::BufferSource(js, kj::mv(unwrapped));
688703
}
689704
};
690705

0 commit comments

Comments
 (0)