PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
digital_signature.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2021-2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
13
16
17#include <openssl/bio.h>
18#include <openssl/err.h>
19#include <openssl/evp.h>
20#include <openssl/pem.h>
21#include <openssl/rand.h>
22#include <openssl/x509.h>
23
24#include <algorithm>
25#include <chrono>
26#include <iomanip>
27#include <sstream>
28
29namespace kcenon::pacs::security {
30
31// Forward declaration of impl classes from certificate.cpp
32class certificate_impl;
33class private_key_impl;
34
35namespace {
36
43inline auto portable_gmtime_r(const time_t* timer, struct tm* result) -> struct tm* {
44#ifdef _WIN32
45 return (gmtime_s(result, timer) == 0) ? result : nullptr;
46#else
47 return gmtime_r(timer, result);
48#endif
49}
50
57inline auto portable_timegm(struct tm* tm_time) -> time_t {
58#ifdef _WIN32
59 return _mkgmtime(tm_time);
60#else
61 return timegm(tm_time);
62#endif
63}
64
65// DICOM Digital Signature tags
66constexpr core::dicom_tag digital_signature_sequence_tag{0x0400, 0x0561};
67constexpr core::dicom_tag signature_uid_tag{0x0400, 0x0100};
68constexpr core::dicom_tag signature_datetime_tag{0x0400, 0x0105};
69constexpr core::dicom_tag certificate_type_tag{0x0400, 0x0110};
70constexpr core::dicom_tag certificate_of_signer_tag{0x0400, 0x0115};
71constexpr core::dicom_tag signature_tag{0x0400, 0x0120};
72constexpr core::dicom_tag mac_id_number_tag{0x0400, 0x0005};
73constexpr core::dicom_tag mac_calculation_transfer_syntax_tag{0x0400, 0x0010};
74constexpr core::dicom_tag mac_algorithm_tag{0x0400, 0x0015};
75constexpr core::dicom_tag data_elements_signed_tag{0x0400, 0x0020};
76
80auto get_openssl_error() -> std::string {
81 unsigned long err = ERR_get_error();
82 if (err == 0) {
83 return "Unknown error";
84 }
85 char buf[256];
86 ERR_error_string_n(err, buf, sizeof(buf));
87 return std::string(buf);
88}
89
93struct evp_md_ctx_deleter {
94 void operator()(EVP_MD_CTX* ctx) const {
95 if (ctx) EVP_MD_CTX_free(ctx);
96 }
97};
98using evp_md_ctx_ptr = std::unique_ptr<EVP_MD_CTX, evp_md_ctx_deleter>;
99
103auto get_evp_md(signature_algorithm algo) -> const EVP_MD* {
104 switch (algo) {
107 return EVP_sha256();
110 return EVP_sha384();
112 return EVP_sha512();
113 }
114 return EVP_sha256();
115}
116
120auto get_mac_evp_md(mac_algorithm algo) -> const EVP_MD* {
121 switch (algo) {
123 return EVP_sha256();
125 return EVP_sha384();
127 return EVP_sha512();
128 }
129 return EVP_sha256();
130}
131
135auto get_mac_algorithm(signature_algorithm algo) -> mac_algorithm {
136 switch (algo) {
145 }
147}
148
152auto generate_uid() -> std::string {
153 // Use DICOM private root + random component
154 // Format: 1.2.840.XXXXX.NNNN.timestamp.random
155 auto now = std::chrono::system_clock::now();
156 auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
157 now.time_since_epoch()).count();
158
159 unsigned char random_bytes[4];
160 RAND_bytes(random_bytes, sizeof(random_bytes));
161
162 std::ostringstream oss;
163 oss << "1.2.840.10008.5.1.4.1.1.66."
164 << timestamp << "."
165 << static_cast<unsigned int>(random_bytes[0])
166 << static_cast<unsigned int>(random_bytes[1])
167 << static_cast<unsigned int>(random_bytes[2])
168 << static_cast<unsigned int>(random_bytes[3]);
169
170 return oss.str();
171}
172
176auto format_dicom_datetime() -> std::string {
177 auto now = std::chrono::system_clock::now();
178 auto time_t_now = std::chrono::system_clock::to_time_t(now);
179 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
180 now.time_since_epoch()) % 1000;
181
182 std::tm tm_now{};
183 portable_gmtime_r(&time_t_now, &tm_now);
184
185 std::ostringstream oss;
186 oss << std::put_time(&tm_now, "%Y%m%d%H%M%S")
187 << "." << std::setfill('0') << std::setw(3) << ms.count()
188 << "+0000";
189
190 return oss.str();
191}
192
196auto parse_dicom_datetime(std::string_view dt_str)
197 -> std::chrono::system_clock::time_point {
198 if (dt_str.size() < 14) {
199 return std::chrono::system_clock::time_point{};
200 }
201
202 std::tm tm{};
203 std::istringstream ss(std::string(dt_str.substr(0, 14)));
204 ss >> std::get_time(&tm, "%Y%m%d%H%M%S");
205
206 if (ss.fail()) {
207 return std::chrono::system_clock::time_point{};
208 }
209
210 time_t time = portable_timegm(&tm);
211 return std::chrono::system_clock::from_time_t(time);
212}
213
217auto serialize_elements_for_mac(
218 const core::dicom_dataset& dataset,
219 std::span<const core::dicom_tag> tags
220) -> std::vector<std::uint8_t> {
221 std::vector<std::uint8_t> result;
222
223 for (const auto& tag : tags) {
224 const auto* elem = dataset.get(tag);
225 if (!elem) {
226 continue;
227 }
228
229 // Add tag (4 bytes, little endian)
230 uint16_t group = tag.group();
231 uint16_t element = tag.element();
232
233 result.push_back(static_cast<uint8_t>(group & 0xFF));
234 result.push_back(static_cast<uint8_t>((group >> 8) & 0xFF));
235 result.push_back(static_cast<uint8_t>(element & 0xFF));
236 result.push_back(static_cast<uint8_t>((element >> 8) & 0xFF));
237
238 // Add element value
239 const auto& data = elem->raw_data();
240 for (auto byte : data) {
241 result.push_back(byte);
242 }
243 }
244
245 return result;
246}
247
251auto get_signable_tags(const core::dicom_dataset& dataset)
252 -> std::vector<core::dicom_tag> {
253 std::vector<core::dicom_tag> tags;
254
255 for (const auto& [tag, elem] : dataset) {
256 // Skip signature-related tags
257 if (tag.group() == 0x0400) {
258 continue;
259 }
260 tags.push_back(tag);
261 }
262
263 std::sort(tags.begin(), tags.end());
264 return tags;
265}
266
267// External declaration to access OpenSSL handles
268extern "C" {
269 X509* get_x509_from_certificate(const certificate& cert);
270 EVP_PKEY* get_pkey_from_private_key(const private_key& key);
271}
272
273} // anonymous namespace
274
275// Helper to access OpenSSL X509 handle
277 auto* impl = cert.impl();
278 if (!impl) return nullptr;
279 // Access x509 through the impl
280 // This is a workaround - in production, expose a proper internal API
281 return reinterpret_cast<X509*>(const_cast<void*>(
282 static_cast<const void*>(impl)));
283}
284
285// ============================================================================
286// digital_signature implementation
287// ============================================================================
288
290 core::dicom_dataset& dataset,
291 const certificate& cert,
292 const private_key& key,
294) -> kcenon::common::VoidResult {
295 // Get all signable tags
296 auto tags = get_signable_tags(dataset);
297 return sign_tags(dataset, cert, key, tags, algo);
298}
299
301 core::dicom_dataset& dataset,
302 const certificate& cert,
303 const private_key& key,
304 std::span<const core::dicom_tag> tags_to_sign,
306) -> kcenon::common::VoidResult {
307 if (!cert.is_loaded()) {
308 return kcenon::common::make_error<std::monostate>(
309 1, "Certificate not loaded", "digital_signature");
310 }
311
312 if (!key.is_loaded()) {
313 return kcenon::common::make_error<std::monostate>(
314 2, "Private key not loaded", "digital_signature");
315 }
316
317 if (!cert.is_valid()) {
318 return kcenon::common::make_error<std::monostate>(
319 3, "Certificate is not valid (expired or not yet valid)", "digital_signature");
320 }
321
322 // Compute MAC of the data
323 auto mac_algo = get_mac_algorithm(algo);
324 auto mac = compute_mac(dataset, tags_to_sign, mac_algo);
325
326 // Sign the MAC
327 auto signature_result = sign_mac(mac, key, algo);
328 if (signature_result.is_err()) {
329 return kcenon::common::make_error<std::monostate>(
330 signature_result.error().code,
331 signature_result.error().message,
332 "digital_signature");
333 }
334
335 const auto& signature_bytes = signature_result.value();
336
337 // Create Digital Signature Sequence item
338 // For simplicity, we'll add the signature data directly to the dataset
339 // In a full implementation, this would create a proper sequence item
340
341 // Generate signature UID
342 std::string sig_uid = generate_signature_uid();
343
344 // Get certificate DER
345 auto cert_der = cert.to_der();
346
347 // Create signed tags list (as AT type - list of tags)
348 std::vector<std::uint8_t> signed_tags_data;
349 for (const auto& tag : tags_to_sign) {
350 uint16_t group = tag.group();
351 uint16_t element = tag.element();
352 signed_tags_data.push_back(static_cast<uint8_t>(group & 0xFF));
353 signed_tags_data.push_back(static_cast<uint8_t>((group >> 8) & 0xFF));
354 signed_tags_data.push_back(static_cast<uint8_t>(element & 0xFF));
355 signed_tags_data.push_back(static_cast<uint8_t>((element >> 8) & 0xFF));
356 }
357
358 // Add signature elements to dataset
359 // Note: In a full DICOM implementation, these would be in a sequence item
360 dataset.set_string(signature_uid_tag, encoding::vr_type::UI, sig_uid);
361 dataset.set_string(signature_datetime_tag, encoding::vr_type::DT, format_dicom_datetime());
362 dataset.set_string(certificate_type_tag, encoding::vr_type::CS, "X509_1993_SIG");
363
364 // Add certificate data
365 dataset.insert(core::dicom_element(
366 certificate_of_signer_tag,
368 cert_der));
369
370 // Add signature
371 dataset.insert(core::dicom_element(
372 signature_tag,
374 signature_bytes));
375
376 // Add MAC algorithm
377 dataset.set_string(mac_algorithm_tag, encoding::vr_type::CS,
378 std::string(to_dicom_uid(mac_algo)));
379
380 // Add signed tags
381 dataset.insert(core::dicom_element(
382 data_elements_signed_tag,
384 signed_tags_data));
385
386 return kcenon::common::ok();
387}
388
390 const core::dicom_dataset& dataset
392 // Check if signature exists
393 if (!has_signature(dataset)) {
395 }
396
397 // Get signature info
398 auto info = get_signature_info(dataset);
399 if (!info) {
400 return kcenon::common::make_error<signature_status>(
401 1, "Failed to extract signature info", "digital_signature");
402 }
403
404 // Get certificate from dataset
405 const auto* cert_elem = dataset.get(certificate_of_signer_tag);
406 if (!cert_elem) {
407 return kcenon::common::make_error<signature_status>(
408 2, "No certificate in signature", "digital_signature");
409 }
410
411 auto cert_result = certificate::load_from_der(cert_elem->raw_data());
412 if (cert_result.is_err()) {
413 return kcenon::common::make_error<signature_status>(
414 3, "Failed to load certificate: " + cert_result.error().message,
415 "digital_signature");
416 }
417
418 const auto& cert = cert_result.value();
419
420 // Check certificate validity
421 if (cert.is_expired()) {
423 }
424
425 if (!cert.is_valid()) {
427 }
428
429 // Get signed tags
430 const auto* signed_tags_elem = dataset.get(data_elements_signed_tag);
431 std::vector<core::dicom_tag> signed_tags;
432
433 if (signed_tags_elem) {
434 const auto& tag_data = signed_tags_elem->raw_data();
435 for (size_t i = 0; i + 3 < tag_data.size(); i += 4) {
436 uint16_t group = static_cast<uint16_t>(tag_data[i]) |
437 (static_cast<uint16_t>(tag_data[i + 1]) << 8);
438 uint16_t element = static_cast<uint16_t>(tag_data[i + 2]) |
439 (static_cast<uint16_t>(tag_data[i + 3]) << 8);
440 signed_tags.emplace_back(group, element);
441 }
442 } else {
443 // If no signed tags specified, use all non-signature tags
444 signed_tags = get_signable_tags(dataset);
445 }
446
447 // Compute MAC
448 auto mac = compute_mac(dataset, signed_tags, mac_algorithm::sha256);
449
450 // Get signature
451 const auto* sig_elem = dataset.get(signature_tag);
452 if (!sig_elem) {
453 return kcenon::common::make_error<signature_status>(
454 4, "No signature data found", "digital_signature");
455 }
456
457 // Verify signature
458 bool valid = verify_mac_signature(
459 mac,
460 sig_elem->raw_data(),
461 cert,
462 info->algorithm);
463
465}
466
468 const core::dicom_dataset& dataset,
469 const std::vector<certificate>& trusted_certs
471 // First do basic verification
472 auto basic_result = verify(dataset);
473 if (basic_result.is_err()) {
474 return basic_result;
475 }
476
477 auto status = basic_result.value();
479 return status;
480 }
481
482 // Get signer certificate
483 const auto* cert_elem = dataset.get(certificate_of_signer_tag);
484 if (!cert_elem) {
486 }
487
488 auto cert_result = certificate::load_from_der(cert_elem->raw_data());
489 if (cert_result.is_err()) {
491 }
492
493 const auto& signer_cert = cert_result.value();
494 std::string signer_thumbprint = signer_cert.thumbprint();
495
496 // Check if signer is in trusted list
497 for (const auto& trusted : trusted_certs) {
498 if (trusted.thumbprint() == signer_thumbprint) {
500 }
501 }
502
504}
505
507 const core::dicom_dataset& dataset
508) -> std::optional<signature_info> {
509 if (!has_signature(dataset)) {
510 return std::nullopt;
511 }
512
513 signature_info info;
514
515 // Get signature UID
516 info.signature_uid = dataset.get_string(signature_uid_tag);
517
518 // Get timestamp
519 std::string dt_str = dataset.get_string(signature_datetime_tag);
520 if (!dt_str.empty()) {
521 info.timestamp = parse_dicom_datetime(dt_str);
522 }
523
524 // Get certificate and extract signer info
525 const auto* cert_elem = dataset.get(certificate_of_signer_tag);
526 if (cert_elem) {
527 auto cert_result = certificate::load_from_der(cert_elem->raw_data());
528 if (cert_result.is_ok()) {
529 const auto& cert = cert_result.value();
530 info.signer_name = cert.subject_common_name();
531 info.signer_organization = cert.subject_organization();
532 info.certificate_thumbprint = cert.thumbprint();
533 }
534 }
535
536 // Default algorithm (could be extracted from MAC algorithm tag)
537 info.algorithm = signature_algorithm::rsa_sha256;
538
539 // Get signed tags
540 const auto* signed_tags_elem = dataset.get(data_elements_signed_tag);
541 if (signed_tags_elem) {
542 const auto& tag_data = signed_tags_elem->raw_data();
543 for (size_t i = 0; i + 3 < tag_data.size(); i += 4) {
544 uint16_t group = static_cast<uint16_t>(tag_data[i]) |
545 (static_cast<uint16_t>(tag_data[i + 1]) << 8);
546 uint16_t element = static_cast<uint16_t>(tag_data[i + 2]) |
547 (static_cast<uint16_t>(tag_data[i + 3]) << 8);
548 info.signed_tags.push_back(
549 (static_cast<uint32_t>(group) << 16) | element);
550 }
551 }
552
553 return info;
554}
555
557 const core::dicom_dataset& dataset
558) -> std::vector<signature_info> {
559 // For this simplified implementation, we only support one signature
560 std::vector<signature_info> result;
561
562 auto info = get_signature_info(dataset);
563 if (info) {
564 result.push_back(std::move(*info));
565 }
566
567 return result;
568}
569
571 const core::dicom_dataset& dataset
572) -> bool {
573 // Check for signature tag or signature sequence
574 return dataset.contains(signature_tag) ||
575 dataset.contains(digital_signature_sequence_tag);
576}
577
579 core::dicom_dataset& dataset
580) -> bool {
581 bool removed = false;
582
583 // Remove all signature-related tags
584 removed |= dataset.remove(digital_signature_sequence_tag);
585 removed |= dataset.remove(signature_uid_tag);
586 removed |= dataset.remove(signature_datetime_tag);
587 removed |= dataset.remove(certificate_type_tag);
588 removed |= dataset.remove(certificate_of_signer_tag);
589 removed |= dataset.remove(signature_tag);
590 removed |= dataset.remove(mac_id_number_tag);
591 removed |= dataset.remove(mac_calculation_transfer_syntax_tag);
592 removed |= dataset.remove(mac_algorithm_tag);
593 removed |= dataset.remove(data_elements_signed_tag);
594
595 return removed;
596}
597
599 return generate_uid();
600}
601
603 const core::dicom_dataset& dataset,
604 std::span<const core::dicom_tag> tags,
605 mac_algorithm algo
606) -> std::vector<std::uint8_t> {
607 // Serialize elements
608 auto data = serialize_elements_for_mac(dataset, tags);
609
610 // Compute hash
611 const EVP_MD* md = get_mac_evp_md(algo);
612 unsigned int md_len = EVP_MD_size(md);
613
614 std::vector<std::uint8_t> result(md_len);
615
616 evp_md_ctx_ptr ctx(EVP_MD_CTX_new());
617 if (!ctx) {
618 return {};
619 }
620
621 if (EVP_DigestInit_ex(ctx.get(), md, nullptr) != 1) {
622 return {};
623 }
624
625 if (EVP_DigestUpdate(ctx.get(), data.data(), data.size()) != 1) {
626 return {};
627 }
628
629 if (EVP_DigestFinal_ex(ctx.get(), result.data(), &md_len) != 1) {
630 return {};
631 }
632
633 result.resize(md_len);
634 return result;
635}
636
638 std::span<const std::uint8_t> mac_data,
639 const private_key& key,
642 if (!key.is_loaded()) {
643 return kcenon::common::make_error<std::vector<std::uint8_t>>(
644 1, "Private key not loaded", "digital_signature");
645 }
646
647 // Get EVP_PKEY from private_key
648 // Note: This requires internal access to the private_key implementation
649 auto* key_impl = key.impl();
650 if (!key_impl) {
651 return kcenon::common::make_error<std::vector<std::uint8_t>>(
652 2, "Invalid private key", "digital_signature");
653 }
654
655 // Access the EVP_PKEY through the impl
656 // In production, this would use a proper internal API
657 EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(const_cast<void*>(
658 static_cast<const void*>(key_impl)));
659
660 const EVP_MD* md = get_evp_md(algo);
661
662 evp_md_ctx_ptr ctx(EVP_MD_CTX_new());
663 if (!ctx) {
664 return kcenon::common::make_error<std::vector<std::uint8_t>>(
665 3, "Failed to create context: " + get_openssl_error(), "digital_signature");
666 }
667
668 if (EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, pkey) != 1) {
669 return kcenon::common::make_error<std::vector<std::uint8_t>>(
670 4, "Failed to init sign: " + get_openssl_error(), "digital_signature");
671 }
672
673 if (EVP_DigestSignUpdate(ctx.get(), mac_data.data(), mac_data.size()) != 1) {
674 return kcenon::common::make_error<std::vector<std::uint8_t>>(
675 5, "Failed to update sign: " + get_openssl_error(), "digital_signature");
676 }
677
678 // Get signature length
679 size_t sig_len = 0;
680 if (EVP_DigestSignFinal(ctx.get(), nullptr, &sig_len) != 1) {
681 return kcenon::common::make_error<std::vector<std::uint8_t>>(
682 6, "Failed to get signature length: " + get_openssl_error(), "digital_signature");
683 }
684
685 std::vector<std::uint8_t> signature(sig_len);
686 if (EVP_DigestSignFinal(ctx.get(), signature.data(), &sig_len) != 1) {
687 return kcenon::common::make_error<std::vector<std::uint8_t>>(
688 7, "Failed to create signature: " + get_openssl_error(), "digital_signature");
689 }
690
691 signature.resize(sig_len);
692 return signature;
693}
694
696 std::span<const std::uint8_t> mac_data,
697 std::span<const std::uint8_t> signature,
698 const certificate& cert,
700) -> bool {
701 if (!cert.is_loaded()) {
702 return false;
703 }
704
705 // Get X509 from certificate
706 auto* cert_impl = cert.impl();
707 if (!cert_impl) {
708 return false;
709 }
710
711 // Access the X509 through the impl
712 X509* x509 = reinterpret_cast<X509*>(const_cast<void*>(
713 static_cast<const void*>(cert_impl)));
714
715 EVP_PKEY* pkey = X509_get_pubkey(x509);
716 if (!pkey) {
717 return false;
718 }
719
720 const EVP_MD* md = get_evp_md(algo);
721
722 evp_md_ctx_ptr ctx(EVP_MD_CTX_new());
723 if (!ctx) {
724 EVP_PKEY_free(pkey);
725 return false;
726 }
727
728 bool result = false;
729
730 if (EVP_DigestVerifyInit(ctx.get(), nullptr, md, nullptr, pkey) == 1 &&
731 EVP_DigestVerifyUpdate(ctx.get(), mac_data.data(), mac_data.size()) == 1 &&
732 EVP_DigestVerifyFinal(ctx.get(), signature.data(), signature.size()) == 1) {
733 result = true;
734 }
735
736 EVP_PKEY_free(pkey);
737 return result;
738}
739
740} // namespace kcenon::pacs::security
static auto load_from_der(std::span< const std::uint8_t > der_data) -> kcenon::common::Result< certificate >
Load certificate from DER-encoded bytes.
auto impl() const noexcept -> const certificate_impl *
Get internal implementation (for internal use only)
static auto compute_mac(const core::dicom_dataset &dataset, std::span< const core::dicom_tag > tags, mac_algorithm algo) -> std::vector< std::uint8_t >
static auto sign_mac(std::span< const std::uint8_t > mac_data, const private_key &key, signature_algorithm algo) -> kcenon::common::Result< std::vector< std::uint8_t > >
static auto has_signature(const core::dicom_dataset &dataset) -> bool
Check if a dataset contains digital signatures.
static auto remove_signatures(core::dicom_dataset &dataset) -> bool
Remove all digital signatures from a dataset.
static auto sign(core::dicom_dataset &dataset, const certificate &cert, const private_key &key, signature_algorithm algo=signature_algorithm::rsa_sha256) -> kcenon::common::VoidResult
Sign a DICOM dataset.
static auto verify_with_trust(const core::dicom_dataset &dataset, const std::vector< certificate > &trusted_certs) -> kcenon::common::Result< signature_status >
Verify digital signatures with a trusted certificate store.
static auto verify(const core::dicom_dataset &dataset) -> kcenon::common::Result< signature_status >
Verify digital signatures in a dataset.
static auto sign_tags(core::dicom_dataset &dataset, const certificate &cert, const private_key &key, std::span< const core::dicom_tag > tags_to_sign, signature_algorithm algo=signature_algorithm::rsa_sha256) -> kcenon::common::VoidResult
Sign specific tags in a DICOM dataset.
static auto verify_mac_signature(std::span< const std::uint8_t > mac_data, std::span< const std::uint8_t > signature, const certificate &cert, signature_algorithm algo) -> bool
static auto get_signature_info(const core::dicom_dataset &dataset) -> std::optional< signature_info >
Get information about signatures in a dataset.
static auto get_all_signatures(const core::dicom_dataset &dataset) -> std::vector< signature_info >
Get all signatures in a dataset.
static auto generate_signature_uid() -> std::string
Generate a new Digital Signature UID.
DICOM Data Element representation (Tag, VR, Value)
DICOM Digital Signature creation and verification per PS3.15.
@ OB
Other Byte (variable length)
@ DT
Date Time (26 chars max)
@ UI
Unique Identifier (64 chars max)
@ CS
Code String (16 chars max, uppercase + digits + space + underscore)
@ AT
Attribute Tag (4 bytes)
std::string generate_uid(const std::string &root="1.2.826.0.1.3680043.9.9999")
Generate a unique UID for testing.
constexpr std::string_view to_dicom_uid(mac_algorithm algo)
Convert mac_algorithm to DICOM UID string.
X509 * get_x509_from_certificate(const certificate &cert)
mac_algorithm
MAC algorithm identifiers per DICOM PS3.15.
@ untrusted_signer
Signer certificate is not trusted.
@ no_signature
No signature present in dataset.
@ valid
Signature is valid and trusted.
@ expired
Signer certificate has expired.
@ invalid
Signature verification failed (tampered data)
signature_algorithm
Signature algorithms supported for DICOM digital signatures.
@ rsa_sha512
RSA with SHA-512 (highest security)
@ ecdsa_sha256
ECDSA with SHA-256 (compact signatures)
@ rsa_sha256
RSA with SHA-256 (recommended for most use cases)
Information about a digital signature.