LCOV - code coverage report
Current view: top level - src/extension - security.cc (source / functions) Hit Total Coverage
Test: vlink Lines: 732 732 100.0 %
Date: 2026-06-27 19:56:04 Functions: 88 88 100.0 %
Branches: 540 830 65.1 %

           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

Generated by: LCOV version 1.14