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