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>
23#include <system_error>
35inline auto portable_timegm(
struct tm* tm_time) -> time_t {
37 return _mkgmtime(tm_time);
39 return timegm(tm_time);
46auto get_openssl_error() -> std::string {
47 unsigned long err = ERR_get_error();
49 return "Unknown error";
52 ERR_error_string_n(err, buf,
sizeof(buf));
53 return std::string(buf);
75 const std::string path_str(path);
82 if (!std::filesystem::is_regular_file(path_str, ec) || ec) {
83 return kcenon::common::make_error<std::string>(
84 1,
"Failed to open file (not a regular file): " + path_str,
88 const auto size = std::filesystem::file_size(path_str, ec);
90 return kcenon::common::make_error<std::string>(
91 1,
"Failed to stat file: " + path_str,
"certificate");
94 std::ifstream file(path_str, std::ios::binary);
95 if (!file.is_open()) {
96 return kcenon::common::make_error<std::string>(
97 1,
"Failed to open file: " + path_str,
"certificate");
101 content.resize(
static_cast<std::size_t
>(size));
103 file.read(content.data(),
static_cast<std::streamsize
>(size));
105 return kcenon::common::make_error<std::string>(
106 1,
"Failed to read file: " + path_str,
"certificate");
116 void operator()(BIO* bio)
const {
117 if (bio) BIO_free(bio);
120using bio_ptr = std::unique_ptr<BIO, bio_deleter>;
125auto asn1_time_to_time_point(
const ASN1_TIME* asn1_time)
126 -> std::chrono::system_clock::time_point {
128 return std::chrono::system_clock::time_point{};
131 struct tm tm_time = {};
135 if (ASN1_TIME_to_tm(asn1_time, &tm_time) != 1) {
136 return std::chrono::system_clock::time_point{};
140 time_t
time = portable_timegm(&tm_time);
142 return std::chrono::system_clock::from_time_t(time);
174 other.x509_ =
nullptr;
178 if (
this != &
other) {
192 if (
this != &
other) {
197 other.x509_ =
nullptr;
202 [[nodiscard]]
auto x509() const noexcept -> X509* {
return x509_; }
203 [[nodiscard]]
auto is_loaded() const noexcept ->
bool {
return x509_ !=
nullptr; }
221 EVP_PKEY_free(
pkey_);
231 other.pkey_ =
nullptr;
235 if (
this != &
other) {
237 EVP_PKEY_free(
pkey_);
240 other.pkey_ =
nullptr;
245 [[nodiscard]]
auto pkey() const noexcept -> EVP_PKEY* {
return pkey_; }
246 [[nodiscard]]
auto is_loaded() const noexcept ->
bool {
return pkey_ !=
nullptr; }
264 if (
this != &
other) {
265 impl_ = std::make_unique<certificate_impl>(*
other.impl_);
276 auto content_result = read_file(path);
277 if (content_result.is_err()) {
278 return kcenon::common::make_error<certificate>(
279 content_result.error().code,
280 content_result.error().message,
284 return load_from_pem_string(content_result.value());
289 bio_ptr bio(BIO_new_mem_buf(pem_data.data(),
static_cast<int>(pem_data.size())));
291 return kcenon::common::make_error<certificate>(
292 2,
"Failed to create BIO: " + get_openssl_error(),
"certificate");
295 X509* x509 = PEM_read_bio_X509(bio.get(),
nullptr,
nullptr,
nullptr);
297 return kcenon::common::make_error<certificate>(
298 3,
"Failed to parse PEM certificate: " + get_openssl_error(),
"certificate");
302 cert.
impl_ = std::make_unique<certificate_impl>(x509);
308 const unsigned char* data = der_data.data();
309 X509* x509 = d2i_X509(
nullptr, &data,
static_cast<long>(der_data.size()));
311 return kcenon::common::make_error<certificate>(
312 4,
"Failed to parse DER certificate: " + get_openssl_error(),
"certificate");
316 cert.
impl_ = std::make_unique<certificate_impl>(x509);
321 if (!
impl_->is_loaded()) {
325 X509_NAME*
name = X509_get_subject_name(
impl_->x509());
330 bio_ptr bio(BIO_new(BIO_s_mem()));
331 X509_NAME_print_ex(bio.get(),
name, 0, XN_FLAG_RFC2253);
333 char* data =
nullptr;
334 long len = BIO_get_mem_data(bio.get(), &data);
335 return std::string(data,
static_cast<size_t>(len));
339 if (!
impl_->is_loaded()) {
343 X509_NAME*
name = X509_get_subject_name(
impl_->x509());
348 int idx = X509_NAME_get_index_by_NID(
name, NID_commonName, -1);
353 X509_NAME_ENTRY* entry = X509_NAME_get_entry(
name, idx);
358 ASN1_STRING* data = X509_NAME_ENTRY_get_data(entry);
363 unsigned char* utf8 =
nullptr;
364 int len = ASN1_STRING_to_UTF8(&utf8, data);
369 std::string result(
reinterpret_cast<char*
>(utf8),
static_cast<size_t>(len));
375 if (!
impl_->is_loaded()) {
379 X509_NAME*
name = X509_get_subject_name(
impl_->x509());
384 int idx = X509_NAME_get_index_by_NID(
name, NID_organizationName, -1);
389 X509_NAME_ENTRY* entry = X509_NAME_get_entry(
name, idx);
394 ASN1_STRING* data = X509_NAME_ENTRY_get_data(entry);
399 unsigned char* utf8 =
nullptr;
400 int len = ASN1_STRING_to_UTF8(&utf8, data);
405 std::string result(
reinterpret_cast<char*
>(utf8),
static_cast<size_t>(len));
411 if (!
impl_->is_loaded()) {
415 X509_NAME*
name = X509_get_issuer_name(
impl_->x509());
420 bio_ptr bio(BIO_new(BIO_s_mem()));
421 X509_NAME_print_ex(bio.get(),
name, 0, XN_FLAG_RFC2253);
423 char* data =
nullptr;
424 long len = BIO_get_mem_data(bio.get(), &data);
425 return std::string(data,
static_cast<size_t>(len));
429 if (!
impl_->is_loaded()) {
433 const ASN1_INTEGER* serial = X509_get_serialNumber(
impl_->x509());
438 BIGNUM* bn = ASN1_INTEGER_to_BN(serial,
nullptr);
443 char* hex = BN_bn2hex(bn);
450 std::string result(hex);
456 if (!
impl_->is_loaded()) {
460 unsigned char hash[EVP_MAX_MD_SIZE];
461 unsigned int hash_len = 0;
463 if (X509_digest(
impl_->x509(), EVP_sha256(),
hash, &hash_len) != 1) {
468 result.reserve(hash_len * 2);
470 static const char hex_chars[] =
"0123456789ABCDEF";
471 for (
unsigned int i = 0; i < hash_len; ++i) {
472 result += hex_chars[(
hash[i] >> 4) & 0x0F];
473 result += hex_chars[
hash[i] & 0x0F];
480 if (!
impl_->is_loaded()) {
481 return std::chrono::system_clock::time_point{};
484 const ASN1_TIME* time = X509_get0_notBefore(
impl_->x509());
485 return asn1_time_to_time_point(time);
489 if (!
impl_->is_loaded()) {
490 return std::chrono::system_clock::time_point{};
493 const ASN1_TIME* time = X509_get0_notAfter(
impl_->x509());
494 return asn1_time_to_time_point(time);
498 if (!
impl_->is_loaded()) {
502 auto now = std::chrono::system_clock::now();
507 if (!
impl_->is_loaded()) {
511 auto now = std::chrono::system_clock::now();
516 if (!
impl_->is_loaded()) {
520 bio_ptr bio(BIO_new(BIO_s_mem()));
521 if (PEM_write_bio_X509(bio.get(),
impl_->x509()) != 1) {
525 char* data =
nullptr;
526 long len = BIO_get_mem_data(bio.get(), &data);
527 return std::string(data,
static_cast<size_t>(len));
531 if (!
impl_->is_loaded()) {
535 unsigned char* data =
nullptr;
536 int len = i2d_X509(
impl_->x509(), &data);
541 std::vector<std::uint8_t> result(data, data + len);
572 auto content_result = read_file(path);
573 if (content_result.is_err()) {
574 return kcenon::common::make_error<private_key>(
575 content_result.error().code,
576 content_result.error().message,
580 return load_from_pem_string(content_result.value(), password);
585 bio_ptr bio(BIO_new_mem_buf(pem_data.data(),
static_cast<int>(pem_data.size())));
587 return kcenon::common::make_error<private_key>(
588 2,
"Failed to create BIO: " + get_openssl_error(),
"private_key");
591 EVP_PKEY* pkey =
nullptr;
592 if (password.empty()) {
593 pkey = PEM_read_bio_PrivateKey(bio.get(),
nullptr,
nullptr,
nullptr);
595 pkey = PEM_read_bio_PrivateKey(
596 bio.get(),
nullptr,
nullptr,
597 const_cast<char*
>(std::string(password).c_str()));
601 return kcenon::common::make_error<private_key>(
602 3,
"Failed to parse PEM private key: " + get_openssl_error(),
"private_key");
606 key.
impl_ = std::make_unique<private_key_impl>(pkey);
611 if (!
impl_->is_loaded()) {
615 int type = EVP_PKEY_base_id(
impl_->pkey());
624 case EVP_PKEY_ED25519:
634 if (!
impl_->is_loaded()) {
638 return EVP_PKEY_bits(
impl_->pkey());
658 certs_.push_back(std::move(cert));
682 auto content_result = read_file(path);
683 if (content_result.is_err()) {
684 return kcenon::common::make_error<certificate_chain>(
685 content_result.error().code,
686 content_result.error().message,
687 "certificate_chain");
690 const std::string& content = content_result.value();
694 bio_ptr bio(BIO_new_mem_buf(content.data(),
static_cast<int>(content.size())));
696 return kcenon::common::make_error<certificate_chain>(
697 2,
"Failed to create BIO: " + get_openssl_error(),
"certificate_chain");
701 X509* x509 = PEM_read_bio_X509(bio.get(),
nullptr,
nullptr,
nullptr);
704 unsigned long err = ERR_peek_last_error();
705 if (ERR_GET_REASON(err) == PEM_R_NO_START_LINE) {
713 cert.
impl_ = std::make_unique<certificate_impl>(x509);
714 chain.
add(std::move(cert));
718 return kcenon::common::make_error<certificate_chain>(
719 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)
Represents an X.509 certificate.
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
Represents a private key for signing operations.
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.