GCC Code Coverage Report | |||||||||||||||||||||
|
|||||||||||||||||||||
Line | Branch | Exec | Source |
1 |
#include "crypto/crypto_sig.h" |
||
2 |
#include "crypto/crypto_ec.h" |
||
3 |
#include "crypto/crypto_keys.h" |
||
4 |
#include "crypto/crypto_util.h" |
||
5 |
#include "allocated_buffer-inl.h" |
||
6 |
#include "async_wrap-inl.h" |
||
7 |
#include "base_object-inl.h" |
||
8 |
#include "env-inl.h" |
||
9 |
#include "memory_tracker-inl.h" |
||
10 |
#include "threadpoolwork-inl.h" |
||
11 |
#include "v8.h" |
||
12 |
|||
13 |
namespace node { |
||
14 |
|||
15 |
using v8::ArrayBuffer; |
||
16 |
using v8::BackingStore; |
||
17 |
using v8::FunctionCallbackInfo; |
||
18 |
using v8::FunctionTemplate; |
||
19 |
using v8::HandleScope; |
||
20 |
using v8::Int32; |
||
21 |
using v8::Just; |
||
22 |
using v8::Local; |
||
23 |
using v8::Maybe; |
||
24 |
using v8::Nothing; |
||
25 |
using v8::Object; |
||
26 |
using v8::Uint32; |
||
27 |
using v8::Value; |
||
28 |
|||
29 |
namespace crypto { |
||
30 |
namespace { |
||
31 |
112 |
bool ValidateDSAParameters(EVP_PKEY* key) { |
|
32 |
/* Validate DSA2 parameters from FIPS 186-4 */ |
||
33 |
#if OPENSSL_VERSION_MAJOR >= 3 |
||
34 |
✗✓✗✗ ✗✓ |
112 |
if (EVP_default_properties_is_fips_enabled(nullptr) && |
35 |
EVP_PKEY_DSA == EVP_PKEY_base_id(key)) { |
||
36 |
#else |
||
37 |
if (FIPS_mode() && EVP_PKEY_DSA == EVP_PKEY_base_id(key)) { |
||
38 |
#endif |
||
39 |
const DSA* dsa = EVP_PKEY_get0_DSA(key); |
||
40 |
const BIGNUM* p; |
||
41 |
DSA_get0_pqg(dsa, &p, nullptr, nullptr); |
||
42 |
size_t L = BN_num_bits(p); |
||
43 |
const BIGNUM* q; |
||
44 |
DSA_get0_pqg(dsa, nullptr, &q, nullptr); |
||
45 |
size_t N = BN_num_bits(q); |
||
46 |
|||
47 |
return (L == 1024 && N == 160) || |
||
48 |
(L == 2048 && N == 224) || |
||
49 |
(L == 2048 && N == 256) || |
||
50 |
(L == 3072 && N == 256); |
||
51 |
} |
||
52 |
|||
53 |
112 |
return true; |
|
54 |
} |
||
55 |
|||
56 |
2198 |
bool ApplyRSAOptions(const ManagedEVPPKey& pkey, |
|
57 |
EVP_PKEY_CTX* pkctx, |
||
58 |
int padding, |
||
59 |
const Maybe<int>& salt_len) { |
||
60 |
✓✗ | 3137 |
if (EVP_PKEY_id(pkey.get()) == EVP_PKEY_RSA || |
61 |
✓✓✓✓ ✓✓ |
3137 |
EVP_PKEY_id(pkey.get()) == EVP_PKEY_RSA2 || |
62 |
939 |
EVP_PKEY_id(pkey.get()) == EVP_PKEY_RSA_PSS) { |
|
63 |
✓✓ | 1592 |
if (EVP_PKEY_CTX_set_rsa_padding(pkctx, padding) <= 0) |
64 |
3 |
return false; |
|
65 |
✓✓✓✓ ✓✓ |
2911 |
if (padding == RSA_PKCS1_PSS_PADDING && salt_len.IsJust()) { |
66 |
✓✓ | 1222 |
if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkctx, salt_len.FromJust()) <= 0) |
67 |
12 |
return false; |
|
68 |
} |
||
69 |
} |
||
70 |
|||
71 |
2183 |
return true; |
|
72 |
} |
||
73 |
|||
74 |
112 |
std::unique_ptr<BackingStore> Node_SignFinal(Environment* env, |
|
75 |
EVPMDPointer&& mdctx, |
||
76 |
const ManagedEVPPKey& pkey, |
||
77 |
int padding, |
||
78 |
Maybe<int> pss_salt_len) { |
||
79 |
unsigned char m[EVP_MAX_MD_SIZE]; |
||
80 |
unsigned int m_len; |
||
81 |
|||
82 |
✗✓ | 112 |
if (!EVP_DigestFinal_ex(mdctx.get(), m, &m_len)) |
83 |
return nullptr; |
||
84 |
|||
85 |
112 |
int signed_sig_len = EVP_PKEY_size(pkey.get()); |
|
86 |
✗✓ | 112 |
CHECK_GE(signed_sig_len, 0); |
87 |
112 |
size_t sig_len = static_cast<size_t>(signed_sig_len); |
|
88 |
112 |
std::unique_ptr<BackingStore> sig; |
|
89 |
{ |
||
90 |
112 |
NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data()); |
|
91 |
112 |
sig = ArrayBuffer::NewBackingStore(env->isolate(), sig_len); |
|
92 |
} |
||
93 |
224 |
EVPKeyCtxPointer pkctx(EVP_PKEY_CTX_new(pkey.get(), nullptr)); |
|
94 |
✓✗ | 112 |
if (pkctx && |
95 |
✓✓ | 336 |
EVP_PKEY_sign_init(pkctx.get()) && |
96 |
✓✓ | 216 |
ApplyRSAOptions(pkey, pkctx.get(), padding, pss_salt_len) && |
97 |
✓✗✓✓ ✓✓ |
328 |
EVP_PKEY_CTX_set_signature_md(pkctx.get(), EVP_MD_CTX_md(mdctx.get())) && |
98 |
98 |
EVP_PKEY_sign(pkctx.get(), static_cast<unsigned char*>(sig->Data()), |
|
99 |
&sig_len, m, m_len)) { |
||
100 |
✗✓ | 97 |
CHECK_LE(sig_len, sig->ByteLength()); |
101 |
✗✓ | 97 |
if (sig_len == 0) |
102 |
sig = ArrayBuffer::NewBackingStore(env->isolate(), 0); |
||
103 |
else |
||
104 |
97 |
sig = BackingStore::Reallocate(env->isolate(), std::move(sig), sig_len); |
|
105 |
97 |
return sig; |
|
106 |
} |
||
107 |
|||
108 |
15 |
return nullptr; |
|
109 |
} |
||
110 |
|||
111 |
1579 |
int GetDefaultSignPadding(const ManagedEVPPKey& m_pkey) { |
|
112 |
✓✓ | 1579 |
return EVP_PKEY_id(m_pkey.get()) == EVP_PKEY_RSA_PSS ? RSA_PKCS1_PSS_PADDING : |
113 |
1579 |
RSA_PKCS1_PADDING; |
|
114 |
} |
||
115 |
|||
116 |
290 |
unsigned int GetBytesOfRS(const ManagedEVPPKey& pkey) { |
|
117 |
290 |
int bits, base_id = EVP_PKEY_base_id(pkey.get()); |
|
118 |
|||
119 |
✓✓ | 290 |
if (base_id == EVP_PKEY_DSA) { |
120 |
28 |
const DSA* dsa_key = EVP_PKEY_get0_DSA(pkey.get()); |
|
121 |
// Both r and s are computed mod q, so their width is limited by that of q. |
||
122 |
28 |
bits = BN_num_bits(DSA_get0_q(dsa_key)); |
|
123 |
✓✗ | 262 |
} else if (base_id == EVP_PKEY_EC) { |
124 |
262 |
const EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(pkey.get()); |
|
125 |
262 |
const EC_GROUP* ec_group = EC_KEY_get0_group(ec_key); |
|
126 |
262 |
bits = EC_GROUP_order_bits(ec_group); |
|
127 |
} else { |
||
128 |
return kNoDsaSignature; |
||
129 |
} |
||
130 |
|||
131 |
290 |
return (bits + 7) / 8; |
|
132 |
} |
||
133 |
|||
134 |
54 |
bool ExtractP1363( |
|
135 |
const unsigned char* sig_data, |
||
136 |
unsigned char* out, |
||
137 |
size_t len, |
||
138 |
size_t n) { |
||
139 |
108 |
ECDSASigPointer asn1_sig(d2i_ECDSA_SIG(nullptr, &sig_data, len)); |
|
140 |
✗✓ | 54 |
if (!asn1_sig) |
141 |
return false; |
||
142 |
|||
143 |
54 |
const BIGNUM* pr = ECDSA_SIG_get0_r(asn1_sig.get()); |
|
144 |
54 |
const BIGNUM* ps = ECDSA_SIG_get0_s(asn1_sig.get()); |
|
145 |
|||
146 |
✓✗✓✗ |
54 |
return BN_bn2binpad(pr, out, n) > 0 && BN_bn2binpad(ps, out + n, n) > 0; |
147 |
} |
||
148 |
|||
149 |
// Returns the maximum size of each of the integers (r, s) of the DSA signature. |
||
150 |
std::unique_ptr<BackingStore> ConvertSignatureToP1363(Environment* env, |
||
151 |
const ManagedEVPPKey& pkey, std::unique_ptr<BackingStore>&& signature) { |
||
152 |
unsigned int n = GetBytesOfRS(pkey); |
||
153 |
if (n == kNoDsaSignature) |
||
154 |
return std::move(signature); |
||
155 |
|||
156 |
std::unique_ptr<BackingStore> buf; |
||
157 |
{ |
||
158 |
NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data()); |
||
159 |
buf = ArrayBuffer::NewBackingStore(env->isolate(), 2 * n); |
||
160 |
} |
||
161 |
if (!ExtractP1363(static_cast<unsigned char*>(signature->Data()), |
||
162 |
static_cast<unsigned char*>(buf->Data()), |
||
163 |
signature->ByteLength(), n)) |
||
164 |
return std::move(signature); |
||
165 |
|||
166 |
return buf; |
||
167 |
} |
||
168 |
|||
169 |
// Returns the maximum size of each of the integers (r, s) of the DSA signature. |
||
170 |
54 |
ByteSource ConvertSignatureToP1363( |
|
171 |
Environment* env, |
||
172 |
const ManagedEVPPKey& pkey, |
||
173 |
const ByteSource& signature) { |
||
174 |
54 |
unsigned int n = GetBytesOfRS(pkey); |
|
175 |
✗✓ | 54 |
if (n == kNoDsaSignature) |
176 |
return ByteSource(); |
||
177 |
|||
178 |
const unsigned char* sig_data = |
||
179 |
54 |
reinterpret_cast<const unsigned char*>(signature.get()); |
|
180 |
|||
181 |
54 |
char* outdata = MallocOpenSSL<char>(n * 2); |
|
182 |
54 |
memset(outdata, 0, n * 2); |
|
183 |
108 |
ByteSource out = ByteSource::Allocated(outdata, n * 2); |
|
184 |
54 |
unsigned char* ptr = reinterpret_cast<unsigned char*>(outdata); |
|
185 |
|||
186 |
✗✓ | 54 |
if (!ExtractP1363(sig_data, ptr, signature.size(), n)) |
187 |
return ByteSource(); |
||
188 |
|||
189 |
54 |
return out; |
|
190 |
} |
||
191 |
|||
192 |
236 |
ByteSource ConvertSignatureToDER( |
|
193 |
const ManagedEVPPKey& pkey, |
||
194 |
ByteSource&& out) { |
||
195 |
236 |
unsigned int n = GetBytesOfRS(pkey); |
|
196 |
✗✓ | 236 |
if (n == kNoDsaSignature) |
197 |
return std::move(out); |
||
198 |
|||
199 |
const unsigned char* sig_data = |
||
200 |
236 |
reinterpret_cast<const unsigned char*>(out.get()); |
|
201 |
|||
202 |
✓✓ | 236 |
if (out.size() != 2 * n) |
203 |
46 |
return ByteSource(); |
|
204 |
|||
205 |
380 |
ECDSASigPointer asn1_sig(ECDSA_SIG_new()); |
|
206 |
✗✓ | 190 |
CHECK(asn1_sig); |
207 |
190 |
BIGNUM* r = BN_new(); |
|
208 |
✗✓ | 190 |
CHECK_NOT_NULL(r); |
209 |
190 |
BIGNUM* s = BN_new(); |
|
210 |
✗✓ | 190 |
CHECK_NOT_NULL(s); |
211 |
✗✓ | 190 |
CHECK_EQ(r, BN_bin2bn(sig_data, n, r)); |
212 |
✗✓ | 190 |
CHECK_EQ(s, BN_bin2bn(sig_data + n, n, s)); |
213 |
✗✓ | 190 |
CHECK_EQ(1, ECDSA_SIG_set0(asn1_sig.get(), r, s)); |
214 |
|||
215 |
190 |
unsigned char* data = nullptr; |
|
216 |
190 |
int len = i2d_ECDSA_SIG(asn1_sig.get(), &data); |
|
217 |
|||
218 |
✗✓ | 190 |
if (len <= 0) |
219 |
return ByteSource(); |
||
220 |
|||
221 |
✗✓ | 190 |
CHECK_NOT_NULL(data); |
222 |
|||
223 |
190 |
return ByteSource::Allocated(reinterpret_cast<char*>(data), len); |
|
224 |
} |
||
225 |
|||
226 |
1488 |
void CheckThrow(Environment* env, SignBase::Error error) { |
|
227 |
1488 |
HandleScope scope(env->isolate()); |
|
228 |
|||
229 |
✓✗✗✓ ✓✗ |
1488 |
switch (error) { |
230 |
1 |
case SignBase::Error::kSignUnknownDigest: |
|
231 |
1 |
return THROW_ERR_CRYPTO_INVALID_DIGEST(env); |
|
232 |
|||
233 |
case SignBase::Error::kSignNotInitialised: |
||
234 |
return THROW_ERR_CRYPTO_INVALID_STATE(env, "Not initialised"); |
||
235 |
|||
236 |
case SignBase::Error::kSignMalformedSignature: |
||
237 |
return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Malformed signature"); |
||
238 |
|||
239 |
29 |
case SignBase::Error::kSignInit: |
|
240 |
case SignBase::Error::kSignUpdate: |
||
241 |
case SignBase::Error::kSignPrivateKey: |
||
242 |
case SignBase::Error::kSignPublicKey: |
||
243 |
{ |
||
244 |
29 |
unsigned long err = ERR_get_error(); // NOLINT(runtime/int) |
|
245 |
✓✗ | 29 |
if (err) |
246 |
29 |
return ThrowCryptoError(env, err); |
|
247 |
switch (error) { |
||
248 |
case SignBase::Error::kSignInit: |
||
249 |
return THROW_ERR_CRYPTO_OPERATION_FAILED(env, |
||
250 |
"EVP_SignInit_ex failed"); |
||
251 |
case SignBase::Error::kSignUpdate: |
||
252 |
return THROW_ERR_CRYPTO_OPERATION_FAILED(env, |
||
253 |
"EVP_SignUpdate failed"); |
||
254 |
case SignBase::Error::kSignPrivateKey: |
||
255 |
return THROW_ERR_CRYPTO_OPERATION_FAILED(env, |
||
256 |
"PEM_read_bio_PrivateKey failed"); |
||
257 |
case SignBase::Error::kSignPublicKey: |
||
258 |
return THROW_ERR_CRYPTO_OPERATION_FAILED(env, |
||
259 |
"PEM_read_bio_PUBKEY failed"); |
||
260 |
default: |
||
261 |
ABORT(); |
||
262 |
} |
||
263 |
} |
||
264 |
|||
265 |
1458 |
case SignBase::Error::kSignOk: |
|
266 |
1458 |
return; |
|
267 |
} |
||
268 |
} |
||
269 |
|||
270 |
288 |
bool IsOneShot(const ManagedEVPPKey& key) { |
|
271 |
✓✓ | 288 |
switch (EVP_PKEY_id(key.get())) { |
272 |
35 |
case EVP_PKEY_ED25519: |
|
273 |
case EVP_PKEY_ED448: |
||
274 |
35 |
return true; |
|
275 |
253 |
default: |
|
276 |
253 |
return false; |
|
277 |
} |
||
278 |
} |
||
279 |
|||
280 |
1457 |
bool UseP1363Encoding(const ManagedEVPPKey& key, |
|
281 |
const DSASigEnc& dsa_encoding) { |
||
282 |
✓✓ | 1457 |
switch (EVP_PKEY_id(key.get())) { |
283 |
449 |
case EVP_PKEY_EC: |
|
284 |
case EVP_PKEY_DSA: |
||
285 |
449 |
return dsa_encoding == kSigEncP1363; |
|
286 |
1008 |
default: |
|
287 |
1008 |
return false; |
|
288 |
} |
||
289 |
} |
||
290 |
} // namespace |
||
291 |
|||
292 |
731 |
SignBase::Error SignBase::Init(const char* sign_type) { |
|
293 |
✗✓ | 731 |
CHECK_NULL(mdctx_); |
294 |
// Historically, "dss1" and "DSS1" were DSA aliases for SHA-1 |
||
295 |
// exposed through the public API. |
||
296 |
✓✗ | 731 |
if (strcmp(sign_type, "dss1") == 0 || |
297 |
✓✓ | 731 |
strcmp(sign_type, "DSS1") == 0) { |
298 |
2 |
sign_type = "SHA1"; |
|
299 |
} |
||
300 |
731 |
const EVP_MD* md = EVP_get_digestbyname(sign_type); |
|
301 |
✓✓ | 731 |
if (md == nullptr) |
302 |
1 |
return kSignUnknownDigest; |
|
303 |
|||
304 |
730 |
mdctx_.reset(EVP_MD_CTX_new()); |
|
305 |
✓✗✗✓ ✗✓ |
730 |
if (!mdctx_ || !EVP_DigestInit_ex(mdctx_.get(), md, nullptr)) { |
306 |
mdctx_.reset(); |
||
307 |
return kSignInit; |
||
308 |
} |
||
309 |
|||
310 |
730 |
return kSignOk; |
|
311 |
} |
||
312 |
|||
313 |
728 |
SignBase::Error SignBase::Update(const char* data, size_t len) { |
|
314 |
✗✓ | 728 |
if (mdctx_ == nullptr) |
315 |
return kSignNotInitialised; |
||
316 |
✗✓ | 728 |
if (!EVP_DigestUpdate(mdctx_.get(), data, len)) |
317 |
return kSignUpdate; |
||
318 |
728 |
return kSignOk; |
|
319 |
} |
||
320 |
|||
321 |
731 |
SignBase::SignBase(Environment* env, Local<Object> wrap) |
|
322 |
731 |
: BaseObject(env, wrap) {} |
|
323 |
|||
324 |
void SignBase::MemoryInfo(MemoryTracker* tracker) const { |
||
325 |
tracker->TrackFieldWithSize("mdctx", mdctx_ ? kSizeOf_EVP_MD_CTX : 0); |
||
326 |
} |
||
327 |
|||
328 |
138 |
Sign::Sign(Environment* env, Local<Object> wrap) : SignBase(env, wrap) { |
|
329 |
138 |
MakeWeak(); |
|
330 |
138 |
} |
|
331 |
|||
332 |
635 |
void Sign::Initialize(Environment* env, Local<Object> target) { |
|
333 |
635 |
Local<FunctionTemplate> t = env->NewFunctionTemplate(New); |
|
334 |
|||
335 |
1270 |
t->InstanceTemplate()->SetInternalFieldCount( |
|
336 |
SignBase::kInternalFieldCount); |
||
337 |
635 |
t->Inherit(BaseObject::GetConstructorTemplate(env)); |
|
338 |
|||
339 |
635 |
env->SetProtoMethod(t, "init", SignInit); |
|
340 |
635 |
env->SetProtoMethod(t, "update", SignUpdate); |
|
341 |
635 |
env->SetProtoMethod(t, "sign", SignFinal); |
|
342 |
|||
343 |
635 |
env->SetConstructorFunction(target, "Sign", t); |
|
344 |
|||
345 |
635 |
SignJob::Initialize(env, target); |
|
346 |
|||
347 |
635 |
constexpr int kSignJobModeSign = SignConfiguration::kSign; |
|
348 |
635 |
constexpr int kSignJobModeVerify = SignConfiguration::kVerify; |
|
349 |
|||
350 |
1905 |
NODE_DEFINE_CONSTANT(target, kSignJobModeSign); |
|
351 |
1905 |
NODE_DEFINE_CONSTANT(target, kSignJobModeVerify); |
|
352 |
1905 |
NODE_DEFINE_CONSTANT(target, kSigEncDER); |
|
353 |
1905 |
NODE_DEFINE_CONSTANT(target, kSigEncP1363); |
|
354 |
1270 |
NODE_DEFINE_CONSTANT(target, RSA_PKCS1_PSS_PADDING); |
|
355 |
635 |
} |
|
356 |
|||
357 |
5174 |
void Sign::RegisterExternalReferences(ExternalReferenceRegistry* registry) { |
|
358 |
5174 |
registry->Register(New); |
|
359 |
5174 |
registry->Register(SignInit); |
|
360 |
5174 |
registry->Register(SignUpdate); |
|
361 |
5174 |
registry->Register(SignFinal); |
|
362 |
5174 |
SignJob::RegisterExternalReferences(registry); |
|
363 |
5174 |
} |
|
364 |
|||
365 |
138 |
void Sign::New(const FunctionCallbackInfo<Value>& args) { |
|
366 |
138 |
Environment* env = Environment::GetCurrent(args); |
|
367 |
138 |
new Sign(env, args.This()); |
|
368 |
138 |
} |
|
369 |
|||
370 |
138 |
void Sign::SignInit(const FunctionCallbackInfo<Value>& args) { |
|
371 |
138 |
Environment* env = Environment::GetCurrent(args); |
|
372 |
Sign* sign; |
||
373 |
✗✓ | 138 |
ASSIGN_OR_RETURN_UNWRAP(&sign, args.Holder()); |
374 |
|||
375 |
276 |
const node::Utf8Value sign_type(args.GetIsolate(), args[0]); |
|
376 |
138 |
crypto::CheckThrow(env, sign->Init(*sign_type)); |
|
377 |
} |
||
378 |
|||
379 |
125 |
void Sign::SignUpdate(const FunctionCallbackInfo<Value>& args) { |
|
380 |
125 |
Decode<Sign>(args, [](Sign* sign, const FunctionCallbackInfo<Value>& args, |
|
381 |
125 |
const char* data, size_t size) { |
|
382 |
125 |
Environment* env = Environment::GetCurrent(args); |
|
383 |
✗✓ | 125 |
if (UNLIKELY(size > INT_MAX)) |
384 |
return THROW_ERR_OUT_OF_RANGE(env, "data is too long"); |
||
385 |
125 |
Error err = sign->Update(data, size); |
|
386 |
125 |
crypto::CheckThrow(sign->env(), err); |
|
387 |
}); |
||
388 |
125 |
} |
|
389 |
|||
390 |
112 |
Sign::SignResult Sign::SignFinal( |
|
391 |
const ManagedEVPPKey& pkey, |
||
392 |
int padding, |
||
393 |
const Maybe<int>& salt_len, |
||
394 |
DSASigEnc dsa_sig_enc) { |
||
395 |
✗✓ | 112 |
if (!mdctx_) |
396 |
return SignResult(kSignNotInitialised); |
||
397 |
|||
398 |
224 |
EVPMDPointer mdctx = std::move(mdctx_); |
|
399 |
|||
400 |
✗✓ | 112 |
if (!ValidateDSAParameters(pkey.get())) |
401 |
return SignResult(kSignPrivateKey); |
||
402 |
|||
403 |
std::unique_ptr<BackingStore> buffer = |
||
404 |
224 |
Node_SignFinal(env(), std::move(mdctx), pkey, padding, salt_len); |
|
405 |
✓✓ | 112 |
Error error = buffer ? kSignOk : kSignPrivateKey; |
406 |
✓✓✗✓ |
112 |
if (error == kSignOk && dsa_sig_enc == kSigEncP1363) { |
407 |
buffer = ConvertSignatureToP1363(env(), pkey, std::move(buffer)); |
||
408 |
CHECK_NOT_NULL(buffer->Data()); |
||
409 |
} |
||
410 |
112 |
return SignResult(error, std::move(buffer)); |
|
411 |
} |
||
412 |
|||
413 |
115 |
void Sign::SignFinal(const FunctionCallbackInfo<Value>& args) { |
|
414 |
115 |
Environment* env = Environment::GetCurrent(args); |
|
415 |
Sign* sign; |
||
416 |
✗✓ | 133 |
ASSIGN_OR_RETURN_UNWRAP(&sign, args.Holder()); |
417 |
|||
418 |
ClearErrorOnReturn clear_error_on_return; |
||
419 |
|||
420 |
115 |
unsigned int offset = 0; |
|
421 |
115 |
ManagedEVPPKey key = ManagedEVPPKey::GetPrivateKeyFromJs(args, &offset, true); |
|
422 |
✓✓ | 115 |
if (!key) |
423 |
3 |
return; |
|
424 |
|||
425 |
112 |
int padding = GetDefaultSignPadding(key); |
|
426 |
✓✗✓✓ |
336 |
if (!args[offset]->IsUndefined()) { |
427 |
✓✗✗✓ |
42 |
CHECK(args[offset]->IsInt32()); |
428 |
✓✗ | 63 |
padding = args[offset].As<Int32>()->Value(); |
429 |
} |
||
430 |
|||
431 |
112 |
Maybe<int> salt_len = Nothing<int>(); |
|
432 |
✓✗✓✓ |
336 |
if (!args[offset + 1]->IsUndefined()) { |
433 |
✓✗✗✓ |
108 |
CHECK(args[offset + 1]->IsInt32()); |
434 |
✓✗ | 162 |
salt_len = Just<int>(args[offset + 1].As<Int32>()->Value()); |
435 |
} |
||
436 |
|||
437 |
✓✗✗✓ |
224 |
CHECK(args[offset + 2]->IsInt32()); |
438 |
DSASigEnc dsa_sig_enc = |
||
439 |
✓✗ | 336 |
static_cast<DSASigEnc>(args[offset + 2].As<Int32>()->Value()); |
440 |
|||
441 |
SignResult ret = sign->SignFinal( |
||
442 |
key, |
||
443 |
padding, |
||
444 |
salt_len, |
||
445 |
112 |
dsa_sig_enc); |
|
446 |
|||
447 |
✓✓ | 112 |
if (ret.error != kSignOk) |
448 |
15 |
return crypto::CheckThrow(env, ret.error); |
|
449 |
|||
450 |
Local<ArrayBuffer> ab = |
||
451 |
97 |
ArrayBuffer::New(env->isolate(), std::move(ret.signature)); |
|
452 |
194 |
args.GetReturnValue().Set( |
|
453 |
194 |
Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local<Value>())); |
|
454 |
} |
||
455 |
|||
456 |
593 |
Verify::Verify(Environment* env, Local<Object> wrap) |
|
457 |
593 |
: SignBase(env, wrap) { |
|
458 |
593 |
MakeWeak(); |
|
459 |
593 |
} |
|
460 |
|||
461 |
635 |
void Verify::Initialize(Environment* env, Local<Object> target) { |
|
462 |
635 |
Local<FunctionTemplate> t = env->NewFunctionTemplate(New); |
|
463 |
|||
464 |
1270 |
t->InstanceTemplate()->SetInternalFieldCount( |
|
465 |
SignBase::kInternalFieldCount); |
||
466 |
635 |
t->Inherit(BaseObject::GetConstructorTemplate(env)); |
|
467 |
|||
468 |
635 |
env->SetProtoMethod(t, "init", VerifyInit); |
|
469 |
635 |
env->SetProtoMethod(t, "update", VerifyUpdate); |
|
470 |
635 |
env->SetProtoMethod(t, "verify", VerifyFinal); |
|
471 |
|||
472 |
635 |
env->SetConstructorFunction(target, "Verify", t); |
|
473 |
635 |
} |
|
474 |
|||
475 |
5174 |
void Verify::RegisterExternalReferences(ExternalReferenceRegistry* registry) { |
|
476 |
5174 |
registry->Register(New); |
|
477 |
5174 |
registry->Register(VerifyInit); |
|
478 |
5174 |
registry->Register(VerifyUpdate); |
|
479 |
5174 |
registry->Register(VerifyFinal); |
|
480 |
5174 |
} |
|
481 |
|||
482 |
593 |
void Verify::New(const FunctionCallbackInfo<Value>& args) { |
|
483 |
593 |
Environment* env = Environment::GetCurrent(args); |
|
484 |
593 |
new Verify(env, args.This()); |
|
485 |
593 |
} |
|
486 |
|||
487 |
593 |
void Verify::VerifyInit(const FunctionCallbackInfo<Value>& args) { |
|
488 |
593 |
Environment* env = Environment::GetCurrent(args); |
|
489 |
Verify* verify; |
||
490 |
✗✓ | 593 |
ASSIGN_OR_RETURN_UNWRAP(&verify, args.Holder()); |
491 |
|||
492 |
1186 |
const node::Utf8Value verify_type(args.GetIsolate(), args[0]); |
|
493 |
593 |
crypto::CheckThrow(env, verify->Init(*verify_type)); |
|
494 |
} |
||
495 |
|||
496 |
603 |
void Verify::VerifyUpdate(const FunctionCallbackInfo<Value>& args) { |
|
497 |
603 |
Decode<Verify>(args, [](Verify* verify, |
|
498 |
const FunctionCallbackInfo<Value>& args, |
||
499 |
603 |
const char* data, size_t size) { |
|
500 |
603 |
Environment* env = Environment::GetCurrent(args); |
|
501 |
✗✓ | 603 |
if (UNLIKELY(size > INT_MAX)) |
502 |
return THROW_ERR_OUT_OF_RANGE(env, "data is too long"); |
||
503 |
603 |
Error err = verify->Update(data, size); |
|
504 |
603 |
crypto::CheckThrow(verify->env(), err); |
|
505 |
}); |
||
506 |
603 |
} |
|
507 |
|||
508 |
586 |
SignBase::Error Verify::VerifyFinal(const ManagedEVPPKey& pkey, |
|
509 |
const ByteSource& sig, |
||
510 |
int padding, |
||
511 |
const Maybe<int>& saltlen, |
||
512 |
bool* verify_result) { |
||
513 |
✗✓ | 586 |
if (!mdctx_) |
514 |
return kSignNotInitialised; |
||
515 |
|||
516 |
unsigned char m[EVP_MAX_MD_SIZE]; |
||
517 |
unsigned int m_len; |
||
518 |
586 |
*verify_result = false; |
|
519 |
1172 |
EVPMDPointer mdctx = std::move(mdctx_); |
|
520 |
|||
521 |
✗✓ | 586 |
if (!EVP_DigestFinal_ex(mdctx.get(), m, &m_len)) |
522 |
return kSignPublicKey; |
||
523 |
|||
524 |
586 |
EVPKeyCtxPointer pkctx(EVP_PKEY_CTX_new(pkey.get(), nullptr)); |
|
525 |
✓✗ | 586 |
if (pkctx && |
526 |
✓✗ | 1758 |
EVP_PKEY_verify_init(pkctx.get()) > 0 && |
527 |
✓✗✓✗ ✓✗ |
1758 |
ApplyRSAOptions(pkey, pkctx.get(), padding, saltlen) && |
528 |
586 |
EVP_PKEY_CTX_set_signature_md(pkctx.get(), |
|
529 |
586 |
EVP_MD_CTX_md(mdctx.get())) > 0) { |
|
530 |
586 |
const unsigned char* s = reinterpret_cast<const unsigned char*>(sig.get()); |
|
531 |
586 |
const int r = EVP_PKEY_verify(pkctx.get(), s, sig.size(), m, m_len); |
|
532 |
586 |
*verify_result = r == 1; |
|
533 |
} |
||
534 |
|||
535 |
586 |
return kSignOk; |
|
536 |
} |
||
537 |
|||
538 |
586 |
void Verify::VerifyFinal(const FunctionCallbackInfo<Value>& args) { |
|
539 |
586 |
Environment* env = Environment::GetCurrent(args); |
|
540 |
ClearErrorOnReturn clear_error_on_return; |
||
541 |
|||
542 |
Verify* verify; |
||
543 |
✗✓ | 586 |
ASSIGN_OR_RETURN_UNWRAP(&verify, args.Holder()); |
544 |
|||
545 |
586 |
unsigned int offset = 0; |
|
546 |
ManagedEVPPKey pkey = |
||
547 |
586 |
ManagedEVPPKey::GetPublicOrPrivateKeyFromJs(args, &offset); |
|
548 |
✗✓ | 586 |
if (!pkey) |
549 |
return; |
||
550 |
|||
551 |
✓✗ | 1172 |
ArrayBufferOrViewContents<char> hbuf(args[offset]); |
552 |
✗✓ | 586 |
if (UNLIKELY(!hbuf.CheckSizeInt32())) |
553 |
return THROW_ERR_OUT_OF_RANGE(env, "buffer is too big"); |
||
554 |
|||
555 |
586 |
int padding = GetDefaultSignPadding(pkey); |
|
556 |
✓✗✓✓ |
1758 |
if (!args[offset + 1]->IsUndefined()) { |
557 |
✓✗✗✓ |
744 |
CHECK(args[offset + 1]->IsInt32()); |
558 |
✓✗ | 1116 |
padding = args[offset + 1].As<Int32>()->Value(); |
559 |
} |
||
560 |
|||
561 |
586 |
Maybe<int> salt_len = Nothing<int>(); |
|
562 |
✓✗✓✓ |
1758 |
if (!args[offset + 2]->IsUndefined()) { |
563 |
✓✗✗✓ |
924 |
CHECK(args[offset + 2]->IsInt32()); |
564 |
✓✗ | 1386 |
salt_len = Just<int>(args[offset + 2].As<Int32>()->Value()); |
565 |
} |
||
566 |
|||
567 |
✓✗✗✓ |
1172 |
CHECK(args[offset + 3]->IsInt32()); |
568 |
DSASigEnc dsa_sig_enc = |
||
569 |
✓✗ | 1758 |
static_cast<DSASigEnc>(args[offset + 3].As<Int32>()->Value()); |
570 |
|||
571 |
586 |
ByteSource signature = hbuf.ToByteSource(); |
|
572 |
✓✓ | 586 |
if (dsa_sig_enc == kSigEncP1363) { |
573 |
6 |
signature = ConvertSignatureToDER(pkey, hbuf.ToByteSource()); |
|
574 |
✗✓ | 6 |
if (signature.get() == nullptr) |
575 |
return crypto::CheckThrow(env, Error::kSignMalformedSignature); |
||
576 |
} |
||
577 |
|||
578 |
bool verify_result; |
||
579 |
586 |
Error err = verify->VerifyFinal(pkey, signature, padding, |
|
580 |
salt_len, &verify_result); |
||
581 |
✗✓ | 586 |
if (err != kSignOk) |
582 |
return crypto::CheckThrow(env, err); |
||
583 |
✓✓ | 1172 |
args.GetReturnValue().Set(verify_result); |
584 |
} |
||
585 |
|||
586 |
1506 |
SignConfiguration::SignConfiguration(SignConfiguration&& other) noexcept |
|
587 |
1506 |
: job_mode(other.job_mode), |
|
588 |
1506 |
mode(other.mode), |
|
589 |
1506 |
key(std::move(other.key)), |
|
590 |
1506 |
data(std::move(other.data)), |
|
591 |
1506 |
signature(std::move(other.signature)), |
|
592 |
1506 |
digest(other.digest), |
|
593 |
1506 |
flags(other.flags), |
|
594 |
1506 |
padding(other.padding), |
|
595 |
1506 |
salt_length(other.salt_length), |
|
596 |
1506 |
dsa_encoding(other.dsa_encoding) {} |
|
597 |
|||
598 |
SignConfiguration& SignConfiguration::operator=( |
||
599 |
SignConfiguration&& other) noexcept { |
||
600 |
if (&other == this) return *this; |
||
601 |
this->~SignConfiguration(); |
||
602 |
return *new (this) SignConfiguration(std::move(other)); |
||
603 |
} |
||
604 |
|||
605 |
void SignConfiguration::MemoryInfo(MemoryTracker* tracker) const { |
||
606 |
tracker->TrackField("key", key); |
||
607 |
if (job_mode == kCryptoJobAsync) { |
||
608 |
tracker->TrackFieldWithSize("data", data.size()); |
||
609 |
tracker->TrackFieldWithSize("signature", signature.size()); |
||
610 |
} |
||
611 |
} |
||
612 |
|||
613 |
1516 |
Maybe<bool> SignTraits::AdditionalConfig( |
|
614 |
CryptoJobMode mode, |
||
615 |
const FunctionCallbackInfo<Value>& args, |
||
616 |
unsigned int offset, |
||
617 |
SignConfiguration* params) { |
||
618 |
1516 |
ClearErrorOnReturn clear_error_on_return; |
|
619 |
1516 |
Environment* env = Environment::GetCurrent(args); |
|
620 |
|||
621 |
1516 |
params->job_mode = mode; |
|
622 |
|||
623 |
✓✗✗✓ |
3032 |
CHECK(args[offset]->IsUint32()); // Sign Mode |
624 |
|||
625 |
1516 |
params->mode = |
|
626 |
✓✗ | 4548 |
static_cast<SignConfiguration::Mode>(args[offset].As<Uint32>()->Value()); |
627 |
|||
628 |
3032 |
ManagedEVPPKey key; |
|
629 |
1516 |
unsigned int keyParamOffset = offset + 1; |
|
630 |
✓✓ | 1516 |
if (params->mode == SignConfiguration::kVerify) { |
631 |
1205 |
key = ManagedEVPPKey::GetPublicOrPrivateKeyFromJs(args, &keyParamOffset); |
|
632 |
} else { |
||
633 |
311 |
key = ManagedEVPPKey::GetPrivateKeyFromJs(args, &keyParamOffset, true); |
|
634 |
} |
||
635 |
✓✓ | 1516 |
if (!key) |
636 |
9 |
return Nothing<bool>(); |
|
637 |
1507 |
params->key = key; |
|
638 |
|||
639 |
✓✗ | 4521 |
ArrayBufferOrViewContents<char> data(args[offset + 5]); |
640 |
✗✓ | 1507 |
if (UNLIKELY(!data.CheckSizeInt32())) { |
641 |
THROW_ERR_OUT_OF_RANGE(env, "data is too big"); |
||
642 |
return Nothing<bool>(); |
||
643 |
} |
||
644 |
params->data = mode == kCryptoJobAsync |
||
645 |
✓✓ | 3014 |
? data.ToCopy() |
646 |
1507 |
: data.ToByteSource(); |
|
647 |
|||
648 |
✓✗✓✓ |
4521 |
if (args[offset + 6]->IsString()) { |
649 |
✓✗ | 2826 |
Utf8Value digest(env->isolate(), args[offset + 6]); |
650 |
1413 |
params->digest = EVP_get_digestbyname(*digest); |
|
651 |
✓✓ | 1413 |
if (params->digest == nullptr) { |
652 |
1 |
THROW_ERR_CRYPTO_INVALID_DIGEST(env); |
|
653 |
1 |
return Nothing<bool>(); |
|
654 |
} |
||
655 |
} |
||
656 |
|||
657 |
✓✗✓✓ |
3012 |
if (args[offset + 7]->IsInt32()) { // Salt length |
658 |
706 |
params->flags |= SignConfiguration::kHasSaltLength; |
|
659 |
✓✗ | 2118 |
params->salt_length = args[offset + 7].As<Int32>()->Value(); |
660 |
} |
||
661 |
✓✗✓✓ |
3012 |
if (args[offset + 8]->IsUint32()) { // Padding |
662 |
619 |
params->flags |= SignConfiguration::kHasPadding; |
|
663 |
✓✗ | 1857 |
params->padding = args[offset + 8].As<Uint32>()->Value(); |
664 |
} |
||
665 |
|||
666 |
✓✗✓✓ |
3012 |
if (args[offset + 9]->IsUint32()) { // DSA Encoding |
667 |
1250 |
params->dsa_encoding = |
|
668 |
✓✗ | 3750 |
static_cast<DSASigEnc>(args[offset + 9].As<Uint32>()->Value()); |
669 |
✓✓ | 1250 |
if (params->dsa_encoding != kSigEncDER && |
670 |
✗✓ | 306 |
params->dsa_encoding != kSigEncP1363) { |
671 |
THROW_ERR_OUT_OF_RANGE(env, "invalid signature encoding"); |
||
672 |
return Nothing<bool>(); |
||
673 |
} |
||
674 |
} |
||
675 |
|||
676 |
✓✓ | 1506 |
if (params->mode == SignConfiguration::kVerify) { |
677 |
✓✗ | 2410 |
ArrayBufferOrViewContents<char> signature(args[offset + 10]); |
678 |
✗✓ | 1205 |
if (UNLIKELY(!signature.CheckSizeInt32())) { |
679 |
THROW_ERR_OUT_OF_RANGE(env, "signature is too big"); |
||
680 |
return Nothing<bool>(); |
||
681 |
} |
||
682 |
// If this is an EC key (assuming ECDSA) we need to convert the |
||
683 |
// the signature from WebCrypto format into DER format... |
||
684 |
2410 |
ManagedEVPPKey m_pkey = params->key; |
|
685 |
2410 |
Mutex::ScopedLock lock(*m_pkey.mutex()); |
|
686 |
✓✓ | 1205 |
if (UseP1363Encoding(m_pkey, params->dsa_encoding)) { |
687 |
params->signature = |
||
688 |
230 |
ConvertSignatureToDER(m_pkey, signature.ToByteSource()); |
|
689 |
} else { |
||
690 |
params->signature = mode == kCryptoJobAsync |
||
691 |
✓✓ | 1950 |
? signature.ToCopy() |
692 |
975 |
: signature.ToByteSource(); |
|
693 |
} |
||
694 |
} |
||
695 |
|||
696 |
1506 |
return Just(true); |
|
697 |
} |
||
698 |
|||
699 |
1506 |
bool SignTraits::DeriveBits( |
|
700 |
Environment* env, |
||
701 |
const SignConfiguration& params, |
||
702 |
ByteSource* out) { |
||
703 |
1506 |
ClearErrorOnReturn clear_error_on_return; |
|
704 |
3012 |
EVPMDPointer context(EVP_MD_CTX_new()); |
|
705 |
1506 |
EVP_PKEY_CTX* ctx = nullptr; |
|
706 |
|||
707 |
✓✓✗ | 1506 |
switch (params.mode) { |
708 |
301 |
case SignConfiguration::kSign: |
|
709 |
✓✓ | 602 |
if (!EVP_DigestSignInit( |
710 |
context.get(), |
||
711 |
&ctx, |
||
712 |
301 |
params.digest, |
|
713 |
nullptr, |
||
714 |
params.key.get())) { |
||
715 |
6 |
crypto::CheckThrow(env, SignBase::Error::kSignInit); |
|
716 |
6 |
return false; |
|
717 |
} |
||
718 |
295 |
break; |
|
719 |
1205 |
case SignConfiguration::kVerify: |
|
720 |
✗✓ | 2410 |
if (!EVP_DigestVerifyInit( |
721 |
context.get(), |
||
722 |
&ctx, |
||
723 |
1205 |
params.digest, |
|
724 |
nullptr, |
||
725 |
params.key.get())) { |
||
726 |
crypto::CheckThrow(env, SignBase::Error::kSignInit); |
||
727 |
return false; |
||
728 |
} |
||
729 |
1205 |
break; |
|
730 |
} |
||
731 |
|||
732 |
1500 |
int padding = params.flags & SignConfiguration::kHasPadding |
|
733 |
✓✓ | 1500 |
? params.padding |
734 |
881 |
: GetDefaultSignPadding(params.key); |
|
735 |
|||
736 |
1500 |
Maybe<int> salt_length = params.flags & SignConfiguration::kHasSaltLength |
|
737 |
✓✓ | 1500 |
? Just<int>(params.salt_length) : Nothing<int>(); |
738 |
|||
739 |
✓✓ | 1500 |
if (!ApplyRSAOptions( |
740 |
1500 |
params.key, |
|
741 |
ctx, |
||
742 |
padding, |
||
743 |
salt_length)) { |
||
744 |
7 |
crypto::CheckThrow(env, SignBase::Error::kSignPrivateKey); |
|
745 |
7 |
return false; |
|
746 |
} |
||
747 |
|||
748 |
✓✓✗ | 1493 |
switch (params.mode) { |
749 |
288 |
case SignConfiguration::kSign: { |
|
750 |
size_t len; |
||
751 |
288 |
unsigned char* data = nullptr; |
|
752 |
✓✓ | 288 |
if (IsOneShot(params.key)) { |
753 |
✗✓ | 35 |
if (!EVP_DigestSign( |
754 |
context.get(), |
||
755 |
nullptr, |
||
756 |
&len, |
||
757 |
params.data.data<unsigned char>(), |
||
758 |
params.data.size())) { |
||
759 |
crypto::CheckThrow(env, SignBase::Error::kSignPrivateKey); |
||
760 |
return false; |
||
761 |
} |
||
762 |
35 |
data = MallocOpenSSL<unsigned char>(len); |
|
763 |
✗✓ | 35 |
if (!EVP_DigestSign( |
764 |
context.get(), |
||
765 |
data, |
||
766 |
&len, |
||
767 |
params.data.data<unsigned char>(), |
||
768 |
params.data.size())) { |
||
769 |
crypto::CheckThrow(env, SignBase::Error::kSignPrivateKey); |
||
770 |
return false; |
||
771 |
} |
||
772 |
ByteSource buf = |
||
773 |
70 |
ByteSource::Allocated(reinterpret_cast<char*>(data), len); |
|
774 |
35 |
*out = std::move(buf); |
|
775 |
} else { |
||
776 |
506 |
if (!EVP_DigestSignUpdate( |
|
777 |
context.get(), |
||
778 |
253 |
params.data.data<unsigned char>(), |
|
779 |
✓✗✗✓ ✗✓ |
506 |
params.data.size()) || |
780 |
253 |
!EVP_DigestSignFinal(context.get(), nullptr, &len)) { |
|
781 |
crypto::CheckThrow(env, SignBase::Error::kSignPrivateKey); |
||
782 |
1 |
return false; |
|
783 |
} |
||
784 |
253 |
data = MallocOpenSSL<unsigned char>(len); |
|
785 |
ByteSource buf = |
||
786 |
253 |
ByteSource::Allocated(reinterpret_cast<char*>(data), len); |
|
787 |
✓✓ | 253 |
if (!EVP_DigestSignFinal(context.get(), data, &len)) { |
788 |
1 |
crypto::CheckThrow(env, SignBase::Error::kSignPrivateKey); |
|
789 |
1 |
return false; |
|
790 |
} |
||
791 |
|||
792 |
✓✓ | 252 |
if (UseP1363Encoding(params.key, params.dsa_encoding)) { |
793 |
54 |
*out = ConvertSignatureToP1363(env, params.key, buf); |
|
794 |
} else { |
||
795 |
198 |
buf.Resize(len); |
|
796 |
198 |
*out = std::move(buf); |
|
797 |
} |
||
798 |
} |
||
799 |
287 |
break; |
|
800 |
} |
||
801 |
1205 |
case SignConfiguration::kVerify: { |
|
802 |
1205 |
char* data = MallocOpenSSL<char>(1); |
|
803 |
1205 |
data[0] = 0; |
|
804 |
1205 |
*out = ByteSource::Allocated(data, 1); |
|
805 |
1205 |
if (EVP_DigestVerify( |
|
806 |
context.get(), |
||
807 |
params.signature.data<unsigned char>(), |
||
808 |
params.signature.size(), |
||
809 |
params.data.data<unsigned char>(), |
||
810 |
✓✓ | 1205 |
params.data.size()) == 1) { |
811 |
659 |
data[0] = 1; |
|
812 |
} |
||
813 |
} |
||
814 |
} |
||
815 |
|||
816 |
1492 |
return true; |
|
817 |
} |
||
818 |
|||
819 |
1492 |
Maybe<bool> SignTraits::EncodeOutput( |
|
820 |
Environment* env, |
||
821 |
const SignConfiguration& params, |
||
822 |
ByteSource* out, |
||
823 |
Local<Value>* result) { |
||
824 |
✓✓✗ | 1492 |
switch (params.mode) { |
825 |
287 |
case SignConfiguration::kSign: |
|
826 |
287 |
*result = out->ToArrayBuffer(env); |
|
827 |
287 |
break; |
|
828 |
1205 |
case SignConfiguration::kVerify: |
|
829 |
✓✓ | 1205 |
*result = out->get()[0] == 1 |
830 |
659 |
? v8::True(env->isolate()) |
|
831 |
546 |
: v8::False(env->isolate()); |
|
832 |
1205 |
break; |
|
833 |
default: |
||
834 |
UNREACHABLE(); |
||
835 |
} |
||
836 |
1492 |
return Just(!result->IsEmpty()); |
|
837 |
} |
||
838 |
|||
839 |
} // namespace crypto |
||
840 |
} // namespace node |
Generated by: GCOVR (Version 4.2) |