Coverage Report

Created: 2025-12-10 07:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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