LCOV - code coverage report
Current view: top level - src/extension - security.cc (source / functions) Hit Total Coverage
Test: vlink Lines: 742 743 99.9 %
Date: 2026-07-26 14:05:51 Functions: 91 91 100.0 %
Branches: 546 836 65.3 %

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

Generated by: LCOV version 1.14