Branch data Line data Source code
1 : : /*
2 : : * Copyright (C) 2026 by Thun Lu. All rights reserved.
3 : : * Author: Thun Lu <thun.lu@zohomail.cn>
4 : : * Repo: https://github.com/thun-res/vlink
5 : : * _ __ __ _ __
6 : : * | | / / / / (_) ____ / /__
7 : : * | | / / / / / / / __ \ / //_/
8 : : * | |/ / / /___ / / / / / / / ,<
9 : : * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10 : : *
11 : : * Licensed under the Apache License, Version 2.0 (the "License");
12 : : * you may not use this file except in compliance with the License.
13 : : * You may obtain a copy of the License at
14 : : *
15 : : * http://www.apache.org/licenses/LICENSE-2.0
16 : : *
17 : : * Unless required by applicable law or agreed to in writing, software
18 : : * distributed under the License is distributed on an "AS IS" BASIS,
19 : : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 : : * See the License for the specific language governing permissions and
21 : : * limitations under the License.
22 : : */
23 : :
24 : : #include "./extension/security.h"
25 : :
26 : : #include <algorithm>
27 : : #include <array>
28 : : #include <cstring>
29 : : #include <fstream>
30 : : #include <iterator>
31 : : #include <limits>
32 : : #include <map>
33 : : #include <mutex>
34 : : #include <string>
35 : : #include <string_view>
36 : : #include <utility>
37 : : #include <vector>
38 : :
39 : : #include "./base/logger.h"
40 : :
41 : : #ifdef VLINK_ENABLE_SECURITY
42 : : #include <openssl/bio.h>
43 : : #include <openssl/evp.h>
44 : : #include <openssl/pem.h>
45 : : #include <openssl/rand.h>
46 : : #include <openssl/rsa.h>
47 : : #endif
48 : :
49 : : namespace vlink {
50 : :
51 : : #ifdef VLINK_ENABLE_SECURITY
52 : :
53 : : [[maybe_unused]] static constexpr size_t kAesKeySize = 16U;
54 : : [[maybe_unused]] static constexpr size_t kAesNonceSize = 12U;
55 : : [[maybe_unused]] static constexpr size_t kAesTagSize = 16U;
56 : : [[maybe_unused]] static constexpr size_t kRsaWrapLenFieldSize = 2U;
57 : : [[maybe_unused]] static constexpr size_t kRsaSigLenFieldSize = 2U;
58 : : [[maybe_unused]] static constexpr size_t kAsymHeaderFieldsSize = kRsaWrapLenFieldSize + kRsaSigLenFieldSize;
59 : : [[maybe_unused]] static constexpr int kRsaMinBits = 2048;
60 : : [[maybe_unused]] static constexpr size_t kPbkdf2MinSaltSize = 16U;
61 : : [[maybe_unused]] static constexpr uint8_t kEnvelopeMagic0 = 'V';
62 : : [[maybe_unused]] static constexpr uint8_t kEnvelopeMagic1 = 'S';
63 : : [[maybe_unused]] static constexpr uint8_t kEnvelopeVersion = 2U;
64 : : [[maybe_unused]] static constexpr uint8_t kEnvelopeModeSymmetric = 1U;
65 : : [[maybe_unused]] static constexpr uint8_t kEnvelopeModeAsymmetric = 2U;
66 : : [[maybe_unused]] static constexpr size_t kEnvelopeFixedHeaderSize = 34U;
67 : : [[maybe_unused]] static constexpr uint32_t kReplayWindowMax = 65536U;
68 : : [[maybe_unused]] static constexpr size_t kReplayPeerMax = 1024U;
69 : : [[maybe_unused]] static constexpr char kAadDomain[] = "vlink-security-v2";
70 : : [[maybe_unused]] static constexpr size_t kAadDomainSize = sizeof(kAadDomain) - 1U;
71 : :
72 : : #ifdef RSA_PSS_SALTLEN_DIGEST
73 : : [[maybe_unused]] static constexpr int kRsaPssSaltLenDigest = RSA_PSS_SALTLEN_DIGEST;
74 : : #else
75 : : [[maybe_unused]] static constexpr int kRsaPssSaltLenDigest = -1;
76 : : #endif
77 : :
78 : : struct ReplayWindow final {
79 : : uint64_t highest{0};
80 : : std::vector<uint64_t> words;
81 : : };
82 : :
83 : : using PeerReplayMap = std::map<uint64_t, ReplayWindow>;
84 : :
85 : : struct SymmetricKeySlot final {
86 : : Bytes key;
87 : : PeerReplayMap peers;
88 : : bool peer_limit_reported{false};
89 : : };
90 : :
91 : : struct EnvelopeHeader final {
92 : : uint8_t mode{0};
93 : : uint16_t flags{0};
94 : : uint64_t sender_id{0};
95 : : uint64_t seq{0};
96 : : const uint8_t* nonce{nullptr};
97 : : size_t size{0};
98 : : };
99 : :
100 : : struct AadParts final {
101 : : const std::string* context{nullptr};
102 : : const uint8_t* header{nullptr};
103 : : size_t header_len{0};
104 : : const uint8_t* extra{nullptr};
105 : : size_t extra_len{0};
106 : : };
107 : :
108 : : struct DigestScrub final {
109 : : uint8_t* ptr{nullptr};
110 : : size_t size{0};
111 : :
112 : 2146 : DigestScrub(uint8_t* p, size_t n) noexcept : ptr(p), size(n) {}
113 : :
114 : 2146 : ~DigestScrub() noexcept { OPENSSL_cleanse(ptr, size); }
115 : :
116 : : DigestScrub(const DigestScrub&) = delete;
117 : :
118 : : DigestScrub& operator=(const DigestScrub&) = delete;
119 : :
120 : : DigestScrub(DigestScrub&&) = delete;
121 : :
122 : : DigestScrub& operator=(DigestScrub&&) = delete;
123 : : };
124 : :
125 : : struct EvpCipherCtxDeleter final {
126 : 2158 : void operator()(EVP_CIPHER_CTX* ptr) const noexcept { EVP_CIPHER_CTX_free(ptr); }
127 : : };
128 : :
129 : : struct EvpPkeyDeleter final {
130 : 72 : void operator()(EVP_PKEY* ptr) const noexcept { EVP_PKEY_free(ptr); }
131 : : };
132 : :
133 : : struct EvpPkeyCtxDeleter final {
134 : 56 : void operator()(EVP_PKEY_CTX* ptr) const noexcept { EVP_PKEY_CTX_free(ptr); }
135 : : };
136 : :
137 : : struct BioDeleter final {
138 : 80 : void operator()(BIO* ptr) const noexcept { BIO_free(ptr); }
139 : : };
140 : :
141 : : using EvpCipherCtxPtr = std::unique_ptr<EVP_CIPHER_CTX, EvpCipherCtxDeleter>;
142 : : using EvpPkeyPtr = std::unique_ptr<EVP_PKEY, EvpPkeyDeleter>;
143 : : using EvpPkeyCtxPtr = std::unique_ptr<EVP_PKEY_CTX, EvpPkeyCtxDeleter>;
144 : : using BioPtr = std::unique_ptr<BIO, BioDeleter>;
145 : :
146 : 31830 : [[nodiscard]] static inline bool size_fits_int(size_t value) noexcept {
147 : 31830 : return value <= static_cast<size_t>(std::numeric_limits<int>::max());
148 : : }
149 : :
150 : 8118 : static inline void write_u16_le(uint8_t* dst, uint16_t value) noexcept {
151 : 8118 : dst[0] = static_cast<uint8_t>(value & 0xFFU);
152 : 8118 : dst[1] = static_cast<uint8_t>((value >> 8U) & 0xFFU);
153 : 8118 : }
154 : :
155 : 2689 : static inline uint16_t read_u16_le(const uint8_t* src) noexcept {
156 : 2689 : return static_cast<uint16_t>(static_cast<uint16_t>(src[0]) | (static_cast<uint16_t>(src[1]) << 8U));
157 : : }
158 : :
159 : 5390 : static inline void write_u64_le(uint8_t* dst, uint64_t value) noexcept {
160 [ + + ]: 48510 : for (size_t i = 0; i < sizeof(value); ++i) {
161 : 43120 : dst[i] = static_cast<uint8_t>((value >> (i * 8U)) & 0xFFU);
162 : : }
163 : 5390 : }
164 : :
165 : 7374 : static inline uint64_t read_u64_le(const uint8_t* src) noexcept {
166 : 7374 : uint64_t value = 0;
167 : :
168 [ + + ]: 66366 : for (size_t i = 0; i < sizeof(value); ++i) {
169 : 58992 : value |= static_cast<uint64_t>(src[i]) << (i * 8U);
170 : : }
171 : :
172 : 7374 : return value;
173 : : }
174 : :
175 : 21 : static inline void copy_string_bytes(uint8_t* dst, std::string_view value) noexcept {
176 : 21 : std::copy_n(reinterpret_cast<const uint8_t*>(value.data()), value.size(), dst);
177 : 21 : }
178 : :
179 : 2214 : static uint32_t normalize_replay_window(uint32_t window) noexcept {
180 [ + + ]: 2214 : return (window > kReplayWindowMax) ? kReplayWindowMax : window;
181 : : }
182 : :
183 : 1567 : static bool accept_replay(ReplayWindow& replay, uint64_t seq, uint32_t window_bits) {
184 : 1567 : const auto word_count = static_cast<size_t>((window_bits + 63U) / 64U);
185 : :
186 [ + + ]: 1567 : if VUNLIKELY (replay.words.size() != word_count) {
187 [ + - ]: 1050 : replay.words.assign(word_count, 0U);
188 : 1050 : replay.highest = 0U;
189 : : }
190 : :
191 : 1567 : const auto slot_count = static_cast<uint64_t>(word_count * 64U);
192 : :
193 : 1565 : auto clear_seq = [&replay, slot_count](uint64_t value) {
194 : 1565 : const auto bit = value % slot_count;
195 : 1565 : replay.words[static_cast<size_t>(bit / 64U)] &= ~(uint64_t{1} << (bit % 64U));
196 : 3132 : };
197 : :
198 : 1566 : auto test_seq = [&replay, slot_count](uint64_t value) -> bool {
199 : 1566 : const auto bit = value % slot_count;
200 : 1566 : return (replay.words[static_cast<size_t>(bit / 64U)] & (uint64_t{1} << (bit % 64U))) != 0U;
201 : 1567 : };
202 : :
203 : 1564 : auto set_seq = [&replay, slot_count](uint64_t value) {
204 : 1564 : const auto bit = value % slot_count;
205 : 1564 : replay.words[static_cast<size_t>(bit / 64U)] |= uint64_t{1} << (bit % 64U);
206 : 3131 : };
207 : :
208 [ + + ]: 1567 : if VLIKELY (seq > replay.highest) {
209 : 1480 : const auto gap = seq - replay.highest;
210 : :
211 [ + + ]: 1480 : if VUNLIKELY (gap >= slot_count) {
212 [ + - ]: 1 : std::fill(replay.words.begin(), replay.words.end(), 0U);
213 [ + + ]: 1479 : } else if VLIKELY (gap == 1U) {
214 : 1412 : clear_seq(seq);
215 : : } else {
216 [ + + ]: 220 : for (uint64_t offset = 1U; offset <= gap; ++offset) {
217 : 153 : clear_seq(replay.highest + offset);
218 : : }
219 : : }
220 : :
221 : 1480 : replay.highest = seq;
222 [ + + ]: 87 : } else if VUNLIKELY (replay.highest - seq >= static_cast<uint64_t>(window_bits)) {
223 : 1 : return false;
224 : : }
225 : :
226 [ + + ]: 1566 : if VUNLIKELY (test_seq(seq)) {
227 : 2 : return false;
228 : : }
229 : :
230 : 1564 : set_seq(seq);
231 : :
232 : 1564 : return true;
233 : : }
234 : :
235 : 2595 : static bool accept_peer_replay(PeerReplayMap& peers, bool& peer_limit_reported, uint64_t sender_id, uint64_t seq,
236 : : uint32_t window_bits) {
237 [ + + ]: 2595 : if (window_bits == 0U) {
238 : 1027 : return true;
239 : : }
240 : :
241 [ - + ]: 1568 : if VUNLIKELY (seq == 0U) {
242 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
243 : : }
244 : :
245 [ + - ]: 1568 : auto peer_iter = peers.find(sender_id);
246 : :
247 [ + + ]: 1568 : if (peer_iter == peers.end()) {
248 [ + + ]: 1051 : if VUNLIKELY (peers.size() >= kReplayPeerMax) {
249 [ + - ]: 1 : if VUNLIKELY (!peer_limit_reported) {
250 [ + - + - ]: 2 : VLOG_W("Security: replay peer limit reached; rejecting new sender identities.");
251 : 1 : peer_limit_reported = true;
252 : : }
253 : :
254 : 1 : return false;
255 : : }
256 : :
257 [ + - ]: 1050 : peer_iter = peers.try_emplace(sender_id).first;
258 : : }
259 : :
260 [ + - ]: 1567 : return accept_replay(peer_iter->second, seq, window_bits);
261 : : }
262 : :
263 : 2695 : static bool write_envelope_header(uint8_t mode, uint64_t sender_id, uint64_t seq, const uint8_t* nonce, uint8_t* dst,
264 : : size_t dst_size) {
265 [ + - - + : 2695 : if VUNLIKELY (nonce == nullptr || dst == nullptr) {
- + ]
266 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
267 : : }
268 : :
269 [ - + ]: 2695 : if VUNLIKELY (dst_size < kEnvelopeFixedHeaderSize) {
270 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
271 : : }
272 : :
273 : 2695 : dst[0] = kEnvelopeMagic0;
274 : 2695 : dst[1] = kEnvelopeMagic1;
275 : 2695 : dst[2] = kEnvelopeVersion;
276 : 2695 : dst[3] = mode;
277 : 2695 : write_u16_le(dst + 4U, 0U);
278 : 2695 : write_u64_le(dst + 6U, sender_id);
279 : 2695 : write_u64_le(dst + 14U, seq);
280 : 2695 : std::memcpy(dst + 22U, nonce, kAesNonceSize);
281 : :
282 : 2695 : return true;
283 : : }
284 : :
285 : 27 : static bool build_envelope_header(uint8_t mode, uint64_t sender_id, uint64_t seq, const uint8_t* nonce, Bytes& out) {
286 : 27 : out = Bytes::create(kEnvelopeFixedHeaderSize);
287 : :
288 [ - + ]: 27 : if VUNLIKELY (out.data() == nullptr) {
289 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
290 : : }
291 : :
292 : 27 : return write_envelope_header(mode, sender_id, seq, nonce, out.data(), out.size());
293 : : }
294 : :
295 : 2641 : static bool parse_envelope_header(const Bytes& in, EnvelopeHeader& header) {
296 [ + + - + : 2641 : if VUNLIKELY (in.size() < kEnvelopeFixedHeaderSize || in.data() == nullptr) {
+ + ]
297 : 4 : return false;
298 : : }
299 : :
300 : 2637 : const uint8_t* src = in.data();
301 : :
302 [ + + - + : 2637 : if VUNLIKELY (src[0] != kEnvelopeMagic0 || src[1] != kEnvelopeMagic1 || src[2] != kEnvelopeVersion) {
+ + + + +
+ ]
303 : 4 : return false;
304 : : }
305 : :
306 : 2633 : header.mode = src[3];
307 : 2633 : header.flags = read_u16_le(src + 4U);
308 : 2633 : header.sender_id = read_u64_le(src + 6U);
309 : 2633 : header.seq = read_u64_le(src + 14U);
310 : 2633 : header.nonce = src + 22U;
311 : 2633 : header.size = kEnvelopeFixedHeaderSize;
312 : :
313 : 2633 : return true;
314 : : }
315 : :
316 : 52 : static Bytes build_aad(const std::string& context, const uint8_t* header, size_t header_len,
317 : : const uint8_t* extra = nullptr, size_t extra_len = 0U) {
318 [ + - - + : 52 : if VUNLIKELY (context.size() > 0xFFFFU || header == nullptr || header_len == 0U ||
+ - - + +
- + - - +
- + - + ]
319 : : (extra_len > 0U && extra == nullptr)) {
320 : : return Bytes{}; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
321 : : }
322 : :
323 : 52 : const size_t total = kAadDomainSize + sizeof(uint16_t) + context.size() + header_len + extra_len;
324 : 52 : Bytes out = Bytes::create(total);
325 : :
326 [ - + ]: 52 : if VUNLIKELY (out.data() == nullptr) {
327 : : return Bytes{}; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
328 : : }
329 : :
330 : 52 : uint8_t* dst = out.data();
331 : 52 : std::memcpy(dst, kAadDomain, kAadDomainSize);
332 : 52 : dst += kAadDomainSize;
333 : 52 : write_u16_le(dst, static_cast<uint16_t>(context.size()));
334 : 52 : dst += sizeof(uint16_t);
335 : :
336 [ + + ]: 52 : if (!context.empty()) {
337 : 21 : copy_string_bytes(dst, context);
338 : 21 : dst += context.size();
339 : : }
340 : :
341 : 52 : std::memcpy(dst, header, header_len);
342 : 52 : dst += header_len;
343 : :
344 [ + - ]: 52 : if (extra_len > 0U) {
345 : 52 : std::memcpy(dst, extra, extra_len);
346 : : }
347 : :
348 : 52 : return out;
349 : 52 : }
350 : :
351 : 5265 : static bool aad_parts_valid(const AadParts& aad) noexcept {
352 [ + - + - : 10530 : return aad.context != nullptr && aad.context->size() <= 0xFFFFU && aad.header != nullptr && aad.header_len > 0U &&
+ - + - ]
353 [ + - + - : 15795 : size_fits_int(kAadDomainSize) && size_fits_int(sizeof(uint16_t)) && size_fits_int(aad.context->size()) &&
+ - ]
354 [ + - + - : 15795 : size_fits_int(aad.header_len) && size_fits_int(aad.extra_len) && (aad.extra_len == 0U || aad.extra != nullptr);
- + - - ]
355 : : }
356 : :
357 : 13340 : static bool encrypt_aad_chunk(EVP_CIPHER_CTX* ctx, const uint8_t* data, size_t size) noexcept {
358 [ + + ]: 13340 : if (size == 0U) {
359 : 5333 : return true;
360 : : }
361 : :
362 : 8007 : int len_update = 0;
363 : :
364 : 8007 : return EVP_EncryptUpdate(ctx, nullptr, &len_update, data, static_cast<int>(size)) == 1;
365 : : }
366 : :
367 : 12985 : static bool decrypt_aad_chunk(EVP_CIPHER_CTX* ctx, const uint8_t* data, size_t size) noexcept {
368 [ + + ]: 12985 : if (size == 0U) {
369 : 5190 : return true;
370 : : }
371 : :
372 : 7795 : int len_update = 0;
373 : :
374 : 7795 : return EVP_DecryptUpdate(ctx, nullptr, &len_update, data, static_cast<int>(size)) == 1;
375 : : }
376 : :
377 : 2668 : static bool encrypt_aad_parts(EVP_CIPHER_CTX* ctx, const AadParts& aad) noexcept {
378 [ - + ]: 2668 : if VUNLIKELY (!aad_parts_valid(aad)) {
379 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
380 : : }
381 : :
382 : 2668 : uint8_t context_len[sizeof(uint16_t)] = {};
383 : 2668 : write_u16_le(context_len, static_cast<uint16_t>(aad.context->size()));
384 : :
385 [ + - - + : 2668 : if VUNLIKELY (!encrypt_aad_chunk(ctx, reinterpret_cast<const uint8_t*>(kAadDomain), kAadDomainSize) ||
+ - - + +
- - + + -
- + - + ]
386 : : !encrypt_aad_chunk(ctx, context_len, sizeof(context_len)) ||
387 : : !encrypt_aad_chunk(ctx, reinterpret_cast<const uint8_t*>(aad.context->data()), aad.context->size()) ||
388 : : !encrypt_aad_chunk(ctx, aad.header, aad.header_len) ||
389 : : !encrypt_aad_chunk(ctx, aad.extra, aad.extra_len)) {
390 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
391 : : }
392 : :
393 : 2668 : return true;
394 : : }
395 : :
396 : 2597 : static bool decrypt_aad_parts(EVP_CIPHER_CTX* ctx, const AadParts& aad) noexcept {
397 [ - + ]: 2597 : if VUNLIKELY (!aad_parts_valid(aad)) {
398 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
399 : : }
400 : :
401 : 2597 : uint8_t context_len[sizeof(uint16_t)] = {};
402 : 2597 : write_u16_le(context_len, static_cast<uint16_t>(aad.context->size()));
403 : :
404 [ + - - + : 2597 : if VUNLIKELY (!decrypt_aad_chunk(ctx, reinterpret_cast<const uint8_t*>(kAadDomain), kAadDomainSize) ||
+ - - + +
- - + + -
- + - + ]
405 : : !decrypt_aad_chunk(ctx, context_len, sizeof(context_len)) ||
406 : : !decrypt_aad_chunk(ctx, reinterpret_cast<const uint8_t*>(aad.context->data()), aad.context->size()) ||
407 : : !decrypt_aad_chunk(ctx, aad.header, aad.header_len) ||
408 : : !decrypt_aad_chunk(ctx, aad.extra, aad.extra_len)) {
409 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
410 : : }
411 : :
412 : 2597 : return true;
413 : : }
414 : :
415 : 2112 : static Bytes derive_aes_key_sha256(const uint8_t* seed, size_t seed_size) noexcept {
416 : 2112 : Bytes out = Bytes::create(kAesKeySize);
417 : :
418 [ - + ]: 2112 : if VUNLIKELY (out.data() == nullptr) {
419 : : return Bytes{}; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
420 : : }
421 : :
422 : : uint8_t digest[EVP_MAX_MD_SIZE];
423 : 2112 : DigestScrub digest_scrub{digest, sizeof(digest)};
424 : 2112 : unsigned int digest_len = 0;
425 : :
426 [ - + ]: 2112 : if VUNLIKELY (EVP_Digest(seed, seed_size, digest, &digest_len, EVP_sha256(), nullptr) != 1) {
427 : : return Bytes{}; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
428 : : }
429 : :
430 : 2112 : std::memcpy(out.data(), digest, kAesKeySize);
431 : :
432 : 2112 : return out;
433 : 2112 : }
434 : :
435 : 2098 : static Bytes derive_aes_key_sha256(const std::string& seed) noexcept {
436 : 2098 : return derive_aes_key_sha256(reinterpret_cast<const uint8_t*>(seed.data()), seed.size());
437 : : }
438 : :
439 : 10 : static Bytes derive_aes_key_pbkdf2(const std::string& passphrase, const uint8_t* salt, size_t salt_len,
440 : : uint32_t iterations) noexcept {
441 [ + - - + : 10 : if VUNLIKELY (!size_fits_int(passphrase.size()) || !size_fits_int(salt_len) ||
+ - + + +
+ ]
442 : : iterations > static_cast<uint32_t>(std::numeric_limits<int>::max())) {
443 : 1 : return Bytes{};
444 : : }
445 : :
446 : 9 : Bytes out = Bytes::create(kAesKeySize);
447 : :
448 [ - + ]: 9 : if VUNLIKELY (out.data() == nullptr) {
449 : : return Bytes{}; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
450 : : }
451 : :
452 : : const int rv =
453 : 9 : PKCS5_PBKDF2_HMAC(passphrase.data(), static_cast<int>(passphrase.size()), salt, static_cast<int>(salt_len),
454 : : static_cast<int>(iterations), EVP_sha256(), static_cast<int>(kAesKeySize), out.data());
455 : :
456 [ - + ]: 9 : if VUNLIKELY (rv != 1) {
457 : : OPENSSL_cleanse(out.data(), out.size()); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
458 : : return Bytes{}; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
459 : : }
460 : :
461 : 9 : return out;
462 : 9 : }
463 : :
464 : 27 : static bool aes_gcm_encrypt(const uint8_t* key, const uint8_t* nonce, const uint8_t* in, size_t in_len,
465 : : const uint8_t* aad, size_t aad_len, uint8_t* cipher_out, uint8_t* tag_out) noexcept {
466 [ + - - + : 27 : if VUNLIKELY (!size_fits_int(in_len) || !size_fits_int(aad_len)) {
- + ]
467 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
468 : : }
469 : :
470 : 27 : EvpCipherCtxPtr ctx{EVP_CIPHER_CTX_new()};
471 : :
472 [ - + ]: 27 : if VUNLIKELY (!ctx) {
473 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
474 : : }
475 : :
476 [ - + ]: 27 : if VUNLIKELY (EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) {
477 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
478 : : }
479 : :
480 [ - + ]: 27 : if VUNLIKELY (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, static_cast<int>(kAesNonceSize), nullptr) != 1) {
481 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
482 : : }
483 : :
484 [ - + ]: 27 : if VUNLIKELY (EVP_EncryptInit_ex(ctx.get(), nullptr, nullptr, key, nonce) != 1) {
485 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
486 : : }
487 : :
488 : 27 : int len_update = 0;
489 : :
490 [ + - ]: 27 : if VLIKELY (aad_len > 0U) {
491 [ - + ]: 27 : if VUNLIKELY (EVP_EncryptUpdate(ctx.get(), nullptr, &len_update, aad, static_cast<int>(aad_len)) != 1) {
492 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
493 : : }
494 : : }
495 : :
496 [ + - ]: 27 : if VLIKELY (in_len > 0U) {
497 [ - + ]: 27 : if VUNLIKELY (EVP_EncryptUpdate(ctx.get(), cipher_out, &len_update, in, static_cast<int>(in_len)) != 1) {
498 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
499 : : }
500 : : }
501 : :
502 : 27 : int len_final = 0;
503 : :
504 [ - + ]: 27 : if VUNLIKELY (EVP_EncryptFinal_ex(ctx.get(), cipher_out + len_update, &len_final) != 1) {
505 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
506 : : }
507 : :
508 [ - + ]: 27 : if VUNLIKELY (static_cast<size_t>(len_update) + static_cast<size_t>(len_final) != in_len) {
509 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
510 : : }
511 : :
512 [ - + ]: 27 : if VUNLIKELY (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, static_cast<int>(kAesTagSize), tag_out) != 1) {
513 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
514 : : }
515 : :
516 : 27 : return true;
517 : 27 : }
518 : :
519 : 5265 : static EVP_CIPHER_CTX* ensure_gcm_ctx(EvpCipherCtxPtr& slot, const uint8_t* key, bool for_encrypt) noexcept {
520 [ + + ]: 5265 : if VLIKELY (slot) {
521 : 3151 : return slot.get();
522 : : }
523 : :
524 : 2114 : EvpCipherCtxPtr ctx{EVP_CIPHER_CTX_new()};
525 : :
526 [ - + ]: 2114 : if VUNLIKELY (!ctx) {
527 : : return nullptr; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
528 : : }
529 : :
530 [ + + ]: 2114 : const int cipher_ok = for_encrypt ? EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr)
531 : 32 : : EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr);
532 : :
533 [ - + ]: 2114 : if VUNLIKELY (cipher_ok != 1) {
534 : : return nullptr; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
535 : : }
536 : :
537 [ - + ]: 2114 : if VUNLIKELY (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, static_cast<int>(kAesNonceSize), nullptr) != 1) {
538 : : return nullptr; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
539 : : }
540 : :
541 [ + + ]: 2114 : const int key_ok = for_encrypt ? EVP_EncryptInit_ex(ctx.get(), nullptr, nullptr, key, nullptr)
542 : 32 : : EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, key, nullptr);
543 : :
544 [ - + ]: 2114 : if VUNLIKELY (key_ok != 1) {
545 : : return nullptr; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
546 : : }
547 : :
548 : 2114 : slot = std::move(ctx);
549 : :
550 : 2114 : return slot.get();
551 : 2114 : }
552 : :
553 : 2668 : static bool aes_gcm_encrypt_parts(EVP_CIPHER_CTX* ctx, const uint8_t* nonce, const uint8_t* in, size_t in_len,
554 : : const AadParts& aad, uint8_t* cipher_out, uint8_t* tag_out) noexcept {
555 [ - + ]: 2668 : if VUNLIKELY (!size_fits_int(in_len)) {
556 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
557 : : }
558 : :
559 [ - + ]: 2668 : if VUNLIKELY (EVP_EncryptInit_ex(ctx, nullptr, nullptr, nullptr, nonce) != 1) {
560 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
561 : : }
562 : :
563 [ - + ]: 2668 : if VUNLIKELY (!encrypt_aad_parts(ctx, aad)) {
564 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
565 : : }
566 : :
567 : 2668 : int len_update = 0;
568 : :
569 [ + - ]: 2668 : if VLIKELY (in_len > 0U) {
570 [ - + ]: 2668 : if VUNLIKELY (EVP_EncryptUpdate(ctx, cipher_out, &len_update, in, static_cast<int>(in_len)) != 1) {
571 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
572 : : }
573 : : }
574 : :
575 : 2668 : int len_final = 0;
576 : :
577 [ - + ]: 2668 : if VUNLIKELY (EVP_EncryptFinal_ex(ctx, cipher_out + len_update, &len_final) != 1) {
578 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
579 : : }
580 : :
581 [ - + ]: 2668 : if VUNLIKELY (static_cast<size_t>(len_update) + static_cast<size_t>(len_final) != in_len) {
582 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
583 : : }
584 : :
585 [ - + ]: 2668 : if VUNLIKELY (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, static_cast<int>(kAesTagSize), tag_out) != 1) {
586 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
587 : : }
588 : :
589 : 2668 : return true;
590 : : }
591 : :
592 : 17 : static bool aes_gcm_decrypt(const uint8_t* key, const uint8_t* nonce, const uint8_t* cipher, size_t cipher_len,
593 : : const uint8_t* aad, size_t aad_len, const uint8_t* tag, uint8_t* plain_out) noexcept {
594 [ + - - + : 17 : if VUNLIKELY (!size_fits_int(cipher_len) || !size_fits_int(aad_len)) {
- + ]
595 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
596 : : }
597 : :
598 : 17 : EvpCipherCtxPtr ctx{EVP_CIPHER_CTX_new()};
599 : :
600 [ - + ]: 17 : if VUNLIKELY (!ctx) {
601 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
602 : : }
603 : :
604 [ - + ]: 17 : if VUNLIKELY (EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) {
605 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
606 : : }
607 : :
608 [ - + ]: 17 : if VUNLIKELY (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, static_cast<int>(kAesNonceSize), nullptr) != 1) {
609 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
610 : : }
611 : :
612 [ - + ]: 17 : if VUNLIKELY (EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, key, nonce) != 1) {
613 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
614 : : }
615 : :
616 : 17 : int len_update = 0;
617 : :
618 [ + - ]: 17 : if VLIKELY (aad_len > 0U) {
619 [ - + ]: 17 : if VUNLIKELY (EVP_DecryptUpdate(ctx.get(), nullptr, &len_update, aad, static_cast<int>(aad_len)) != 1) {
620 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
621 : : }
622 : : }
623 : :
624 [ + - ]: 17 : if VLIKELY (cipher_len > 0U) {
625 [ - + ]: 17 : if VUNLIKELY (EVP_DecryptUpdate(ctx.get(), plain_out, &len_update, cipher, static_cast<int>(cipher_len)) != 1) {
626 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
627 : : }
628 : : }
629 : :
630 [ - + ]: 17 : if VUNLIKELY (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, static_cast<int>(kAesTagSize),
631 : : const_cast<uint8_t*>(tag)) != 1) {
632 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
633 : : }
634 : :
635 : 17 : int len_final = 0;
636 : :
637 [ + + ]: 17 : if VUNLIKELY (EVP_DecryptFinal_ex(ctx.get(), plain_out + len_update, &len_final) <= 0) {
638 : 8 : return false;
639 : : }
640 : :
641 [ - + ]: 9 : if VUNLIKELY (static_cast<size_t>(len_update) + static_cast<size_t>(len_final) != cipher_len) {
642 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
643 : : }
644 : :
645 : 9 : return true;
646 : 17 : }
647 : :
648 : 2597 : static bool aes_gcm_decrypt_parts(EVP_CIPHER_CTX* ctx, const uint8_t* nonce, const uint8_t* cipher, size_t cipher_len,
649 : : const AadParts& aad, const uint8_t* tag, uint8_t* plain_out) noexcept {
650 [ - + ]: 2597 : if VUNLIKELY (!size_fits_int(cipher_len)) {
651 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
652 : : }
653 : :
654 [ - + ]: 2597 : if VUNLIKELY (EVP_DecryptInit_ex(ctx, nullptr, nullptr, nullptr, nonce) != 1) {
655 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
656 : : }
657 : :
658 [ - + ]: 2597 : if VUNLIKELY (!decrypt_aad_parts(ctx, aad)) {
659 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
660 : : }
661 : :
662 : 2597 : int len_update = 0;
663 : :
664 [ + - ]: 2597 : if VLIKELY (cipher_len > 0U) {
665 [ - + ]: 2597 : if VUNLIKELY (EVP_DecryptUpdate(ctx, plain_out, &len_update, cipher, static_cast<int>(cipher_len)) != 1) {
666 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
667 : : }
668 : : }
669 : :
670 [ - + ]: 2597 : if VUNLIKELY (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, static_cast<int>(kAesTagSize),
671 : : const_cast<uint8_t*>(tag)) != 1) {
672 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
673 : : }
674 : :
675 : 2597 : int len_final = 0;
676 : :
677 [ + + ]: 2597 : if VUNLIKELY (EVP_DecryptFinal_ex(ctx, plain_out + len_update, &len_final) <= 0) {
678 : 11 : return false;
679 : : }
680 : :
681 [ - + ]: 2586 : if VUNLIKELY (static_cast<size_t>(len_update) + static_cast<size_t>(len_final) != cipher_len) {
682 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
683 : : }
684 : :
685 : 2586 : return true;
686 : : }
687 : :
688 : 27 : static bool rsa_oaep_encrypt(EVP_PKEY* pkey, const uint8_t* in, size_t in_len, Bytes& out) noexcept {
689 [ - + ]: 27 : if VUNLIKELY (!size_fits_int(in_len)) {
690 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
691 : : }
692 : :
693 : 27 : EvpPkeyCtxPtr ctx{EVP_PKEY_CTX_new(pkey, nullptr)};
694 : :
695 [ - + ]: 27 : if VUNLIKELY (!ctx) {
696 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
697 : : }
698 : :
699 [ - + ]: 27 : if VUNLIKELY (EVP_PKEY_encrypt_init(ctx.get()) <= 0) {
700 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
701 : : }
702 : :
703 [ - + ]: 27 : if VUNLIKELY (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_OAEP_PADDING) <= 0) {
704 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
705 : : }
706 : :
707 [ - + ]: 27 : if VUNLIKELY (EVP_PKEY_CTX_set_rsa_oaep_md(ctx.get(), EVP_sha256()) <= 0) {
708 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
709 : : }
710 : :
711 [ - + ]: 27 : if VUNLIKELY (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx.get(), EVP_sha256()) <= 0) {
712 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
713 : : }
714 : :
715 : 27 : size_t cipher_len = 0;
716 : :
717 [ - + ]: 27 : if VUNLIKELY (EVP_PKEY_encrypt(ctx.get(), nullptr, &cipher_len, in, in_len) <= 0) {
718 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
719 : : }
720 : :
721 : 27 : out = Bytes::create(cipher_len);
722 : :
723 [ - + ]: 27 : if VUNLIKELY (out.data() == nullptr) {
724 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
725 : : }
726 : :
727 [ - + ]: 27 : if VUNLIKELY (EVP_PKEY_encrypt(ctx.get(), out.data(), &cipher_len, in, in_len) <= 0) {
728 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
729 : : }
730 : :
731 [ - + ]: 27 : if VUNLIKELY (!out.resize(cipher_len)) {
732 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
733 : : }
734 : :
735 : 27 : return true;
736 : 27 : }
737 : :
738 : 22 : static bool rsa_oaep_decrypt(EVP_PKEY* pkey, const uint8_t* in, size_t in_len, Bytes& out) noexcept {
739 [ - + ]: 22 : if VUNLIKELY (!size_fits_int(in_len)) {
740 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
741 : : }
742 : :
743 : 22 : EvpPkeyCtxPtr ctx{EVP_PKEY_CTX_new(pkey, nullptr)};
744 : :
745 [ - + ]: 22 : if VUNLIKELY (!ctx) {
746 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
747 : : }
748 : :
749 [ - + ]: 22 : if VUNLIKELY (EVP_PKEY_decrypt_init(ctx.get()) <= 0) {
750 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
751 : : }
752 : :
753 [ - + ]: 22 : if VUNLIKELY (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_OAEP_PADDING) <= 0) {
754 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
755 : : }
756 : :
757 [ - + ]: 22 : if VUNLIKELY (EVP_PKEY_CTX_set_rsa_oaep_md(ctx.get(), EVP_sha256()) <= 0) {
758 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
759 : : }
760 : :
761 [ - + ]: 22 : if VUNLIKELY (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx.get(), EVP_sha256()) <= 0) {
762 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
763 : : }
764 : :
765 : 22 : size_t plain_len = 0;
766 : :
767 [ - + ]: 22 : if VUNLIKELY (EVP_PKEY_decrypt(ctx.get(), nullptr, &plain_len, in, in_len) <= 0) {
768 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
769 : : }
770 : :
771 : 22 : out = Bytes::create(plain_len);
772 : :
773 [ - + ]: 22 : if VUNLIKELY (out.data() == nullptr) {
774 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
775 : : }
776 : :
777 [ + + ]: 22 : if VUNLIKELY (EVP_PKEY_decrypt(ctx.get(), out.data(), &plain_len, in, in_len) <= 0) {
778 : 5 : OPENSSL_cleanse(out.data(), out.size());
779 : 5 : out = Bytes{};
780 : 5 : return false;
781 : : }
782 : :
783 [ - + ]: 17 : if VUNLIKELY (!out.resize(plain_len)) {
784 : : OPENSSL_cleanse(out.data(), out.size()); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
785 : : out = Bytes{}; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
786 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
787 : : }
788 : :
789 : 17 : return true;
790 : 22 : }
791 : :
792 : 7 : static bool sha256_digest_parts(const uint8_t* first, size_t first_len, const uint8_t* second, size_t second_len,
793 : : uint8_t* digest, unsigned int& digest_len) noexcept {
794 : 7 : EVP_MD_CTX* ctx = EVP_MD_CTX_new();
795 : :
796 [ - + ]: 7 : if VUNLIKELY (!ctx) {
797 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
798 : : }
799 : :
800 : 7 : const bool success = EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr) == 1 &&
801 [ + - + - : 14 : EVP_DigestUpdate(ctx, first, first_len) == 1 && EVP_DigestUpdate(ctx, second, second_len) == 1 &&
+ - ]
802 [ + - ]: 7 : EVP_DigestFinal_ex(ctx, digest, &digest_len) == 1;
803 : :
804 : 7 : EVP_MD_CTX_free(ctx);
805 : :
806 : 7 : return success;
807 : : }
808 : :
809 : 4 : static bool rsa_pss_sign(EVP_PKEY* pkey, const uint8_t* first, size_t first_len, const uint8_t* second,
810 : : size_t second_len, Bytes& sig_out) noexcept {
811 : : uint8_t digest[EVP_MAX_MD_SIZE];
812 : 4 : DigestScrub digest_scrub{digest, sizeof(digest)};
813 : 4 : unsigned int digest_len = 0;
814 : :
815 [ - + ]: 4 : if VUNLIKELY (!sha256_digest_parts(first, first_len, second, second_len, digest, digest_len)) {
816 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
817 : : }
818 : :
819 : 4 : EvpPkeyCtxPtr ctx{EVP_PKEY_CTX_new(pkey, nullptr)};
820 : :
821 [ - + ]: 4 : if VUNLIKELY (!ctx) {
822 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
823 : : }
824 : :
825 [ - + ]: 4 : if VUNLIKELY (EVP_PKEY_sign_init(ctx.get()) <= 0) {
826 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
827 : : }
828 : :
829 [ - + ]: 4 : if VUNLIKELY (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_PSS_PADDING) <= 0) {
830 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
831 : : }
832 : :
833 [ - + ]: 4 : if VUNLIKELY (EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx.get(), kRsaPssSaltLenDigest) <= 0) {
834 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
835 : : }
836 : :
837 [ - + ]: 4 : if VUNLIKELY (EVP_PKEY_CTX_set_signature_md(ctx.get(), EVP_sha256()) <= 0) {
838 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
839 : : }
840 : :
841 : 4 : size_t sig_len = 0;
842 : :
843 [ - + ]: 4 : if VUNLIKELY (EVP_PKEY_sign(ctx.get(), nullptr, &sig_len, digest, digest_len) <= 0) {
844 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
845 : : }
846 : :
847 : 4 : sig_out = Bytes::create(sig_len);
848 : :
849 [ - + ]: 4 : if VUNLIKELY (sig_out.data() == nullptr) {
850 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
851 : : }
852 : :
853 [ - + ]: 4 : if VUNLIKELY (EVP_PKEY_sign(ctx.get(), sig_out.data(), &sig_len, digest, digest_len) <= 0) {
854 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
855 : : }
856 : :
857 [ - + ]: 4 : if VUNLIKELY (!sig_out.resize(sig_len)) {
858 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
859 : : }
860 : :
861 : 4 : return true;
862 : 4 : }
863 : :
864 : 3 : static bool rsa_pss_verify(EVP_PKEY* pkey, const uint8_t* first, size_t first_len, const uint8_t* second,
865 : : size_t second_len, const uint8_t* sig, size_t sig_len) noexcept {
866 [ - + ]: 3 : if VUNLIKELY (!size_fits_int(sig_len)) {
867 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
868 : : }
869 : :
870 : : uint8_t digest[EVP_MAX_MD_SIZE];
871 : 3 : DigestScrub digest_scrub{digest, sizeof(digest)};
872 : 3 : unsigned int digest_len = 0;
873 : :
874 [ - + ]: 3 : if VUNLIKELY (!sha256_digest_parts(first, first_len, second, second_len, digest, digest_len)) {
875 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
876 : : }
877 : :
878 : 3 : EvpPkeyCtxPtr ctx{EVP_PKEY_CTX_new(pkey, nullptr)};
879 : :
880 [ - + ]: 3 : if VUNLIKELY (!ctx) {
881 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
882 : : }
883 : :
884 [ - + ]: 3 : if VUNLIKELY (EVP_PKEY_verify_init(ctx.get()) <= 0) {
885 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
886 : : }
887 : :
888 [ - + ]: 3 : if VUNLIKELY (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_PSS_PADDING) <= 0) {
889 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
890 : : }
891 : :
892 [ - + ]: 3 : if VUNLIKELY (EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx.get(), kRsaPssSaltLenDigest) <= 0) {
893 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
894 : : }
895 : :
896 [ - + ]: 3 : if VUNLIKELY (EVP_PKEY_CTX_set_signature_md(ctx.get(), EVP_sha256()) <= 0) {
897 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
898 : : }
899 : :
900 : 3 : return EVP_PKEY_verify(ctx.get(), sig, sig_len, digest, digest_len) == 1;
901 : 3 : }
902 : :
903 : 80 : static bool validate_rsa_key(EVP_PKEY* pkey) noexcept {
904 [ + + ]: 80 : if VUNLIKELY (pkey == nullptr) {
905 : 8 : return false;
906 : : }
907 : :
908 [ + + ]: 72 : if VUNLIKELY (EVP_PKEY_id(pkey) != EVP_PKEY_RSA) {
909 : 4 : VLOG_W("Security: key is not RSA (id=", EVP_PKEY_id(pkey), "); only RSA is supported");
910 : 2 : return false;
911 : : }
912 : :
913 : 70 : const int bits = EVP_PKEY_bits(pkey);
914 : :
915 [ + + ]: 70 : if VUNLIKELY (bits < kRsaMinBits) {
916 : 2 : VLOG_W("Security: RSA key has only ", bits, " bits; require >= ", kRsaMinBits);
917 : 1 : return false;
918 : : }
919 : :
920 : 69 : return true;
921 : : }
922 : :
923 : 43 : static EvpPkeyPtr load_pubkey_from_pem(const std::string& pem) noexcept {
924 [ - + ]: 43 : if VUNLIKELY (!size_fits_int(pem.size())) {
925 : : return nullptr; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
926 : : }
927 : :
928 : 43 : BioPtr bio{BIO_new_mem_buf(pem.data(), static_cast<int>(pem.size()))};
929 : :
930 [ - + ]: 43 : if VUNLIKELY (!bio) {
931 : : return nullptr; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
932 : : }
933 : :
934 : 43 : EvpPkeyPtr pkey{PEM_read_bio_PUBKEY(bio.get(), nullptr, nullptr, nullptr)};
935 : :
936 [ + + ]: 43 : if VUNLIKELY (!validate_rsa_key(pkey.get())) {
937 : 8 : return nullptr;
938 : : }
939 : :
940 : 35 : return pkey;
941 : 43 : }
942 : :
943 : 37 : static EvpPkeyPtr load_privkey_from_pem(const std::string& pem) noexcept {
944 [ - + ]: 37 : if VUNLIKELY (!size_fits_int(pem.size())) {
945 : : return nullptr; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
946 : : }
947 : :
948 : 37 : BioPtr bio{BIO_new_mem_buf(pem.data(), static_cast<int>(pem.size()))};
949 : :
950 [ - + ]: 37 : if VUNLIKELY (!bio) {
951 : : return nullptr; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
952 : : }
953 : :
954 : 37 : EvpPkeyPtr pkey{PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, nullptr)};
955 : :
956 [ + + ]: 37 : if VUNLIKELY (!validate_rsa_key(pkey.get())) {
957 : 3 : return nullptr;
958 : : }
959 : :
960 : 34 : return pkey;
961 : 37 : }
962 : :
963 : 2200 : static bool derive_symmetric_slot_key(const std::string& key, const std::string& passphrase, const Bytes& salt,
964 : : uint32_t iterations, Bytes& out) {
965 : 2200 : out = Bytes{};
966 : :
967 [ + + ]: 2200 : if (!passphrase.empty()) {
968 [ + + - + : 12 : if VUNLIKELY (salt.size() < kPbkdf2MinSaltSize || salt.data() == nullptr) {
+ + ]
969 [ + - + - ]: 2 : VLOG_W("Security: rejected passphrase: salt must be >= ", kPbkdf2MinSaltSize, " bytes");
970 : 1 : return false;
971 : : }
972 : :
973 [ + + ]: 11 : if VUNLIKELY (iterations == 0U) {
974 [ + - + - ]: 2 : VLOG_W("Security: rejected passphrase: iterations must be > 0");
975 : 1 : return false;
976 : : }
977 : :
978 : 10 : out = derive_aes_key_pbkdf2(passphrase, salt.data(), salt.size(), iterations);
979 : :
980 [ + + ]: 10 : if VUNLIKELY (out.size() != kAesKeySize) {
981 [ + - + - ]: 2 : VLOG_W("Security: PBKDF2 derivation failed");
982 : 1 : return false;
983 : : }
984 : :
985 : 9 : return true;
986 : : }
987 : :
988 [ + + ]: 2188 : if (!key.empty()) {
989 : 2098 : out = derive_aes_key_sha256(key);
990 : :
991 [ - + ]: 2098 : if VUNLIKELY (out.size() != kAesKeySize) {
992 : : VLOG_W("Security: SHA-256 derivation failed"); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
993 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
994 : : }
995 : :
996 : 2098 : return true;
997 : : }
998 : :
999 : 90 : return false;
1000 : : }
1001 : :
1002 : 2200 : static bool install_symmetric_key(SymmetricKeySlot& slot, const std::string& key, const std::string& passphrase,
1003 : : const Bytes& salt, uint32_t iterations) {
1004 : 2200 : Bytes derived;
1005 : :
1006 [ + - + + ]: 2200 : if VUNLIKELY (!derive_symmetric_slot_key(key, passphrase, salt, iterations, derived)) {
1007 : 93 : return false;
1008 : : }
1009 : :
1010 [ - + ]: 2107 : if (!slot.key.empty()) {
1011 : : OPENSSL_cleanse(slot.key.data(), slot.key.size()); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1012 : : }
1013 : :
1014 : 2107 : slot.key = std::move(derived);
1015 : 2107 : slot.peers.clear();
1016 : :
1017 : 2107 : return true;
1018 : 2200 : }
1019 : :
1020 : 14 : static bool install_built_in_default_slot(SymmetricKeySlot& slot) {
1021 : 14 : std::array<uint8_t, kAadDomainSize + 2U> seed{};
1022 : 14 : std::memcpy(seed.data(), kAadDomain, kAadDomainSize);
1023 : 14 : seed[kAadDomainSize] = kEnvelopeVersion;
1024 : 14 : seed[kAadDomainSize + 1U] = kEnvelopeModeSymmetric;
1025 : :
1026 : 14 : Bytes derived = derive_aes_key_sha256(seed.data(), seed.size());
1027 [ + - ]: 14 : OPENSSL_cleanse(seed.data(), seed.size());
1028 : :
1029 [ + - - + : 14 : if VUNLIKELY (derived.size() != kAesKeySize || derived.data() == nullptr) {
- + ]
1030 : : VLOG_W("Security: default security slot derivation failed"); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1031 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1032 : : }
1033 : :
1034 [ - + ]: 14 : if (!slot.key.empty()) {
1035 : : OPENSSL_cleanse(slot.key.data(), slot.key.size()); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1036 : : }
1037 : :
1038 : 14 : slot.key = std::move(derived);
1039 : 14 : slot.peers.clear();
1040 : :
1041 : 14 : return true;
1042 : 14 : }
1043 : :
1044 : 2214 : static bool has_explicit_security_field(const Security::Config& cfg) noexcept {
1045 [ + + + + : 2329 : return !cfg.key.empty() || !cfg.passphrase.empty() || !cfg.public_key_pem.empty() || !cfg.private_key_pem.empty() ||
+ + ]
1046 [ + - + - : 78 : !cfg.advanced.signing_key_pem.empty() || !cfg.advanced.verify_key_pem.empty() ||
+ + ]
1047 [ + + + + ]: 2368 : static_cast<bool>(cfg.encrypt_callback) || static_cast<bool>(cfg.decrypt_callback);
1048 : : }
1049 : :
1050 : 2695 : static bool next_nonce(uint64_t& send_seq, uint64_t& sender_id, std::array<uint8_t, kAesNonceSize>& nonce_base,
1051 : : bool& nonce_ready, uint64_t& seq, uint8_t* nonce) noexcept {
1052 [ - + ]: 2695 : if VUNLIKELY (nonce == nullptr) {
1053 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1054 : : }
1055 : :
1056 [ + + ]: 2695 : if VUNLIKELY (!nonce_ready) {
1057 : 2108 : std::array<uint8_t, sizeof(uint64_t) + kAesNonceSize> seed{};
1058 : :
1059 [ - + ]: 2108 : if VUNLIKELY (RAND_bytes(seed.data(), static_cast<int>(seed.size())) != 1) {
1060 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1061 : : }
1062 : :
1063 : 2108 : sender_id = read_u64_le(seed.data());
1064 : :
1065 [ - + ]: 2108 : if VUNLIKELY (sender_id == 0U) {
1066 : : sender_id = 1U; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1067 : : }
1068 : :
1069 : 2108 : std::memcpy(nonce_base.data(), seed.data() + sizeof(uint64_t), kAesNonceSize);
1070 : 2108 : nonce_ready = true;
1071 : : }
1072 : :
1073 [ - + ]: 2695 : if VUNLIKELY (send_seq == std::numeric_limits<uint64_t>::max()) {
1074 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1075 : : }
1076 : :
1077 : 2695 : seq = ++send_seq;
1078 : 2695 : std::memcpy(nonce, nonce_base.data(), kAesNonceSize);
1079 : :
1080 [ + + ]: 24255 : for (size_t i = 0; i < sizeof(seq); ++i) {
1081 : 21560 : nonce[4U + i] ^= static_cast<uint8_t>((seq >> (i * 8U)) & 0xFFU);
1082 : : }
1083 : :
1084 : 2695 : return true;
1085 : : }
1086 : :
1087 : 2216 : static void cleanse_symmetric_key(SymmetricKeySlot& slot) noexcept {
1088 [ + + + - : 2216 : if (!slot.key.empty() && slot.key.data() != nullptr) {
+ + ]
1089 : 2121 : OPENSSL_cleanse(slot.key.data(), slot.key.size());
1090 : : }
1091 : :
1092 : 2216 : slot.key.clear();
1093 : 2216 : slot.peers.clear();
1094 : 2216 : }
1095 : :
1096 : 13296 : static void cleanse_string(std::string& value) noexcept {
1097 [ + + ]: 13296 : if (!value.empty()) {
1098 : 2191 : OPENSSL_cleanse(value.data(), value.size());
1099 : 2191 : value.clear();
1100 : : }
1101 : 13296 : }
1102 : :
1103 : 2216 : static void cleanse_bytes(Bytes& value) noexcept {
1104 [ + + + - : 2216 : if (!value.empty() && value.data() != nullptr) {
+ + ]
1105 : 12 : OPENSSL_cleanse(value.data(), value.size());
1106 : 12 : value.clear();
1107 : : }
1108 : 2216 : }
1109 : :
1110 : 2216 : static void cleanse_config(Security::Config& config) noexcept {
1111 : 2216 : cleanse_string(config.key);
1112 : 2216 : cleanse_string(config.passphrase);
1113 : 2216 : cleanse_bytes(config.pbkdf2_salt);
1114 : 2216 : cleanse_string(config.public_key_pem);
1115 : 2216 : cleanse_string(config.private_key_pem);
1116 : 2216 : cleanse_string(config.advanced.signing_key_pem);
1117 : 2216 : cleanse_string(config.advanced.verify_key_pem);
1118 : :
1119 : 2216 : config.encrypt_callback = nullptr;
1120 : 2216 : config.decrypt_callback = nullptr;
1121 : 2216 : }
1122 : :
1123 : : #endif // VLINK_ENABLE_SECURITY
1124 : :
1125 : : //////////////////////////////////////////
1126 : :
1127 : : // Security::Impl
1128 : : struct Security::Impl final { // NOLINT(clang-analyzer-optin.performance.Padding)
1129 : : mutable std::mutex mtx;
1130 : : Security::Config config;
1131 : : bool aad_context_valid{true};
1132 : :
1133 : : #ifdef VLINK_ENABLE_SECURITY
1134 : : SymmetricKeySlot symmetric_key;
1135 : : EvpCipherCtxPtr sym_encrypt_ctx;
1136 : : EvpCipherCtxPtr sym_decrypt_ctx;
1137 : : uint64_t send_seq{0};
1138 : : uint64_t sender_id{0};
1139 : : std::array<uint8_t, kAesNonceSize> nonce_base{};
1140 : : bool nonce_ready{false};
1141 : : PeerReplayMap asym_peers;
1142 : : bool asym_peer_limit_reported{false};
1143 : : EvpPkeyPtr public_key;
1144 : : EvpPkeyPtr private_key;
1145 : : EvpPkeyPtr signing_key;
1146 : : EvpPkeyPtr verify_key;
1147 : : #endif
1148 : : };
1149 : :
1150 : : // Security
1151 [ + - ]: 4 : Security::Security() : Security(Config{}) {}
1152 : :
1153 : 2 : Security::Config Security::from_private_key_path(const std::string& private_key_path) {
1154 : 2 : Config config;
1155 [ + - ]: 2 : std::ifstream file(private_key_path, std::ios::binary);
1156 : :
1157 [ + - + + ]: 2 : if VUNLIKELY (!file) {
1158 [ + - + - ]: 2 : VLOG_W("Security: failed to open private key file: ", private_key_path);
1159 : 1 : return config;
1160 : : }
1161 : :
1162 [ + - ]: 1 : config.private_key_pem.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
1163 : :
1164 [ + - - + ]: 1 : if VUNLIKELY (file.bad()) {
1165 : : VLOG_W("Security: failed to read private key file: ", private_key_path); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1166 : : #ifdef VLINK_ENABLE_SECURITY
1167 : : cleanse_string(config.private_key_pem); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1168 : : #else
1169 : : config.private_key_pem.clear();
1170 : : #endif
1171 : : }
1172 : :
1173 : 1 : return config;
1174 : 2 : }
1175 : :
1176 : 2 : Security::Config Security::from_public_key_path(const std::string& public_key_path) {
1177 : 2 : Config config;
1178 [ + - ]: 2 : std::ifstream file(public_key_path, std::ios::binary);
1179 : :
1180 [ + - + + ]: 2 : if VUNLIKELY (!file) {
1181 [ + - + - ]: 2 : VLOG_W("Security: failed to open public key file: ", public_key_path);
1182 : 1 : return config;
1183 : : }
1184 : :
1185 [ + - ]: 1 : config.public_key_pem.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
1186 : :
1187 [ + - - + ]: 1 : if VUNLIKELY (file.bad()) {
1188 : : VLOG_W("Security: failed to read public key file: ", public_key_path); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1189 : : config.public_key_pem.clear(); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1190 : : }
1191 : :
1192 : 1 : return config;
1193 : 2 : }
1194 : :
1195 : 3 : Security::Config Security::from_key_paths(const std::string& public_key_path, const std::string& private_key_path) {
1196 : 3 : Config config;
1197 : :
1198 : : {
1199 [ + - ]: 3 : std::ifstream file(public_key_path, std::ios::binary);
1200 : :
1201 [ + - + + ]: 3 : if VUNLIKELY (!file) {
1202 [ + - + - ]: 2 : VLOG_W("Security: failed to open public key file: ", public_key_path);
1203 : : } else {
1204 [ + - ]: 2 : config.public_key_pem.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
1205 : :
1206 [ + - - + ]: 2 : if VUNLIKELY (file.bad()) {
1207 : : VLOG_W("Security: failed to read public key file: ", public_key_path); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1208 : : config.public_key_pem.clear(); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1209 : : }
1210 : : }
1211 : 3 : }
1212 : :
1213 : : {
1214 [ + - ]: 3 : std::ifstream file(private_key_path, std::ios::binary);
1215 : :
1216 [ + - + + ]: 3 : if VUNLIKELY (!file) {
1217 [ + - + - ]: 2 : VLOG_W("Security: failed to open private key file: ", private_key_path);
1218 : : } else {
1219 [ + - ]: 2 : config.private_key_pem.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
1220 : :
1221 [ + - - + ]: 2 : if VUNLIKELY (file.bad()) {
1222 : : VLOG_W("Security: failed to read private key file: ", private_key_path); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1223 : : #ifdef VLINK_ENABLE_SECURITY
1224 : : cleanse_string(config.private_key_pem); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1225 : : #else
1226 : : config.private_key_pem.clear();
1227 : : #endif
1228 : : }
1229 : : }
1230 : 3 : }
1231 : :
1232 : 3 : return config;
1233 : : } // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1234 : :
1235 [ + - ]: 2118 : Security::Security(const Config& cfg) : Security(Config{cfg}) {}
1236 : :
1237 : 2214 : Security::Security(Config&& cfg) : impl_(std::make_unique<Impl>()) {
1238 : 2214 : impl_->config = std::move(cfg);
1239 : :
1240 : : #ifdef VLINK_ENABLE_SECURITY
1241 : 2214 : const bool had_explicit_security_field = has_explicit_security_field(impl_->config);
1242 : : #endif
1243 : :
1244 [ + + ]: 2214 : if VUNLIKELY (static_cast<bool>(impl_->config.encrypt_callback) !=
1245 : : static_cast<bool>(impl_->config.decrypt_callback)) {
1246 [ + - + - ]: 4 : VLOG_W(
1247 : : "Security: encrypt_callback and decrypt_callback must be installed as a pair; ignoring lone "
1248 : : "callback to avoid asymmetric encrypt/decrypt behaviour.");
1249 : 2 : impl_->config.encrypt_callback = nullptr;
1250 : 2 : impl_->config.decrypt_callback = nullptr;
1251 : : }
1252 : :
1253 : : #ifdef VLINK_ENABLE_SECURITY
1254 : 2214 : impl_->config.advanced.replay_window = normalize_replay_window(impl_->config.advanced.replay_window);
1255 : 2214 : impl_->aad_context_valid = impl_->config.advanced.aad_context.size() <= 0xFFFFU;
1256 : :
1257 [ + + ]: 2214 : if VUNLIKELY (!impl_->aad_context_valid) {
1258 [ + - + - ]: 4 : VLOG_W("Security: rejected aad_context: context exceeds 65535 bytes");
1259 : : }
1260 : :
1261 [ + + ]: 2214 : if (!had_explicit_security_field) {
1262 [ + - ]: 14 : (void)install_built_in_default_slot(impl_->symmetric_key);
1263 : : } else {
1264 [ + - ]: 2200 : (void)install_symmetric_key(impl_->symmetric_key, impl_->config.key, impl_->config.passphrase,
1265 : 2200 : impl_->config.pbkdf2_salt, impl_->config.pbkdf2_iterations);
1266 : : }
1267 : :
1268 [ + + ]: 2214 : if (!impl_->config.public_key_pem.empty()) {
1269 : 38 : auto pkey = load_pubkey_from_pem(impl_->config.public_key_pem);
1270 : :
1271 [ + + ]: 38 : if VUNLIKELY (!pkey) {
1272 [ + - + - ]: 14 : VLOG_W("Security: rejected public_key_pem (parse failed, non-RSA, or <2048 bits)");
1273 : : } else {
1274 : 31 : impl_->public_key = std::move(pkey);
1275 : : }
1276 : 38 : }
1277 : :
1278 [ + + ]: 2214 : if (!impl_->config.private_key_pem.empty()) {
1279 : 32 : auto pkey = load_privkey_from_pem(impl_->config.private_key_pem);
1280 : :
1281 [ + + ]: 32 : if VUNLIKELY (!pkey) {
1282 [ + - + - ]: 4 : VLOG_W("Security: rejected private_key_pem (parse failed, non-RSA, or <2048 bits)");
1283 : : } else {
1284 : 30 : impl_->private_key = std::move(pkey);
1285 : : }
1286 : 32 : }
1287 : :
1288 [ + + ]: 2214 : if (!impl_->config.advanced.signing_key_pem.empty()) {
1289 : 5 : auto pkey = load_privkey_from_pem(impl_->config.advanced.signing_key_pem);
1290 : :
1291 [ + + ]: 5 : if VUNLIKELY (!pkey) {
1292 [ + - + - ]: 2 : VLOG_W("Security: rejected signing_key_pem (parse failed, non-RSA, or <2048 bits)");
1293 : : } else {
1294 : 4 : impl_->signing_key = std::move(pkey);
1295 : : }
1296 : 5 : }
1297 : :
1298 [ + + ]: 2214 : if (!impl_->config.advanced.verify_key_pem.empty()) {
1299 : 5 : auto pkey = load_pubkey_from_pem(impl_->config.advanced.verify_key_pem);
1300 : :
1301 [ + + ]: 5 : if VUNLIKELY (!pkey) {
1302 [ + - + - ]: 2 : VLOG_W("Security: rejected verify_key_pem (parse failed, non-RSA, or <2048 bits)");
1303 : : } else {
1304 : 4 : impl_->verify_key = std::move(pkey);
1305 : : }
1306 : 5 : }
1307 : : #else
1308 : :
1309 : : if (!impl_->config.key.empty() || !impl_->config.passphrase.empty() || !impl_->config.public_key_pem.empty() ||
1310 : : !impl_->config.private_key_pem.empty() || !impl_->config.advanced.signing_key_pem.empty() ||
1311 : : !impl_->config.advanced.verify_key_pem.empty()) {
1312 : : VLOG_W(
1313 : : "Security: ignoring built-in algorithm fields (VLINK_ENABLE_SECURITY off); "
1314 : : "only Config::encrypt_callback / decrypt_callback will function.");
1315 : : }
1316 : : #endif
1317 : 2214 : }
1318 : :
1319 : 4430 : Security::~Security() {
1320 [ - + ]: 2215 : if VUNLIKELY (!impl_) {
1321 : : return; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1322 : : }
1323 : :
1324 : : #ifdef VLINK_ENABLE_SECURITY
1325 : 2215 : std::lock_guard lock(impl_->mtx);
1326 : 2215 : cleanse_symmetric_key(impl_->symmetric_key);
1327 : 2215 : cleanse_config(impl_->config);
1328 : : #endif
1329 [ + - ]: 2215 : }
1330 : :
1331 : 1 : Security::Security(Security&& other) noexcept : impl_(std::move(other.impl_)) {
1332 : 1 : other.impl_ = std::make_unique<Impl>();
1333 : 1 : }
1334 : :
1335 : 2 : Security& Security::operator=(Security&& other) noexcept {
1336 [ + + ]: 2 : if VUNLIKELY (this == &other) {
1337 : 1 : return *this;
1338 : : }
1339 : :
1340 [ + - ]: 1 : if VLIKELY (impl_) {
1341 : : #ifdef VLINK_ENABLE_SECURITY
1342 : 1 : std::lock_guard lock(impl_->mtx);
1343 : 1 : cleanse_symmetric_key(impl_->symmetric_key);
1344 : 1 : cleanse_config(impl_->config);
1345 : : #endif
1346 : 1 : }
1347 : :
1348 : 1 : impl_ = std::move(other.impl_);
1349 : 1 : other.impl_ = std::make_unique<Impl>();
1350 : :
1351 : 1 : return *this;
1352 : : }
1353 : :
1354 : 55 : bool Security::is_configured() const noexcept {
1355 : 55 : std::lock_guard lock(impl_->mtx);
1356 : :
1357 : : #ifdef VLINK_ENABLE_SECURITY
1358 : :
1359 [ + + ]: 55 : if (impl_->aad_context_valid) {
1360 [ + + + - : 53 : if (impl_->symmetric_key.key.size() >= kAesKeySize && impl_->symmetric_key.key.data() != nullptr) {
+ + ]
1361 : 12 : return true;
1362 : : }
1363 : :
1364 [ + + + + : 41 : if (impl_->public_key || impl_->private_key) {
+ + ]
1365 : 18 : return true;
1366 : : }
1367 : : }
1368 : : #endif
1369 : :
1370 [ + + + - : 25 : if (impl_->config.encrypt_callback && impl_->config.decrypt_callback) {
+ + ]
1371 : 20 : return true;
1372 : : }
1373 : :
1374 : 5 : return false;
1375 : 55 : }
1376 : :
1377 : 40 : bool Security::can_encrypt() const noexcept {
1378 : 40 : std::lock_guard lock(impl_->mtx);
1379 : :
1380 [ + + + - : 40 : if (impl_->config.encrypt_callback && impl_->config.decrypt_callback) {
+ + ]
1381 : 18 : return true;
1382 : : }
1383 : :
1384 : : #ifdef VLINK_ENABLE_SECURITY
1385 : :
1386 [ + + ]: 22 : if VUNLIKELY (!impl_->aad_context_valid) {
1387 : 1 : return false;
1388 : : }
1389 : :
1390 [ + + + - : 21 : if (impl_->symmetric_key.key.size() >= kAesKeySize && impl_->symmetric_key.key.data() != nullptr) {
+ + ]
1391 : 7 : return true;
1392 : : }
1393 : :
1394 [ + + ]: 14 : if (impl_->public_key) {
1395 : 10 : return true;
1396 : : }
1397 : : #endif
1398 : :
1399 : 4 : return false;
1400 : 40 : }
1401 : :
1402 : 39 : bool Security::can_decrypt() const noexcept {
1403 : 39 : std::lock_guard lock(impl_->mtx);
1404 : :
1405 [ + + + - : 39 : if (impl_->config.encrypt_callback && impl_->config.decrypt_callback) {
+ + ]
1406 : 16 : return true;
1407 : : }
1408 : :
1409 : : #ifdef VLINK_ENABLE_SECURITY
1410 : :
1411 [ + + ]: 23 : if VUNLIKELY (!impl_->aad_context_valid) {
1412 : 1 : return false;
1413 : : }
1414 : :
1415 [ + + + - : 22 : if (impl_->symmetric_key.key.size() >= kAesKeySize && impl_->symmetric_key.key.data() != nullptr) {
+ + ]
1416 : 8 : return true;
1417 : : }
1418 : :
1419 [ + + ]: 14 : if (impl_->private_key) {
1420 : 10 : return true;
1421 : : }
1422 : : #endif
1423 : :
1424 : 4 : return false;
1425 : 39 : }
1426 : :
1427 : 2729 : bool Security::encrypt(const Bytes& in, Bytes& out) {
1428 [ + - ]: 2729 : std::lock_guard lock(impl_->mtx);
1429 : :
1430 : 2729 : out = Bytes{};
1431 : :
1432 [ + + ]: 2729 : if VUNLIKELY (in.empty()) {
1433 : 1 : return false;
1434 : : }
1435 : :
1436 [ + + ]: 2728 : if (impl_->config.encrypt_callback) {
1437 [ + - + + ]: 19 : if VUNLIKELY (!impl_->config.encrypt_callback(in, out)) {
1438 : 5 : out = Bytes{};
1439 : 5 : return false;
1440 : : }
1441 : :
1442 : 14 : return true;
1443 : : }
1444 : :
1445 : : #ifdef VLINK_ENABLE_SECURITY
1446 : :
1447 [ + + ]: 2709 : if VUNLIKELY (!impl_->aad_context_valid) {
1448 [ + - + - ]: 2 : VLOG_W("Security::encrypt aad_context exceeds 65535 bytes");
1449 : 1 : return false;
1450 : : }
1451 : :
1452 [ + + ]: 2708 : if VUNLIKELY (in.size() > static_cast<size_t>(std::numeric_limits<int>::max())) {
1453 [ + - + - ]: 2 : VLOG_W("Security::encrypt input exceeds INT_MAX bytes");
1454 : 1 : return false;
1455 : : }
1456 : :
1457 [ + + ]: 2707 : if (impl_->public_key) {
1458 : 27 : uint8_t session_key[kAesKeySize] = {};
1459 : 27 : DigestScrub session_scrub{session_key, sizeof session_key};
1460 : 27 : uint8_t nonce[kAesNonceSize] = {};
1461 : 27 : uint64_t seq = 0U;
1462 : :
1463 [ + - - + ]: 27 : if VUNLIKELY (RAND_bytes(session_key, sizeof session_key) != 1) {
1464 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1465 : : }
1466 : :
1467 [ - + ]: 27 : if VUNLIKELY (!next_nonce(impl_->send_seq, impl_->sender_id, impl_->nonce_base, impl_->nonce_ready, seq, nonce)) {
1468 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1469 : : }
1470 : :
1471 : 27 : Bytes wrapped;
1472 : :
1473 [ - + ]: 27 : if VUNLIKELY (!rsa_oaep_encrypt(impl_->public_key.get(), session_key, sizeof session_key, wrapped)) {
1474 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1475 : : }
1476 : :
1477 [ - + ]: 27 : if VUNLIKELY (wrapped.size() > 0xFFFFU) {
1478 : : VLOG_W("Security::encrypt RSA-wrapped key exceeds 65535 bytes"); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1479 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1480 : : }
1481 : :
1482 : 27 : Bytes header;
1483 : :
1484 [ - + ]: 27 : if VUNLIKELY (!build_envelope_header(kEnvelopeModeAsymmetric, impl_->sender_id, seq, nonce, header)) {
1485 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1486 : : }
1487 : :
1488 : 27 : Bytes extra = Bytes::create(kRsaWrapLenFieldSize + wrapped.size());
1489 : :
1490 [ - + ]: 27 : if VUNLIKELY (extra.data() == nullptr) {
1491 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1492 : : }
1493 : :
1494 : 27 : write_u16_le(extra.data(), static_cast<uint16_t>(wrapped.size()));
1495 : 27 : std::memcpy(extra.data() + kRsaWrapLenFieldSize, wrapped.data(), wrapped.size());
1496 : :
1497 : 27 : Bytes aad = build_aad(impl_->config.advanced.aad_context, header.data(), header.size(), extra.data(), extra.size());
1498 : :
1499 [ - + ]: 27 : if VUNLIKELY (aad.empty()) {
1500 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1501 : : }
1502 : :
1503 : 27 : const size_t body_size = in.size() + kAesTagSize;
1504 : 27 : Bytes body = Bytes::create(body_size);
1505 : :
1506 [ - + ]: 27 : if VUNLIKELY (body.data() == nullptr) {
1507 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1508 : : }
1509 : :
1510 : 27 : uint8_t* body_cipher = body.data();
1511 : 27 : uint8_t* body_tag = body_cipher + in.size();
1512 : :
1513 : : const bool gcm_ok =
1514 : 27 : aes_gcm_encrypt(session_key, nonce, in.data(), in.size(), aad.data(), aad.size(), body_cipher, body_tag);
1515 : :
1516 [ - + ]: 27 : if VUNLIKELY (!gcm_ok) {
1517 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1518 : : }
1519 : :
1520 : 27 : Bytes signature;
1521 : :
1522 [ + + ]: 27 : if (impl_->signing_key) {
1523 [ - + ]: 4 : if VUNLIKELY (!rsa_pss_sign(impl_->signing_key.get(), aad.data(), aad.size(), body.data(), body_size,
1524 : : signature)) {
1525 : : VLOG_W("Security::encrypt RSA-PSS sign failed"); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1526 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1527 : : }
1528 : :
1529 [ - + ]: 4 : if VUNLIKELY (signature.size() > 0xFFFFU) {
1530 : : VLOG_W("Security::encrypt signature exceeds 65535 bytes"); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1531 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1532 : : }
1533 : : }
1534 : :
1535 : 27 : const size_t total = header.size() + kAsymHeaderFieldsSize + wrapped.size() + signature.size() + body_size;
1536 : 27 : out = Bytes::create(total);
1537 : :
1538 [ - + ]: 27 : if VUNLIKELY (out.data() == nullptr) {
1539 : : out = Bytes{}; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1540 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1541 : : }
1542 : :
1543 : 27 : uint8_t* dst = out.data();
1544 : 27 : const auto wrap_len_le = static_cast<uint16_t>(wrapped.size());
1545 : 27 : const auto sig_len_le = static_cast<uint16_t>(signature.size());
1546 : :
1547 : 27 : std::memcpy(dst, header.data(), header.size());
1548 : 27 : dst += header.size();
1549 : 27 : write_u16_le(dst, wrap_len_le);
1550 : 27 : write_u16_le(dst + kRsaWrapLenFieldSize, sig_len_le);
1551 : :
1552 : 27 : std::memcpy(dst + kAsymHeaderFieldsSize, wrapped.data(), wrapped.size());
1553 : :
1554 [ + + ]: 27 : if (!signature.empty()) {
1555 : 4 : std::memcpy(dst + kAsymHeaderFieldsSize + wrapped.size(), signature.data(), signature.size());
1556 : : }
1557 : :
1558 : 27 : std::memcpy(dst + kAsymHeaderFieldsSize + wrapped.size() + signature.size(), body.data(), body_size);
1559 : :
1560 : 27 : return true;
1561 : 27 : }
1562 : :
1563 : 2680 : auto* key_slot = &impl_->symmetric_key;
1564 : :
1565 [ + + - + : 2680 : if VUNLIKELY (key_slot->key.size() < kAesKeySize || key_slot->key.data() == nullptr) {
+ + ]
1566 [ + - + - ]: 24 : VLOG_W("Security::encrypt no symmetric key installed; construct with a usable Config");
1567 : 12 : return false;
1568 : : }
1569 : :
1570 : 2668 : uint8_t nonce[kAesNonceSize] = {};
1571 : 2668 : uint64_t seq = 0U;
1572 : :
1573 [ - + ]: 2668 : if VUNLIKELY (!next_nonce(impl_->send_seq, impl_->sender_id, impl_->nonce_base, impl_->nonce_ready, seq, nonce)) {
1574 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1575 : : }
1576 : :
1577 : 2668 : const size_t total = kEnvelopeFixedHeaderSize + in.size() + kAesTagSize;
1578 : 2668 : out = Bytes::create(total);
1579 : :
1580 [ - + ]: 2668 : if VUNLIKELY (out.data() == nullptr) {
1581 : : out = Bytes{}; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1582 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1583 : : }
1584 : :
1585 [ - + ]: 2668 : if VUNLIKELY (!write_envelope_header(kEnvelopeModeSymmetric, impl_->sender_id, seq, nonce, out.data(), out.size())) {
1586 : : out = Bytes{}; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1587 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1588 : : }
1589 : :
1590 : 2668 : uint8_t* cipher_dst = out.data() + kEnvelopeFixedHeaderSize;
1591 : 2668 : uint8_t* tag_dst = cipher_dst + in.size();
1592 : 2668 : const AadParts aad{&impl_->config.advanced.aad_context, out.data(), kEnvelopeFixedHeaderSize};
1593 : :
1594 : 2668 : EVP_CIPHER_CTX* enc_ctx = ensure_gcm_ctx(impl_->sym_encrypt_ctx, key_slot->key.data(), true);
1595 : :
1596 [ - + ]: 2668 : if VUNLIKELY (enc_ctx == nullptr) {
1597 : : out = Bytes{}; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1598 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1599 : : }
1600 : :
1601 [ - + ]: 2668 : if VUNLIKELY (!aes_gcm_encrypt_parts(enc_ctx, nonce, in.data(), in.size(), aad, cipher_dst, tag_dst)) {
1602 : 0 : impl_->sym_encrypt_ctx.reset();
1603 : : out = Bytes{}; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1604 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1605 : : }
1606 : :
1607 : 2668 : return true;
1608 : : #else
1609 : : (void)in;
1610 : : (void)out;
1611 : :
1612 : : VLOG_W("Security: Function [encrypt] is not supported (VLINK_ENABLE_SECURITY not enabled).");
1613 : :
1614 : : return false;
1615 : : #endif
1616 : 2729 : }
1617 : :
1618 : 2659 : bool Security::decrypt(const Bytes& in, Bytes& out) {
1619 [ + - ]: 2659 : std::lock_guard lock(impl_->mtx);
1620 : :
1621 : 2659 : out = Bytes{};
1622 : :
1623 [ + + ]: 2659 : if VUNLIKELY (in.empty()) {
1624 : 1 : return false;
1625 : : }
1626 : :
1627 [ + + ]: 2658 : if (impl_->config.decrypt_callback) {
1628 [ + - + + ]: 15 : if VUNLIKELY (!impl_->config.decrypt_callback(in, out)) {
1629 : 7 : out = Bytes{};
1630 : 7 : return false;
1631 : : }
1632 : :
1633 : 8 : return true;
1634 : : }
1635 : :
1636 : : #ifdef VLINK_ENABLE_SECURITY
1637 : :
1638 [ + + ]: 2643 : if VUNLIKELY (!impl_->aad_context_valid) {
1639 [ + - + - ]: 2 : VLOG_W("Security::decrypt aad_context exceeds 65535 bytes");
1640 : 1 : return false;
1641 : : }
1642 : :
1643 [ + + ]: 2642 : if VUNLIKELY (in.size() > static_cast<size_t>(std::numeric_limits<int>::max())) {
1644 [ + - + - ]: 2 : VLOG_W("Security::decrypt input exceeds INT_MAX bytes");
1645 : 1 : return false;
1646 : : }
1647 : :
1648 : 2641 : EnvelopeHeader header;
1649 : :
1650 [ + + + + : 2641 : if VUNLIKELY (!parse_envelope_header(in, header) || header.flags != 0U || header.sender_id == 0U) {
+ + + + +
+ ]
1651 : 10 : return false;
1652 : : }
1653 : :
1654 : 2631 : const uint8_t* src = in.data();
1655 : :
1656 [ + + ]: 2631 : if (header.mode == kEnvelopeModeAsymmetric) {
1657 [ + + ]: 30 : if (!impl_->private_key) {
1658 [ + - + - ]: 2 : VLOG_W("Security::decrypt no private key installed for asymmetric envelope");
1659 : 1 : return false;
1660 : : }
1661 : :
1662 [ + + ]: 29 : if VUNLIKELY (in.size() <= header.size + kAsymHeaderFieldsSize + kAesTagSize) {
1663 : 1 : return false;
1664 : : }
1665 : :
1666 : 28 : const uint8_t* fields_ptr = src + header.size;
1667 : 28 : const auto wrap_len = read_u16_le(fields_ptr);
1668 : 28 : const auto sig_len = read_u16_le(fields_ptr + kRsaWrapLenFieldSize);
1669 : 28 : const size_t meta_size =
1670 : 28 : header.size + kAsymHeaderFieldsSize + static_cast<size_t>(wrap_len) + static_cast<size_t>(sig_len);
1671 : :
1672 [ + + + + : 28 : if VUNLIKELY (wrap_len == 0U || in.size() <= meta_size + kAesTagSize) {
+ + ]
1673 : 3 : return false;
1674 : : }
1675 : :
1676 : 25 : const uint8_t* wrapped_ptr = fields_ptr + kAsymHeaderFieldsSize;
1677 : 25 : const uint8_t* sig_ptr = wrapped_ptr + wrap_len;
1678 : 25 : const uint8_t* cipher = sig_ptr + sig_len;
1679 : 25 : const size_t cipher_len = in.size() - meta_size - kAesTagSize;
1680 : 25 : const uint8_t* tag = cipher + cipher_len;
1681 : :
1682 : 25 : Bytes extra = Bytes::create(kRsaWrapLenFieldSize + wrap_len);
1683 : :
1684 [ - + ]: 25 : if VUNLIKELY (extra.data() == nullptr) {
1685 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1686 : : }
1687 : :
1688 : 25 : write_u16_le(extra.data(), wrap_len);
1689 : 25 : std::memcpy(extra.data() + kRsaWrapLenFieldSize, wrapped_ptr, wrap_len);
1690 : :
1691 : 25 : Bytes aad = build_aad(impl_->config.advanced.aad_context, src, header.size, extra.data(), extra.size());
1692 : :
1693 [ - + ]: 25 : if VUNLIKELY (aad.empty()) {
1694 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1695 : : }
1696 : :
1697 [ + + ]: 25 : if (impl_->verify_key) {
1698 [ + + ]: 4 : if VUNLIKELY (sig_len == 0U) {
1699 [ + - + - ]: 2 : VLOG_W("Security::decrypt verify_key set but message is unsigned");
1700 : 1 : return false;
1701 : : }
1702 : :
1703 [ + + ]: 3 : if VUNLIKELY (!rsa_pss_verify(impl_->verify_key.get(), aad.data(), aad.size(), cipher, cipher_len + kAesTagSize,
1704 : : sig_ptr, sig_len)) {
1705 [ + - + - ]: 4 : VLOG_W("Security::decrypt RSA-PSS signature verification failed");
1706 : 2 : return false;
1707 : : }
1708 : : }
1709 : :
1710 : 22 : Bytes session_key;
1711 : :
1712 [ + + ]: 22 : if VUNLIKELY (!rsa_oaep_decrypt(impl_->private_key.get(), wrapped_ptr, wrap_len, session_key)) {
1713 : 5 : return false;
1714 : : }
1715 : :
1716 [ + - - + : 17 : if VUNLIKELY (session_key.size() != kAesKeySize || session_key.data() == nullptr) {
- + ]
1717 : : if (!session_key.empty() && session_key.data() != nullptr) { // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1718 : : OPENSSL_cleanse(session_key.data(), session_key.size()); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1719 : : }
1720 : :
1721 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1722 : : }
1723 : :
1724 : 17 : Bytes plain = Bytes::create(cipher_len);
1725 : :
1726 [ - + ]: 17 : if VUNLIKELY (plain.data() == nullptr) {
1727 : : OPENSSL_cleanse(session_key.data(), session_key.size()); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1728 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1729 : : }
1730 : :
1731 : 17 : const bool ok = aes_gcm_decrypt(session_key.data(), header.nonce, cipher, cipher_len, aad.data(), aad.size(), tag,
1732 : : plain.data());
1733 [ + - ]: 17 : OPENSSL_cleanse(session_key.data(), session_key.size());
1734 : :
1735 [ + + ]: 17 : if VUNLIKELY (!ok) {
1736 [ + - ]: 8 : OPENSSL_cleanse(plain.data(), plain.size());
1737 : 8 : return false;
1738 : : }
1739 : :
1740 [ + - + + ]: 9 : if VUNLIKELY (!accept_peer_replay(impl_->asym_peers, impl_->asym_peer_limit_reported, header.sender_id, header.seq,
1741 : : impl_->config.advanced.replay_window)) {
1742 [ + - ]: 1 : OPENSSL_cleanse(plain.data(), plain.size());
1743 : 1 : return false;
1744 : : }
1745 : :
1746 : 8 : out = std::move(plain);
1747 : :
1748 : 8 : return true;
1749 : 25 : }
1750 : :
1751 [ + + ]: 2601 : if (header.mode == kEnvelopeModeSymmetric) {
1752 : 2600 : auto* key_slot = &impl_->symmetric_key;
1753 : :
1754 [ + + - + : 2600 : if VUNLIKELY (key_slot->key.size() < kAesKeySize || key_slot->key.data() == nullptr) {
+ + ]
1755 [ + - + - ]: 4 : VLOG_W("Security::decrypt no symmetric key installed");
1756 : 2 : return false;
1757 : : }
1758 : :
1759 [ + + ]: 2598 : if VUNLIKELY (in.size() <= header.size + kAesTagSize) {
1760 : 1 : return false;
1761 : : }
1762 : :
1763 : 2597 : const size_t cipher_len = in.size() - header.size - kAesTagSize;
1764 : 2597 : const uint8_t* cipher = src + header.size;
1765 : 2597 : const uint8_t* tag = cipher + cipher_len;
1766 : 2597 : const AadParts aad{&impl_->config.advanced.aad_context, src, header.size};
1767 : :
1768 : 2597 : Bytes plain = Bytes::create(cipher_len);
1769 : :
1770 [ - + ]: 2597 : if VUNLIKELY (plain.data() == nullptr) {
1771 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1772 : : }
1773 : :
1774 : 2597 : EVP_CIPHER_CTX* dec_ctx = ensure_gcm_ctx(impl_->sym_decrypt_ctx, key_slot->key.data(), false);
1775 : :
1776 [ - + ]: 2597 : if VUNLIKELY (dec_ctx == nullptr) {
1777 : : OPENSSL_cleanse(plain.data(), plain.size()); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1778 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1779 : : }
1780 : :
1781 [ + + ]: 2597 : if VUNLIKELY (!aes_gcm_decrypt_parts(dec_ctx, header.nonce, cipher, cipher_len, aad, tag, plain.data())) {
1782 : 11 : impl_->sym_decrypt_ctx.reset();
1783 [ + - ]: 11 : OPENSSL_cleanse(plain.data(), plain.size());
1784 : 11 : return false;
1785 : : }
1786 : :
1787 [ + - + + ]: 2586 : if VUNLIKELY (!accept_peer_replay(key_slot->peers, key_slot->peer_limit_reported, header.sender_id, header.seq,
1788 : : impl_->config.advanced.replay_window)) {
1789 [ + - ]: 3 : OPENSSL_cleanse(plain.data(), plain.size());
1790 : 3 : return false;
1791 : : }
1792 : :
1793 : 2583 : out = std::move(plain);
1794 : :
1795 : 2583 : return true;
1796 : 2597 : }
1797 : :
1798 : 1 : return false;
1799 : : #else
1800 : : (void)in;
1801 : : (void)out;
1802 : :
1803 : : VLOG_W("Security: Function [decrypt] is not supported (VLINK_ENABLE_SECURITY not enabled).");
1804 : :
1805 : : return false;
1806 : : #endif
1807 : 2659 : }
1808 : :
1809 : : } // namespace vlink
|