LCOV - code coverage report
Current view: top level - src/net - tls_context.cpp (source / functions) Coverage Total Hit
Test: HPActor Coverage Lines: 16.9 % 148 25
Test Date: 2026-05-20 02:24:49 Functions: 50.0 % 10 5
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: - 0 0

             Branch data     Line data    Source code
       1                 :             : // Copyright 2026 HPActor Contributors
       2                 :             : //
       3                 :             : // Licensed under the Apache License, Version 2.0 (the "License");
       4                 :             : // you may not use this file except in compliance with the License.
       5                 :             : // You may obtain a copy of the License at
       6                 :             : //
       7                 :             : //     http://www.apache.org/licenses/LICENSE-2.0
       8                 :             : //
       9                 :             : // Unless required by applicable law or agreed to in writing, software
      10                 :             : // distributed under the License is distributed on an "AS IS" BASIS,
      11                 :             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12                 :             : // See the License for the specific language governing permissions and
      13                 :             : // limitations under the License.
      14                 :             : 
      15                 :             : #include <hpactor/net/tls_context.hpp>
      16                 :             : 
      17                 :             : #include <hpactor/net/platform.hpp>
      18                 :             : 
      19                 :             : #include <algorithm>
      20                 :             : #include <cstring>
      21                 :             : #include <openssl/err.h>
      22                 :             : #include <openssl/evp.h>
      23                 :             : #include <openssl/pem.h>
      24                 :             : #include <openssl/rsa.h>
      25                 :             : #include <openssl/x509.h>
      26                 :             : 
      27                 :             : namespace hpactor {
      28                 :             : 
      29                 :             : namespace net {
      30                 :             : 
      31                 :             : struct TlsContext::RSAKey {
      32                 :             :     EVP_PKEY* pkey = nullptr;
      33                 :           0 :     ~RSAKey() {
      34                 :           0 :         if (pkey)
      35                 :           0 :             EVP_PKEY_free(pkey);
      36                 :           0 :     }
      37                 :             : };
      38                 :             : 
      39                 :          37 : TlsContext::TlsContext() = default;
      40                 :             : 
      41                 :          37 : TlsContext::~TlsContext() = default;
      42                 :             : 
      43                 :           0 : TlsContext::TlsContext(TlsContext&& other) noexcept
      44                 :           0 :     : endpoint_(other.endpoint_), certificate_(std::move(other.certificate_)),
      45                 :           0 :       public_key_(std::move(other.public_key_)),
      46                 :           0 :       private_key_(std::move(other.private_key_)),
      47                 :           0 :       rsa_key_(std::move(other.rsa_key_)), ca_certs_(std::move(other.ca_certs_)),
      48                 :           0 :       peer_certs_(std::move(other.peer_certs_)) {
      49                 :           0 :     other.endpoint_ = LocalEndpoint;
      50                 :           0 : }
      51                 :             : 
      52                 :           0 : TlsContext& TlsContext::operator=(TlsContext&& other) noexcept {
      53                 :           0 :     if (this != &other) {
      54                 :           0 :         endpoint_ = other.endpoint_;
      55                 :           0 :         certificate_ = std::move(other.certificate_);
      56                 :           0 :         public_key_ = std::move(other.public_key_);
      57                 :           0 :         private_key_ = std::move(other.private_key_);
      58                 :           0 :         rsa_key_ = std::move(other.rsa_key_);
      59                 :           0 :         ca_certs_ = std::move(other.ca_certs_);
      60                 :           0 :         peer_certs_ = std::move(other.peer_certs_);
      61                 :           0 :         other.endpoint_ = LocalEndpoint;
      62                 :             :     }
      63                 :           0 :     return *this;
      64                 :             : }
      65                 :             : 
      66                 :           0 : TlsContext TlsContext::from_filesystem(EndPoint endpoint,
      67                 :             :                                        const std::string& cert_dir) {
      68                 :           0 :     TlsContext ctx;
      69                 :           0 :     ctx.endpoint_ = endpoint;
      70                 :             : 
      71                 :             :     // Sanitize endpoint string for use in filename (replace ':' with '_')
      72                 :           0 :     std::string safe_node_id = endpoint_ops::to_string(endpoint);
      73                 :           0 :     std::replace(safe_node_id.begin(), safe_node_id.end(), ':', '_');
      74                 :             : 
      75                 :             :     // Load own certificate
      76                 :           0 :     std::string cert_path = cert_dir + "/node_" + safe_node_id + ".pem";
      77                 :           0 :     FILE* cert_file = fopen(cert_path.c_str(), "r");
      78                 :           0 :     if (!cert_file) {
      79                 :           0 :         return ctx; // Caller should check node_id().empty() to detect init
      80                 :             :                     // failure
      81                 :             :     }
      82                 :           0 :     X509* cert = PEM_read_X509(cert_file, nullptr, nullptr, nullptr);
      83                 :           0 :     fclose(cert_file);
      84                 :           0 :     if (!cert) {
      85                 :           0 :         return ctx;
      86                 :             :     }
      87                 :             : 
      88                 :             :     // Extract DER from certificate
      89                 :           0 :     unsigned char* cert_der = nullptr;
      90                 :           0 :     int cert_len = i2d_X509(cert, &cert_der);
      91                 :           0 :     if (cert_len > 0 && cert_der) {
      92                 :           0 :         ctx.certificate_.insert(ctx.certificate_.end(), cert_der,
      93                 :           0 :                                 cert_der + cert_len);
      94                 :           0 :         OPENSSL_free(cert_der);
      95                 :             :     }
      96                 :             : 
      97                 :             :     // Extract public key
      98                 :           0 :     EVP_PKEY* pkey = X509_get0_pubkey(cert);
      99                 :           0 :     if (pkey) {
     100                 :           0 :         unsigned char* pkey_der = nullptr;
     101                 :           0 :         int pkey_len = i2d_PUBKEY(pkey, &pkey_der);
     102                 :           0 :         if (pkey_len > 0 && pkey_der) {
     103                 :           0 :             ctx.public_key_.insert(ctx.public_key_.end(), pkey_der,
     104                 :           0 :                                    pkey_der + pkey_len);
     105                 :           0 :             OPENSSL_free(pkey_der);
     106                 :             :         }
     107                 :             :     }
     108                 :             : 
     109                 :             :     // Load private key using EVP API
     110                 :           0 :     std::string key_path = cert_dir + "/node_" + safe_node_id + "_key.pem";
     111                 :           0 :     FILE* key_file = fopen(key_path.c_str(), "r");
     112                 :           0 :     if (key_file) {
     113                 :             :         EVP_PKEY* evp_pkey =
     114                 :           0 :             PEM_read_PrivateKey(key_file, nullptr, nullptr, nullptr);
     115                 :           0 :         fclose(key_file);
     116                 :           0 :         if (evp_pkey) {
     117                 :           0 :             ctx.rsa_key_ = std::make_unique<RSAKey>();
     118                 :           0 :             ctx.rsa_key_->pkey = evp_pkey;
     119                 :             :         }
     120                 :             :     }
     121                 :             : 
     122                 :           0 :     X509_free(cert);
     123                 :           0 :     return ctx;
     124                 :           0 : }
     125                 :             : 
     126                 :          37 : TlsContext TlsContext::from_config(const TlsConfig& config) {
     127                 :          37 :     TlsContext ctx;
     128                 :          37 :     ctx.endpoint_ = config.endpoint;
     129                 :          37 :     ctx.certificate_ = config.own_cert_der;
     130                 :          37 :     ctx.ca_certs_ = config.ca_certs_der;
     131                 :             : 
     132                 :             :     // Parse private key using EVP API
     133                 :          37 :     const unsigned char* key_data = config.own_key_der.data();
     134                 :             :     EVP_PKEY* evp_pkey =
     135                 :          37 :         d2i_PrivateKey(EVP_PKEY_RSA, nullptr, &key_data,
     136                 :          37 :                        static_cast<long>(config.own_key_der.size()));
     137                 :          37 :     if (evp_pkey) {
     138                 :           0 :         ctx.rsa_key_ = std::make_unique<RSAKey>();
     139                 :           0 :         ctx.rsa_key_->pkey = evp_pkey;
     140                 :             :     }
     141                 :             : 
     142                 :             :     // Extract public key from certificate
     143                 :          37 :     const unsigned char* cert_data = config.own_cert_der.data();
     144                 :          37 :     X509* cert = d2i_X509(nullptr, &cert_data,
     145                 :          37 :                           static_cast<long>(config.own_cert_der.size()));
     146                 :          37 :     if (cert) {
     147                 :           0 :         EVP_PKEY* pkey = X509_get0_pubkey(cert);
     148                 :           0 :         if (pkey) {
     149                 :           0 :             unsigned char* pkey_der = nullptr;
     150                 :           0 :             int pkey_len = i2d_PUBKEY(pkey, &pkey_der);
     151                 :           0 :             if (pkey_len > 0 && pkey_der) {
     152                 :           0 :                 ctx.public_key_.insert(ctx.public_key_.end(), pkey_der,
     153                 :           0 :                                        pkey_der + pkey_len);
     154                 :           0 :                 OPENSSL_free(pkey_der);
     155                 :             :             }
     156                 :             :         }
     157                 :           0 :         X509_free(cert);
     158                 :             :     }
     159                 :             : 
     160                 :          37 :     return ctx;
     161                 :             : }
     162                 :             : 
     163                 :             : TlsContext::CertVerifyResult
     164                 :           2 : TlsContext::verify_certificate(const StreamBuffer& cert_der) const {
     165                 :           2 :     const unsigned char* data = cert_der.data();
     166                 :           2 :     X509* cert = d2i_X509(nullptr, &data, static_cast<long>(cert_der.size()));
     167                 :           2 :     if (!cert) {
     168                 :           2 :         return CertVerifyResult::Invalid;
     169                 :             :     }
     170                 :             : 
     171                 :             :     // Check validity period - X509_get0_notBefore/notAfter return const
     172                 :             :     // pointers
     173                 :           0 :     const ASN1_TIME* not_before = X509_get0_notBefore(cert);
     174                 :           0 :     const ASN1_TIME* not_after = X509_get0_notAfter(cert);
     175                 :           0 :     if (!not_before || !not_after) {
     176                 :           0 :         X509_free(cert);
     177                 :           0 :         return CertVerifyResult::Invalid;
     178                 :             :     }
     179                 :             : 
     180                 :             :     // Simple time check (in production use X509_cmp_time)
     181                 :             :     // For now, assume valid if we can parse it
     182                 :             :     (void)not_before;
     183                 :             :     (void)not_after;
     184                 :             : 
     185                 :           0 :     X509_free(cert);
     186                 :           0 :     return CertVerifyResult::Ok;
     187                 :             : }
     188                 :             : 
     189                 :           1 : StreamBuffer TlsContext::sign_data(const StreamBuffer& data) const {
     190                 :           1 :     StreamBuffer signature;
     191                 :           1 :     if (!rsa_key_ || !rsa_key_->pkey) {
     192                 :           1 :         return signature;
     193                 :             :     }
     194                 :             : 
     195                 :             :     // Use EVP API for signing
     196                 :           0 :     EVP_MD_CTX* ctx = EVP_MD_CTX_new();
     197                 :           0 :     if (!ctx) {
     198                 :           0 :         return signature;
     199                 :             :     }
     200                 :             : 
     201                 :           0 :     const EVP_MD* md = EVP_sha256();
     202                 :           0 :     if (EVP_DigestInit_ex(ctx, md, nullptr) != 1) {
     203                 :           0 :         EVP_MD_CTX_free(ctx);
     204                 :           0 :         return signature;
     205                 :             :     }
     206                 :             : 
     207                 :           0 :     if (EVP_DigestSignInit(ctx, nullptr, md, nullptr, rsa_key_->pkey) != 1) {
     208                 :           0 :         EVP_MD_CTX_free(ctx);
     209                 :           0 :         return signature;
     210                 :             :     }
     211                 :             : 
     212                 :           0 :     size_t sig_len = 0;
     213                 :           0 :     if (EVP_DigestSign(ctx, nullptr, &sig_len, data.data(), data.size()) != 1) {
     214                 :           0 :         EVP_MD_CTX_free(ctx);
     215                 :           0 :         return signature;
     216                 :             :     }
     217                 :             : 
     218                 :           0 :     signature.resize(sig_len);
     219                 :           0 :     if (EVP_DigestSign(ctx, signature.data(), &sig_len, data.data(),
     220                 :           0 :                        data.size()) != 1) {
     221                 :           0 :         signature.clear();
     222                 :             :     } else {
     223                 :           0 :         signature.resize(sig_len);
     224                 :             :     }
     225                 :             : 
     226                 :           0 :     EVP_MD_CTX_free(ctx);
     227                 :           0 :     return signature;
     228                 :             : }
     229                 :             : 
     230                 :           0 : bool TlsContext::decrypt_pre_master_secret(const StreamBuffer& encrypted,
     231                 :             :                                            StreamBuffer& pre_master_secret) const {
     232                 :           0 :     if (!rsa_key_ || !rsa_key_->pkey) {
     233                 :           0 :         return false;
     234                 :             :     }
     235                 :             : 
     236                 :             :     // Use EVP API for decryption
     237                 :           0 :     EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(rsa_key_->pkey, nullptr);
     238                 :           0 :     if (!ctx) {
     239                 :           0 :         return false;
     240                 :             :     }
     241                 :             : 
     242                 :           0 :     if (EVP_PKEY_decrypt_init(ctx) != 1) {
     243                 :           0 :         EVP_PKEY_CTX_free(ctx);
     244                 :           0 :         return false;
     245                 :             :     }
     246                 :             : 
     247                 :           0 :     size_t out_len = 0;
     248                 :           0 :     if (EVP_PKEY_decrypt(ctx, nullptr, &out_len, encrypted.data(),
     249                 :           0 :                          encrypted.size()) != 1) {
     250                 :           0 :         EVP_PKEY_CTX_free(ctx);
     251                 :           0 :         return false;
     252                 :             :     }
     253                 :             : 
     254                 :           0 :     pre_master_secret.resize(out_len);
     255                 :           0 :     if (EVP_PKEY_decrypt(ctx, pre_master_secret.data(), &out_len,
     256                 :           0 :                          encrypted.data(), encrypted.size()) != 1) {
     257                 :           0 :         pre_master_secret.clear();
     258                 :           0 :         EVP_PKEY_CTX_free(ctx);
     259                 :           0 :         return false;
     260                 :             :     }
     261                 :             : 
     262                 :           0 :     pre_master_secret.resize(out_len);
     263                 :           0 :     EVP_PKEY_CTX_free(ctx);
     264                 :           0 :     return true;
     265                 :             : }
     266                 :             : 
     267                 :             : } // namespace net
     268                 :             : } // namespace hpactor
        

Generated by: LCOV version 2.0-1