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
|