14#include <openssl/bio.h>
15#include <openssl/err.h>
16#include <openssl/evp.h>
17#include <openssl/pem.h>
18#include <openssl/x509.h>
19#include <openssl/x509v3.h>
34inline auto portable_timegm(
struct tm* tm_time) -> time_t {
36 return _mkgmtime(tm_time);
38 return timegm(tm_time);
45auto get_openssl_error() -> std::string {
46 unsigned long err = ERR_get_error();
48 return "Unknown error";
51 ERR_error_string_n(err, buf,
sizeof(buf));
52 return std::string(buf);
59 std::ifstream file(std::string(path), std::ios::binary);
60 if (!file.is_open()) {
61 return kcenon::common::make_error<std::string>(
62 1,
"Failed to open file: " + std::string(path),
"certificate");
65 std::ostringstream ss;
74 void operator()(BIO* bio)
const {
75 if (bio) BIO_free(bio);
78using bio_ptr = std::unique_ptr<BIO, bio_deleter>;
83auto asn1_time_to_time_point(
const ASN1_TIME* asn1_time)
84 -> std::chrono::system_clock::time_point {
86 return std::chrono::system_clock::time_point{};
89 struct tm tm_time = {};
93 if (ASN1_TIME_to_tm(asn1_time, &tm_time) != 1) {
94 return std::chrono::system_clock::time_point{};
98 time_t
time = portable_timegm(&tm_time);
100 return std::chrono::system_clock::from_time_t(time);
132 other.x509_ =
nullptr;
136 if (
this != &
other) {
150 if (
this != &
other) {
155 other.x509_ =
nullptr;
160 [[nodiscard]]
auto x509() const noexcept -> X509* {
return x509_; }
161 [[nodiscard]]
auto is_loaded() const noexcept ->
bool {
return x509_ !=
nullptr; }
179 EVP_PKEY_free(
pkey_);
189 other.pkey_ =
nullptr;
193 if (
this != &
other) {
195 EVP_PKEY_free(
pkey_);
198 other.pkey_ =
nullptr;
203 [[nodiscard]]
auto pkey() const noexcept -> EVP_PKEY* {
return pkey_; }
204 [[nodiscard]]
auto is_loaded() const noexcept ->
bool {
return pkey_ !=
nullptr; }
222 if (
this != &
other) {
223 impl_ = std::make_unique<certificate_impl>(*
other.impl_);
234 auto content_result = read_file(path);
235 if (content_result.is_err()) {
236 return kcenon::common::make_error<certificate>(
237 content_result.error().code,
238 content_result.error().message,
242 return load_from_pem_string(content_result.value());
247 bio_ptr bio(BIO_new_mem_buf(pem_data.data(),
static_cast<int>(pem_data.size())));
249 return kcenon::common::make_error<certificate>(
250 2,
"Failed to create BIO: " + get_openssl_error(),
"certificate");
253 X509* x509 = PEM_read_bio_X509(bio.get(),
nullptr,
nullptr,
nullptr);
255 return kcenon::common::make_error<certificate>(
256 3,
"Failed to parse PEM certificate: " + get_openssl_error(),
"certificate");
260 cert.
impl_ = std::make_unique<certificate_impl>(x509);
266 const unsigned char* data = der_data.data();
267 X509* x509 = d2i_X509(
nullptr, &data,
static_cast<long>(der_data.size()));
269 return kcenon::common::make_error<certificate>(
270 4,
"Failed to parse DER certificate: " + get_openssl_error(),
"certificate");
274 cert.
impl_ = std::make_unique<certificate_impl>(x509);
279 if (!
impl_->is_loaded()) {
283 X509_NAME*
name = X509_get_subject_name(
impl_->x509());
288 bio_ptr bio(BIO_new(BIO_s_mem()));
289 X509_NAME_print_ex(bio.get(),
name, 0, XN_FLAG_RFC2253);
291 char* data =
nullptr;
292 long len = BIO_get_mem_data(bio.get(), &data);
293 return std::string(data,
static_cast<size_t>(len));
297 if (!
impl_->is_loaded()) {
301 X509_NAME*
name = X509_get_subject_name(
impl_->x509());
306 int idx = X509_NAME_get_index_by_NID(
name, NID_commonName, -1);
311 X509_NAME_ENTRY* entry = X509_NAME_get_entry(
name, idx);
316 ASN1_STRING* data = X509_NAME_ENTRY_get_data(entry);
321 unsigned char* utf8 =
nullptr;
322 int len = ASN1_STRING_to_UTF8(&utf8, data);
327 std::string result(
reinterpret_cast<char*
>(utf8),
static_cast<size_t>(len));
333 if (!
impl_->is_loaded()) {
337 X509_NAME*
name = X509_get_subject_name(
impl_->x509());
342 int idx = X509_NAME_get_index_by_NID(
name, NID_organizationName, -1);
347 X509_NAME_ENTRY* entry = X509_NAME_get_entry(
name, idx);
352 ASN1_STRING* data = X509_NAME_ENTRY_get_data(entry);
357 unsigned char* utf8 =
nullptr;
358 int len = ASN1_STRING_to_UTF8(&utf8, data);
363 std::string result(
reinterpret_cast<char*
>(utf8),
static_cast<size_t>(len));
369 if (!
impl_->is_loaded()) {
373 X509_NAME*
name = X509_get_issuer_name(
impl_->x509());
378 bio_ptr bio(BIO_new(BIO_s_mem()));
379 X509_NAME_print_ex(bio.get(),
name, 0, XN_FLAG_RFC2253);
381 char* data =
nullptr;
382 long len = BIO_get_mem_data(bio.get(), &data);
383 return std::string(data,
static_cast<size_t>(len));
387 if (!
impl_->is_loaded()) {
391 const ASN1_INTEGER* serial = X509_get_serialNumber(
impl_->x509());
396 BIGNUM* bn = ASN1_INTEGER_to_BN(serial,
nullptr);
401 char* hex = BN_bn2hex(bn);
408 std::string result(hex);
414 if (!
impl_->is_loaded()) {
418 unsigned char hash[EVP_MAX_MD_SIZE];
419 unsigned int hash_len = 0;
421 if (X509_digest(
impl_->x509(), EVP_sha256(),
hash, &hash_len) != 1) {
426 result.reserve(hash_len * 2);
428 static const char hex_chars[] =
"0123456789ABCDEF";
429 for (
unsigned int i = 0; i < hash_len; ++i) {
430 result += hex_chars[(
hash[i] >> 4) & 0x0F];
431 result += hex_chars[
hash[i] & 0x0F];
438 if (!
impl_->is_loaded()) {
439 return std::chrono::system_clock::time_point{};
442 const ASN1_TIME* time = X509_get0_notBefore(
impl_->x509());
443 return asn1_time_to_time_point(time);
447 if (!
impl_->is_loaded()) {
448 return std::chrono::system_clock::time_point{};
451 const ASN1_TIME* time = X509_get0_notAfter(
impl_->x509());
452 return asn1_time_to_time_point(time);
456 if (!
impl_->is_loaded()) {
460 auto now = std::chrono::system_clock::now();
465 if (!
impl_->is_loaded()) {
469 auto now = std::chrono::system_clock::now();
474 if (!
impl_->is_loaded()) {
478 bio_ptr bio(BIO_new(BIO_s_mem()));
479 if (PEM_write_bio_X509(bio.get(),
impl_->x509()) != 1) {
483 char* data =
nullptr;
484 long len = BIO_get_mem_data(bio.get(), &data);
485 return std::string(data,
static_cast<size_t>(len));
489 if (!
impl_->is_loaded()) {
493 unsigned char* data =
nullptr;
494 int len = i2d_X509(
impl_->x509(), &data);
499 std::vector<std::uint8_t> result(data, data + len);
530 auto content_result = read_file(path);
531 if (content_result.is_err()) {
532 return kcenon::common::make_error<private_key>(
533 content_result.error().code,
534 content_result.error().message,
538 return load_from_pem_string(content_result.value(), password);
543 bio_ptr bio(BIO_new_mem_buf(pem_data.data(),
static_cast<int>(pem_data.size())));
545 return kcenon::common::make_error<private_key>(
546 2,
"Failed to create BIO: " + get_openssl_error(),
"private_key");
549 EVP_PKEY* pkey =
nullptr;
550 if (password.empty()) {
551 pkey = PEM_read_bio_PrivateKey(bio.get(),
nullptr,
nullptr,
nullptr);
553 pkey = PEM_read_bio_PrivateKey(
554 bio.get(),
nullptr,
nullptr,
555 const_cast<char*
>(std::string(password).c_str()));
559 return kcenon::common::make_error<private_key>(
560 3,
"Failed to parse PEM private key: " + get_openssl_error(),
"private_key");
564 key.
impl_ = std::make_unique<private_key_impl>(pkey);
569 if (!
impl_->is_loaded()) {
573 int type = EVP_PKEY_base_id(
impl_->pkey());
582 case EVP_PKEY_ED25519:
592 if (!
impl_->is_loaded()) {
596 return EVP_PKEY_bits(
impl_->pkey());
616 certs_.push_back(std::move(cert));
640 auto content_result = read_file(path);
641 if (content_result.is_err()) {
642 return kcenon::common::make_error<certificate_chain>(
643 content_result.error().code,
644 content_result.error().message,
645 "certificate_chain");
648 const std::string& content = content_result.value();
652 bio_ptr bio(BIO_new_mem_buf(content.data(),
static_cast<int>(content.size())));
654 return kcenon::common::make_error<certificate_chain>(
655 2,
"Failed to create BIO: " + get_openssl_error(),
"certificate_chain");
659 X509* x509 = PEM_read_bio_X509(bio.get(),
nullptr,
nullptr,
nullptr);
662 unsigned long err = ERR_peek_last_error();
663 if (ERR_GET_REASON(err) == PEM_R_NO_START_LINE) {
671 cert.
impl_ = std::make_unique<certificate_impl>(x509);
672 chain.
add(std::move(cert));
676 return kcenon::common::make_error<certificate_chain>(
677 3,
"No certificates found in PEM file",
"certificate_chain");
X.509 Certificate and Private Key handling for DICOM digital signatures.
Represents a certificate chain for validation.
auto size() const noexcept -> size_t
Get number of certificates in chain.
static auto load_from_pem(std::string_view path) -> kcenon::common::Result< certificate_chain >
Load certificate chain from PEM file.
std::vector< certificate > certs_
auto empty() const noexcept -> bool
Check if chain is empty.
auto end_entity() const -> const certificate *
Get the end-entity (leaf) certificate.
void add(certificate cert)
Add a certificate to the chain.
auto certificates() const -> const std::vector< certificate > &
Get all certificates in the chain.
auto is_loaded() const noexcept -> bool
auto operator=(const certificate_impl &other) -> certificate_impl &
certificate_impl(certificate_impl &&other) noexcept
certificate_impl(X509 *cert)
auto operator=(certificate_impl &&other) noexcept -> certificate_impl &
auto x509() const noexcept -> X509 *
certificate_impl(const certificate_impl &other)
auto issuer_name() const -> std::string
Get the issuer distinguished name.
auto serial_number() const -> std::string
Get the certificate serial number.
std::unique_ptr< certificate_impl > impl_
auto to_der() const -> std::vector< std::uint8_t >
Export certificate as DER bytes.
auto is_expired() const -> bool
Check if the certificate has expired.
static auto load_from_der(std::span< const std::uint8_t > der_data) -> kcenon::common::Result< certificate >
Load certificate from DER-encoded bytes.
static auto load_from_pem(std::string_view path) -> kcenon::common::Result< certificate >
Load certificate from PEM file.
auto impl() const noexcept -> const certificate_impl *
Get internal implementation (for internal use only)
auto subject_name() const -> std::string
Get the subject distinguished name.
auto is_valid() const -> bool
Check if the certificate is currently valid.
auto not_before() const -> std::chrono::system_clock::time_point
Get the not-before date.
auto to_pem() const -> std::string
Export certificate as PEM string.
~certificate()
Destructor.
auto subject_common_name() const -> std::string
Get the common name from the subject.
auto operator=(const certificate &other) -> certificate &
Copy assignment.
auto thumbprint() const -> std::string
Get the certificate thumbprint (SHA-256)
auto is_loaded() const noexcept -> bool
Check if certificate is loaded.
static auto load_from_pem_string(std::string_view pem_data) -> kcenon::common::Result< certificate >
Load certificate from PEM string.
auto not_after() const -> std::chrono::system_clock::time_point
Get the not-after date.
certificate()
Default constructor - creates an empty certificate.
auto subject_organization() const -> std::string
Get the organization from the subject.
private_key_impl(private_key_impl &&other) noexcept
private_key_impl(EVP_PKEY *key)
private_key_impl(const private_key_impl &)=delete
auto operator=(private_key_impl &&other) noexcept -> private_key_impl &
auto pkey() const noexcept -> EVP_PKEY *
auto is_loaded() const noexcept -> bool
auto operator=(const private_key_impl &) -> private_key_impl &=delete
auto impl() const noexcept -> const private_key_impl *
Get internal implementation (for internal use only)
auto operator=(const private_key &) -> private_key &=delete
Copy assignment (deleted)
private_key()
Default constructor - creates an empty key.
auto algorithm_name() const -> std::string
Get the algorithm name.
auto is_loaded() const noexcept -> bool
Check if key is loaded.
~private_key()
Destructor - securely erases key material.
static auto load_from_pem_string(std::string_view pem_data, std::string_view password="") -> kcenon::common::Result< private_key >
Load private key from PEM string.
auto key_size() const -> int
Get the key size in bits.
static auto load_from_pem(std::string_view path, std::string_view password="") -> kcenon::common::Result< private_key >
Load private key from PEM file.
std::unique_ptr< private_key_impl > impl_
@ hash
Hash the value for research linkage.