/src/node/src/crypto/crypto_kem.cc
Line | Count | Source |
1 | | #include "crypto/crypto_kem.h" |
2 | | |
3 | | #if OPENSSL_VERSION_MAJOR >= 3 |
4 | | |
5 | | #include "async_wrap-inl.h" |
6 | | #include "base_object-inl.h" |
7 | | #include "crypto/crypto_keys.h" |
8 | | #include "crypto/crypto_util.h" |
9 | | #include "env-inl.h" |
10 | | #include "memory_tracker-inl.h" |
11 | | #include "node_buffer.h" |
12 | | #include "threadpoolwork-inl.h" |
13 | | #include "v8.h" |
14 | | |
15 | | namespace node { |
16 | | |
17 | | using ncrypto::EVPKeyPointer; |
18 | | using v8::Array; |
19 | | using v8::FunctionCallbackInfo; |
20 | | using v8::Local; |
21 | | using v8::Maybe; |
22 | | using v8::MaybeLocal; |
23 | | using v8::Nothing; |
24 | | using v8::Object; |
25 | | using v8::Value; |
26 | | |
27 | | namespace crypto { |
28 | | |
29 | | KEMConfiguration::KEMConfiguration(KEMConfiguration&& other) noexcept |
30 | 0 | : job_mode(other.job_mode), |
31 | 0 | mode(other.mode), |
32 | 0 | key(std::move(other.key)), |
33 | 0 | ciphertext(std::move(other.ciphertext)) {} |
34 | | |
35 | | KEMConfiguration& KEMConfiguration::operator=( |
36 | 0 | KEMConfiguration&& other) noexcept { |
37 | 0 | if (&other == this) return *this; |
38 | 0 | this->~KEMConfiguration(); |
39 | 0 | return *new (this) KEMConfiguration(std::move(other)); |
40 | 0 | } |
41 | | |
42 | 0 | void KEMConfiguration::MemoryInfo(MemoryTracker* tracker) const { |
43 | 0 | tracker->TrackField("key", key); |
44 | 0 | if (job_mode == kCryptoJobAsync) { |
45 | 0 | tracker->TrackFieldWithSize("ciphertext", ciphertext.size()); |
46 | 0 | } |
47 | 0 | } |
48 | | |
49 | | namespace { |
50 | | |
51 | | bool DoKEMEncapsulate(Environment* env, |
52 | | const EVPKeyPointer& public_key, |
53 | | ByteSource* out, |
54 | 0 | CryptoJobMode mode) { |
55 | 0 | auto result = ncrypto::KEM::Encapsulate(public_key); |
56 | 0 | if (!result) { |
57 | 0 | if (mode == kCryptoJobSync) { |
58 | 0 | THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to perform encapsulation"); |
59 | 0 | } |
60 | 0 | return false; |
61 | 0 | } |
62 | | |
63 | | // Pack the result: [ciphertext_len][shared_key_len][ciphertext][shared_key] |
64 | 0 | size_t ciphertext_len = result->ciphertext.size(); |
65 | 0 | size_t shared_key_len = result->shared_key.size(); |
66 | 0 | size_t total_len = |
67 | 0 | sizeof(uint32_t) + sizeof(uint32_t) + ciphertext_len + shared_key_len; |
68 | |
|
69 | 0 | auto data = ncrypto::DataPointer::Alloc(total_len); |
70 | 0 | if (!data) { |
71 | 0 | if (mode == kCryptoJobSync) { |
72 | 0 | THROW_ERR_CRYPTO_OPERATION_FAILED(env, |
73 | 0 | "Failed to allocate output buffer"); |
74 | 0 | } |
75 | 0 | return false; |
76 | 0 | } |
77 | | |
78 | 0 | unsigned char* ptr = static_cast<unsigned char*>(data.get()); |
79 | | |
80 | | // Write size headers |
81 | 0 | *reinterpret_cast<uint32_t*>(ptr) = static_cast<uint32_t>(ciphertext_len); |
82 | 0 | *reinterpret_cast<uint32_t*>(ptr + sizeof(uint32_t)) = |
83 | 0 | static_cast<uint32_t>(shared_key_len); |
84 | | |
85 | | // Write ciphertext and shared key data |
86 | 0 | unsigned char* ciphertext_ptr = ptr + 2 * sizeof(uint32_t); |
87 | 0 | unsigned char* shared_key_ptr = ciphertext_ptr + ciphertext_len; |
88 | |
|
89 | 0 | std::memcpy(ciphertext_ptr, result->ciphertext.get(), ciphertext_len); |
90 | 0 | std::memcpy(shared_key_ptr, result->shared_key.get(), shared_key_len); |
91 | |
|
92 | 0 | *out = ByteSource::Allocated(data.release()); |
93 | 0 | return true; |
94 | 0 | } |
95 | | |
96 | | bool DoKEMDecapsulate(Environment* env, |
97 | | const EVPKeyPointer& private_key, |
98 | | const ByteSource& ciphertext, |
99 | | ByteSource* out, |
100 | 0 | CryptoJobMode mode) { |
101 | 0 | ncrypto::Buffer<const void> ciphertext_buf{ciphertext.data(), |
102 | 0 | ciphertext.size()}; |
103 | 0 | auto shared_key = ncrypto::KEM::Decapsulate(private_key, ciphertext_buf); |
104 | 0 | if (!shared_key) { |
105 | 0 | if (mode == kCryptoJobSync) { |
106 | 0 | THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to perform decapsulation"); |
107 | 0 | } |
108 | 0 | return false; |
109 | 0 | } |
110 | | |
111 | 0 | *out = ByteSource::Allocated(shared_key.release()); |
112 | 0 | return true; |
113 | 0 | } |
114 | | |
115 | | } // anonymous namespace |
116 | | |
117 | | // KEMEncapsulateTraits implementation |
118 | | Maybe<void> KEMEncapsulateTraits::AdditionalConfig( |
119 | | CryptoJobMode mode, |
120 | | const FunctionCallbackInfo<Value>& args, |
121 | | unsigned int offset, |
122 | 0 | KEMConfiguration* params) { |
123 | 0 | params->job_mode = mode; |
124 | 0 | params->mode = KEMMode::Encapsulate; |
125 | |
|
126 | 0 | unsigned int key_offset = offset; |
127 | 0 | auto public_key_data = |
128 | 0 | KeyObjectData::GetPublicOrPrivateKeyFromJs(args, &key_offset); |
129 | 0 | if (!public_key_data) { |
130 | 0 | return Nothing<void>(); |
131 | 0 | } |
132 | 0 | params->key = std::move(public_key_data); |
133 | |
|
134 | 0 | return v8::JustVoid(); |
135 | 0 | } |
136 | | |
137 | | bool KEMEncapsulateTraits::DeriveBits(Environment* env, |
138 | | const KEMConfiguration& params, |
139 | | ByteSource* out, |
140 | 0 | CryptoJobMode mode) { |
141 | 0 | Mutex::ScopedLock lock(params.key.mutex()); |
142 | 0 | const auto& public_key = params.key.GetAsymmetricKey(); |
143 | |
|
144 | 0 | return DoKEMEncapsulate(env, public_key, out, mode); |
145 | 0 | } |
146 | | |
147 | | MaybeLocal<Value> KEMEncapsulateTraits::EncodeOutput( |
148 | 0 | Environment* env, const KEMConfiguration& params, ByteSource* out) { |
149 | | // The output contains: |
150 | | // [ciphertext_len][shared_key_len][ciphertext][shared_key] |
151 | 0 | const unsigned char* data = out->data<unsigned char>(); |
152 | |
|
153 | 0 | uint32_t ciphertext_len = *reinterpret_cast<const uint32_t*>(data); |
154 | 0 | uint32_t shared_key_len = |
155 | 0 | *reinterpret_cast<const uint32_t*>(data + sizeof(uint32_t)); |
156 | |
|
157 | 0 | const unsigned char* ciphertext_ptr = data + 2 * sizeof(uint32_t); |
158 | 0 | const unsigned char* shared_key_ptr = ciphertext_ptr + ciphertext_len; |
159 | |
|
160 | 0 | MaybeLocal<Object> ciphertext_buf = |
161 | 0 | node::Buffer::Copy(env->isolate(), |
162 | 0 | reinterpret_cast<const char*>(ciphertext_ptr), |
163 | 0 | ciphertext_len); |
164 | |
|
165 | 0 | MaybeLocal<Object> shared_key_buf = |
166 | 0 | node::Buffer::Copy(env->isolate(), |
167 | 0 | reinterpret_cast<const char*>(shared_key_ptr), |
168 | 0 | shared_key_len); |
169 | |
|
170 | 0 | Local<Object> ciphertext_obj; |
171 | 0 | Local<Object> shared_key_obj; |
172 | 0 | if (!ciphertext_buf.ToLocal(&ciphertext_obj) || |
173 | 0 | !shared_key_buf.ToLocal(&shared_key_obj)) { |
174 | 0 | return MaybeLocal<Value>(); |
175 | 0 | } |
176 | | |
177 | | // Return an array [sharedKey, ciphertext]. |
178 | 0 | Local<Array> result = Array::New(env->isolate(), 2); |
179 | 0 | if (result->Set(env->context(), 0, shared_key_obj).IsNothing() || |
180 | 0 | result->Set(env->context(), 1, ciphertext_obj).IsNothing()) { |
181 | 0 | return MaybeLocal<Value>(); |
182 | 0 | } |
183 | | |
184 | 0 | return result; |
185 | 0 | } |
186 | | |
187 | | // KEMDecapsulateTraits implementation |
188 | | Maybe<void> KEMDecapsulateTraits::AdditionalConfig( |
189 | | CryptoJobMode mode, |
190 | | const FunctionCallbackInfo<Value>& args, |
191 | | unsigned int offset, |
192 | 0 | KEMConfiguration* params) { |
193 | 0 | Environment* env = Environment::GetCurrent(args); |
194 | |
|
195 | 0 | params->job_mode = mode; |
196 | 0 | params->mode = KEMMode::Decapsulate; |
197 | |
|
198 | 0 | unsigned int key_offset = offset; |
199 | 0 | auto private_key_data = |
200 | 0 | KeyObjectData::GetPrivateKeyFromJs(args, &key_offset, true); |
201 | 0 | if (!private_key_data) { |
202 | 0 | return Nothing<void>(); |
203 | 0 | } |
204 | 0 | params->key = std::move(private_key_data); |
205 | |
|
206 | 0 | ArrayBufferOrViewContents<unsigned char> ciphertext(args[key_offset]); |
207 | 0 | if (!ciphertext.CheckSizeInt32()) { |
208 | 0 | THROW_ERR_OUT_OF_RANGE(env, "ciphertext is too big"); |
209 | 0 | return Nothing<void>(); |
210 | 0 | } |
211 | | |
212 | 0 | params->ciphertext = |
213 | 0 | mode == kCryptoJobAsync ? ciphertext.ToCopy() : ciphertext.ToByteSource(); |
214 | |
|
215 | 0 | return v8::JustVoid(); |
216 | 0 | } |
217 | | |
218 | | bool KEMDecapsulateTraits::DeriveBits(Environment* env, |
219 | | const KEMConfiguration& params, |
220 | | ByteSource* out, |
221 | 0 | CryptoJobMode mode) { |
222 | 0 | Mutex::ScopedLock lock(params.key.mutex()); |
223 | 0 | const auto& private_key = params.key.GetAsymmetricKey(); |
224 | |
|
225 | 0 | return DoKEMDecapsulate(env, private_key, params.ciphertext, out, mode); |
226 | 0 | } |
227 | | |
228 | | MaybeLocal<Value> KEMDecapsulateTraits::EncodeOutput( |
229 | 0 | Environment* env, const KEMConfiguration& params, ByteSource* out) { |
230 | 0 | return out->ToBuffer(env); |
231 | 0 | } |
232 | | |
233 | 0 | void InitializeKEM(Environment* env, Local<Object> target) { |
234 | 0 | KEMEncapsulateJob::Initialize(env, target); |
235 | 0 | KEMDecapsulateJob::Initialize(env, target); |
236 | |
|
237 | 0 | constexpr int kKEMEncapsulate = static_cast<int>(KEMMode::Encapsulate); |
238 | 0 | constexpr int kKEMDecapsulate = static_cast<int>(KEMMode::Decapsulate); |
239 | |
|
240 | 0 | NODE_DEFINE_CONSTANT(target, kKEMEncapsulate); |
241 | 0 | NODE_DEFINE_CONSTANT(target, kKEMDecapsulate); |
242 | 0 | } |
243 | | |
244 | 0 | void RegisterKEMExternalReferences(ExternalReferenceRegistry* registry) { |
245 | 0 | KEMEncapsulateJob::RegisterExternalReferences(registry); |
246 | 0 | KEMDecapsulateJob::RegisterExternalReferences(registry); |
247 | 0 | } |
248 | | |
249 | | namespace KEM { |
250 | 0 | void Initialize(Environment* env, Local<Object> target) { |
251 | 0 | InitializeKEM(env, target); |
252 | 0 | } |
253 | | |
254 | 0 | void RegisterExternalReferences(ExternalReferenceRegistry* registry) { |
255 | 0 | RegisterKEMExternalReferences(registry); |
256 | 0 | } |
257 | | } // namespace KEM |
258 | | |
259 | | } // namespace crypto |
260 | | } // namespace node |
261 | | |
262 | | #endif |