Skip to content
File

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

6.5 KB
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5#include "crypto.h"
6#include "impl.h"
7 
8#include <workerd/api/util.h>
9#include <workerd/jsg/jsg-test.h>
10#include <workerd/jsg/jsg.h>
11#include <workerd/jsg/setup.h>
12 
13#include <kj/test.h>
14 
15#include <array>
16 
17namespace workerd::api {
18namespace {
19 
20jsg::V8System v8System;
21 
22struct CryptoContext: public jsg::Object, public jsg::ContextGlobal {
23 JSG_RESOURCE_TYPE(CryptoContext) {}
24};
25JSG_DECLARE_ISOLATE_TYPE(CryptoIsolate, CryptoContext);
26 
27KJ_TEST("AES-KW key wrap") {
28 // Basic test that I wrote when I was seeing heap corruption. Found it easier to iterate on with
29 // ASAN/valgrind than using our conformance tests with test-runner.
30 jsg::test::Evaluator<CryptoContext, CryptoIsolate> e(v8System);
31 e.getIsolate().runInLockScope([&](CryptoIsolate::Lock& isolateLock) {
32 auto rawWrappingKeys = std::array<kj::Array<kj::byte>, 3>({
33 kj::heapArray<kj::byte>({0xe6, 0x95, 0xea, 0xe3, 0xa8, 0xc0, 0x30, 0xf1, 0x76, 0xe3, 0x0e,
34 0x8e, 0x36, 0xf8, 0xf4, 0x31}),
35 // AES-KW 128
36 kj::heapArray<kj::byte>({0x20, 0xa7, 0x98, 0xd1, 0x82, 0x8c, 0x18, 0x67, 0xfd, 0xda, 0x16,
37 0x03, 0x57, 0xc6, 0x32, 0x4f, 0xcc, 0xe8, 0x08, 0x6d, 0x21, 0xe9, 0x3c, 0x60}),
38 // AES-KW 192
39 kj::heapArray<kj::byte>({0x52, 0x4b, 0x67, 0x25, 0xe3, 0x56, 0xaa, 0xce, 0x7e, 0x76, 0x9b,
40 0x48, 0x92, 0x55, 0x49, 0x06, 0x12, 0x5e, 0xf5, 0xae, 0xce, 0x39, 0xde, 0xc2, 0x5b, 0x27,
41 0x33, 0x4e, 0x6e, 0x52, 0x32, 0x4e}),
42 // AES-KW 256
43 });
44 
45 auto aesKeys = KJ_MAP(rawKey, kj::mv(rawWrappingKeys)) {
46 SubtleCrypto::ImportKeyAlgorithm algorithm = {
47 .name = kj::str("AES-KW"),
48 };
49 bool extractable = false;
50 
51 return CryptoKey::Impl::importAes(isolateLock, "AES-KW", "raw", kj::mv(rawKey),
52 kj::mv(algorithm), extractable, {kj::str("wrapKey"), kj::str("unwrapKey")});
53 };
54 
55 auto keyMaterial = kj::heapArray<const kj::byte>(
56 {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24});
57 
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");
63 
64 auto wrapped = aesKey->wrapKey(js, kj::mv(params), keyMaterial.asPtr());
65 
66 params = {};
67 params.name = kj::str("AES-KW");
68 
69 auto unwrapped = aesKey->unwrapKey(js, kj::mv(params), wrapped.asArrayPtr().asConst());
70 
71 KJ_EXPECT(unwrapped.asArrayPtr() == keyMaterial);
72 
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(
78 "[24 == -1]", aesKey->unwrapKey(js, kj::mv(params), wrapped.asArrayPtr().asConst()));
79 }
80 });
81 });
82}
83 
84// Disable null pointer checks (a subset of UBSan) here due to the null reference being passed for
85// jwkHandler. Using attribute push as annotating just the test itself didn't seem to work.
86#if __clang__ && __has_feature(undefined_behavior_sanitizer)
87#pragma clang attribute push(__attribute__((no_sanitize("null"))), apply_to = function)
88#endif
89KJ_TEST("AES-CTR key wrap") {
90 // Basic test that let me repro an issue where using an AES key that's not AES-KW would fail to
91 // wrap if it didn't have "encrypt" in its usages when created.
92 
93 const jsg::TypeHandler<SubtleCrypto::JsonWebKey>* jwkHandler = nullptr;
94 // Not testing JWK here, so valid value isn't needed.
95 
96 static constexpr kj::byte RAW_KEY_DATA[] = {0x52, 0x4b, 0x67, 0x25, 0xe3, 0x56, 0xaa, 0xce, 0x7e,
97 0x76, 0x9b, 0x48, 0x92, 0x55, 0x49, 0x06, 0x12, 0x5e, 0xf5, 0xae, 0xce, 0x39, 0xde, 0xc2, 0x5b,
98 0x27, 0x33, 0x4e, 0x6e, 0x52, 0x32, 0x4e};
99 
100 static constexpr kj::ArrayPtr<const kj::byte> KEY_DATA(RAW_KEY_DATA, 32);
101 
102 SubtleCrypto subtle;
103 
104 static constexpr auto getWrappingKey = [](jsg::Lock& js, SubtleCrypto& subtle) {
105 return subtle.importKeySync(js, "raw", kj::heapArray<kj::byte>(KEY_DATA),
106 SubtleCrypto::ImportKeyAlgorithm{.name = kj::str("AES-CTR")}, false /* extractable */,
107 {kj::str("wrapKey"), kj::str("unwrapKey")});
108 };
109 
110 static constexpr auto getEnc = [](jsg::Lock& js) {
111 static constexpr auto kRaw =
112 "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10"_kjb;
113 auto counter = jsg::JsUint8Array::create(js, kRaw);
114 
115 return SubtleCrypto::EncryptAlgorithm{
116 .name = kj::str("AES-CTR"),
117 .counter = jsg::JsBufferSource(counter).addRef(js),
118 .length = 5,
119 };
120 };
121 
122 static constexpr auto getImportKeyAlg = [] {
123 return SubtleCrypto::ImportKeyAlgorithm{
124 .name = kj::str("AES-CBC"),
125 .length = 256,
126 };
127 };
128 
129 jsg::test::Evaluator<CryptoContext, CryptoIsolate> e(v8System);
130 bool completed = false;
131 
132 e.getIsolate().runInLockScope([&](CryptoIsolate::Lock& isolateLock) {
133 JSG_WITHIN_CONTEXT_SCOPE(isolateLock,
134 isolateLock.newContext<CryptoContext>().getHandle(isolateLock), [&](jsg::Lock& js) {
135 auto wrappingKey = getWrappingKey(js, subtle);
136 subtle
137 .importKey(js, kj::str("raw"), kj::heapArray(KEY_DATA), getImportKeyAlg(), true,
138 kj::arr(kj::str("decrypt")))
139 .then(js,
140 [&](jsg::Lock&, jsg::Ref<CryptoKey> toWrap) {
141 return subtle.wrapKey(js, kj::str("raw"), *toWrap, *wrappingKey, getEnc(js), *jwkHandler);
142 })
143 .then(js,
144 [&](jsg::Lock& js, jsg::JsRef<jsg::JsArrayBuffer> wrapped) {
145 auto data = wrapped.getHandle(js).copy();
146 return subtle.unwrapKey(js, kj::str("raw"), kj::mv(data), *wrappingKey, getEnc(js),
147 getImportKeyAlg(), true, kj::arr(kj::str("encrypt")), *jwkHandler);
148 })
149 .then(js, [&](jsg::Lock& js, jsg::Ref<CryptoKey> unwrapped) {
150 return subtle.exportKey(js, kj::str("raw"), *unwrapped);
151 }).then(js, [&](jsg::Lock& js, api::SubtleCrypto::ExportKeyData roundTrippedKeyMaterial) {
152 auto& buf = roundTrippedKeyMaterial.get<jsg::JsRef<jsg::JsArrayBuffer>>();
153 KJ_ASSERT(buf.getHandle(js).asArrayPtr() == KEY_DATA);
154 completed = true;
155 });
156 
157 js.runMicrotasks();
158 });
159 });
160 
161 KJ_ASSERT(completed, "Microtasks did not run fully.");
162}
163#if __clang__ && __has_feature(undefined_behavior_sanitizer)
164#pragma clang attribute pop // __attribute__((no_sanitize("null"))
165#endif
166 
167} // namespace
168} // namespace workerd::api