PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
test_tls_integration.cpp File Reference

TLS Integration Tests - Secure DICOM Communication. More...

#include "test_fixtures.h"
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>
#include "kcenon/pacs/integration/network_adapter.h"
#include "kcenon/pacs/network/dimse/dimse_message.h"
#include "kcenon/pacs/services/verification_scp.h"
#include <filesystem>
#include <thread>
Include dependency graph for test_tls_integration.cpp:

Go to the source code of this file.

Functions

 TEST_CASE ("TLS C-ECHO connection", "[tls][connectivity]")
 
 TEST_CASE ("TLS certificate validation", "[tls][security]")
 
 TEST_CASE ("Mutual TLS authentication", "[tls][mtls]")
 
 TEST_CASE ("TLS version negotiation", "[tls][version]")
 
 TEST_CASE ("Multiple concurrent TLS connections", "[tls][concurrent]")
 
 TEST_CASE ("TLS configuration validation", "[tls][config]")
 

Detailed Description

TLS Integration Tests - Secure DICOM Communication.

Tests TLS-secured DICOM communication using the secure_dicom example applications and network_adapter TLS configuration.

See also
Issue #141 - TLS Integration Tests
DICOM PS3.15 - Security and System Management Profiles

Test Scenarios:

  1. Basic TLS connection
  2. Certificate validation
  3. Mutual TLS (mTLS)
  4. TLS-secured store/query workflow

Definition in file test_tls_integration.cpp.

Function Documentation

◆ TEST_CASE() [1/6]

TEST_CASE ( "Multiple concurrent TLS connections" ,
"" [tls][concurrent] )

Definition at line 598 of file test_tls_integration.cpp.

598 {
599 auto certs = get_test_certificates();
600
601 if (!certs.all_exist()) {
602 SKIP("Test certificates not available");
603 }
604
605 tls_config server_tls;
606 server_tls.enabled = true;
607 server_tls.cert_path = certs.server_cert;
608 server_tls.key_path = certs.server_key;
609 server_tls.ca_path = certs.ca_cert;
610 server_tls.verify_peer = false;
611 server_tls.min_version = tls_config::tls_version::v1_2;
612
613 auto port = find_available_port();
614 tls_test_server server(port, "CONCURRENT_TLS", server_tls);
615
616 if (!server.is_tls_valid()) {
617 SKIP("TLS not properly configured");
618 }
619
620 server.register_service(std::make_shared<verification_scp>());
621 REQUIRE(server.start());
622
623 constexpr int num_connections = 3;
624 std::vector<std::thread> threads;
625 std::atomic<int> success_count{0};
626
627 for (int i = 0; i < num_connections; ++i) {
628 threads.emplace_back([&, i]() {
629 tls_config client_tls;
630 client_tls.enabled = true;
631 client_tls.ca_path = certs.ca_cert;
632 client_tls.verify_peer = true;
633 client_tls.min_version = tls_config::tls_version::v1_2;
634
635 auto result = tls_test_client::connect(
636 "localhost", port, server.ae_title(),
637 "TLS_SCU_" + std::to_string(i), client_tls);
638
639 if (result.is_err()) {
640 return;
641 }
642
643 auto& assoc = result.value();
644 auto ctx_opt = assoc.accepted_context_id(verification_sop_class_uid);
645 if (!ctx_opt) {
646 return;
647 }
648
649 using namespace kcenon::pacs::network::dimse;
650 auto echo_rq = make_c_echo_rq(1, verification_sop_class_uid);
651 auto send_result = assoc.send_dimse(*ctx_opt, echo_rq);
652 if (send_result.is_err()) {
653 return;
654 }
655
656 auto recv_result = assoc.receive_dimse(default_timeout());
657 if (recv_result.is_ok()) {
658 auto& [ctx, rsp] = recv_result.value();
659 if (rsp.status() == status_success) {
660 ++success_count;
661 }
662 }
663
664 (void)assoc.release(default_timeout());
665 });
666 }
667
668 for (auto& t : threads) {
669 t.join();
670 }
671
672 server.stop();
673
674 REQUIRE(success_count == num_connections);
675}
uint16_t find_available_port(uint16_t start=default_test_port, int max_attempts=200)
Find an available port for testing.
std::chrono::milliseconds default_timeout()
Default timeout for test operations (5s normal, 30s CI)
constexpr std::string_view verification_sop_class_uid
Verification SOP Class UID (1.2.840.10008.1.1)
Configuration for TLS/SSL secure transport.
bool enabled
Enable TLS for connections.
enum kcenon::pacs::integration::tls_config::tls_version min_version
std::filesystem::path cert_path
Path to certificate file (PEM format)
std::filesystem::path ca_path
Path to CA certificate file for verification (optional)
bool verify_peer
Verify peer certificate.
std::filesystem::path key_path
Path to private key file (PEM format)

References kcenon::pacs::integration::tls_config::ca_path, kcenon::pacs::integration::tls_config::cert_path, kcenon::pacs::integration_test::default_timeout(), kcenon::pacs::integration::tls_config::enabled, kcenon::pacs::integration_test::find_available_port(), kcenon::pacs::integration::tls_config::key_path, kcenon::pacs::integration::tls_config::min_version, kcenon::pacs::services::verification_sop_class_uid, and kcenon::pacs::integration::tls_config::verify_peer.

Here is the call graph for this function:

◆ TEST_CASE() [2/6]

TEST_CASE ( "Mutual TLS authentication" ,
"" [tls][mtls] )

Definition at line 406 of file test_tls_integration.cpp.

406 {
407 auto certs = get_test_certificates();
408
409 if (!certs.all_exist()) {
410 SKIP("Test certificates not available");
411 }
412
413 SECTION("Client with valid certificate succeeds") {
414 // Server requires client certificate
415 tls_config server_tls;
416 server_tls.enabled = true;
417 server_tls.cert_path = certs.server_cert;
418 server_tls.key_path = certs.server_key;
419 server_tls.ca_path = certs.ca_cert;
420 server_tls.verify_peer = true; // Require client cert
421 server_tls.min_version = tls_config::tls_version::v1_2;
422
423 auto port = find_available_port();
424 tls_test_server server(port, "MTLS_SCP", server_tls);
425
426 if (!server.is_tls_valid()) {
427 SKIP("TLS not properly configured");
428 }
429
430 server.register_service(std::make_shared<verification_scp>());
431 REQUIRE(server.start());
432
433 // Client with certificate
434 tls_config client_tls;
435 client_tls.enabled = true;
436 client_tls.cert_path = certs.client_cert;
437 client_tls.key_path = certs.client_key;
438 client_tls.ca_path = certs.ca_cert;
439 client_tls.verify_peer = true;
440 client_tls.min_version = tls_config::tls_version::v1_2;
441
442 auto result = tls_test_client::connect(
443 "localhost", port, server.ae_title(), "MTLS_SCU", client_tls);
444
445 REQUIRE(result.is_ok());
446 auto& assoc = result.value();
447
448 // Verify we can communicate
449 REQUIRE(assoc.has_accepted_context(verification_sop_class_uid));
450
451 using namespace kcenon::pacs::network::dimse;
452 auto ctx_id = *assoc.accepted_context_id(verification_sop_class_uid);
453 auto echo_rq = make_c_echo_rq(1, verification_sop_class_uid);
454 auto send_result = assoc.send_dimse(ctx_id, echo_rq);
455 REQUIRE(send_result.is_ok());
456
457 auto recv_result = assoc.receive_dimse(default_timeout());
458 REQUIRE(recv_result.is_ok());
459
460 auto& [recv_ctx, rsp] = recv_result.value();
461 REQUIRE(rsp.status() == status_success);
462
463 (void)assoc.release(default_timeout());
464 server.stop();
465 }
466
467 SECTION("Client without certificate fails when server requires it") {
468 tls_config server_tls;
469 server_tls.enabled = true;
470 server_tls.cert_path = certs.server_cert;
471 server_tls.key_path = certs.server_key;
472 server_tls.ca_path = certs.ca_cert;
473 server_tls.verify_peer = true; // Require client cert
474 server_tls.min_version = tls_config::tls_version::v1_2;
475
476 auto port = find_available_port();
477 tls_test_server server(port, "MTLS_SCP", server_tls);
478
479 if (!server.is_tls_valid()) {
480 SKIP("TLS not properly configured");
481 }
482
483 server.register_service(std::make_shared<verification_scp>());
484 REQUIRE(server.start());
485
486 // Client without certificate
487 tls_config client_tls;
488 client_tls.enabled = true;
489 client_tls.ca_path = certs.ca_cert;
490 client_tls.verify_peer = true;
491 // No client cert/key
492 client_tls.min_version = tls_config::tls_version::v1_2;
493
494 auto result = tls_test_client::connect(
495 "localhost", port, server.ae_title(), "NO_CERT_SCU", client_tls);
496
497 // Should fail due to missing client certificate
498 // Note: Actual behavior depends on server configuration
499 if (result.is_ok()) {
500 auto& assoc = result.value();
501 // Connection might succeed but operations may fail
502 assoc.abort();
503 }
504
505 server.stop();
506 }
507}

References kcenon::pacs::integration::tls_config::ca_path, kcenon::pacs::integration::tls_config::cert_path, kcenon::pacs::integration_test::default_timeout(), kcenon::pacs::integration::tls_config::enabled, kcenon::pacs::integration_test::find_available_port(), kcenon::pacs::integration::tls_config::key_path, kcenon::pacs::integration::tls_config::min_version, kcenon::pacs::services::verification_sop_class_uid, and kcenon::pacs::integration::tls_config::verify_peer.

Here is the call graph for this function:

◆ TEST_CASE() [3/6]

TEST_CASE ( "TLS C-ECHO connection" ,
"" [tls][connectivity] )

Definition at line 247 of file test_tls_integration.cpp.

247 {
248 auto certs = get_test_certificates();
249
250 // Skip if certificates not available
251 if (!certs.all_exist()) {
252 WARN("Skipping TLS tests: certificates not found at " << certs.ca_cert.parent_path());
253 SKIP("Test certificates not available");
254 }
255
256 SECTION("TLS server accepts connection and responds to C-ECHO") {
257 // Configure TLS server
258 tls_config server_tls;
259 server_tls.enabled = true;
260 server_tls.cert_path = certs.server_cert;
261 server_tls.key_path = certs.server_key;
262 server_tls.ca_path = certs.ca_cert;
263 server_tls.verify_peer = false; // Don't require client cert
264 server_tls.min_version = tls_config::tls_version::v1_2;
265
266 auto port = find_available_port();
267 tls_test_server server(port, "TLS_SCP", server_tls);
268
269 if (!server.is_tls_valid()) {
270 WARN("TLS configuration not valid, skipping test");
271 SKIP("TLS not properly configured");
272 }
273
274 server.register_service(std::make_shared<verification_scp>());
275 REQUIRE(server.start());
276 REQUIRE(server.is_running());
277
278 // Configure TLS client
279 tls_config client_tls;
280 client_tls.enabled = true;
281 client_tls.ca_path = certs.ca_cert;
282 client_tls.verify_peer = true; // Verify server cert
283 client_tls.min_version = tls_config::tls_version::v1_2;
284
285 auto connect_result = tls_test_client::connect(
286 "localhost", port, server.ae_title(), "TLS_SCU", client_tls);
287
288 REQUIRE(connect_result.is_ok());
289 auto& assoc = connect_result.value();
290
291 // Verify connection
292 REQUIRE(assoc.has_accepted_context(verification_sop_class_uid));
293
294 auto context_id_opt = assoc.accepted_context_id(verification_sop_class_uid);
295 REQUIRE(context_id_opt.has_value());
296
297 // Send C-ECHO
298 using namespace kcenon::pacs::network::dimse;
299 auto echo_rq = make_c_echo_rq(1, verification_sop_class_uid);
300 auto send_result = assoc.send_dimse(*context_id_opt, echo_rq);
301 REQUIRE(send_result.is_ok());
302
303 auto recv_result = assoc.receive_dimse(default_timeout());
304 REQUIRE(recv_result.is_ok());
305
306 auto& [recv_ctx, echo_rsp] = recv_result.value();
307 REQUIRE(echo_rsp.command() == command_field::c_echo_rsp);
308 REQUIRE(echo_rsp.status() == status_success);
309
310 // Clean up
311 (void)assoc.release(default_timeout());
312 server.stop();
313 }
314}

References kcenon::pacs::integration::tls_config::ca_path, kcenon::pacs::integration::tls_config::cert_path, kcenon::pacs::integration_test::default_timeout(), kcenon::pacs::integration::tls_config::enabled, kcenon::pacs::integration_test::find_available_port(), kcenon::pacs::integration::tls_config::key_path, kcenon::pacs::integration::tls_config::min_version, kcenon::pacs::services::verification_sop_class_uid, and kcenon::pacs::integration::tls_config::verify_peer.

Here is the call graph for this function:

◆ TEST_CASE() [4/6]

TEST_CASE ( "TLS certificate validation" ,
"" [tls][security] )

Definition at line 320 of file test_tls_integration.cpp.

320 {
321 auto certs = get_test_certificates();
322
323 if (!certs.all_exist()) {
324 SKIP("Test certificates not available");
325 }
326
327 // Start TLS server
328 tls_config server_tls;
329 server_tls.enabled = true;
330 server_tls.cert_path = certs.server_cert;
331 server_tls.key_path = certs.server_key;
332 server_tls.ca_path = certs.ca_cert;
333 server_tls.verify_peer = false;
334 server_tls.min_version = tls_config::tls_version::v1_2;
335
336 auto port = find_available_port();
337 tls_test_server server(port, "TLS_SCP", server_tls);
338
339 if (!server.is_tls_valid()) {
340 SKIP("TLS not properly configured");
341 }
342
343 server.register_service(std::make_shared<verification_scp>());
344 REQUIRE(server.start());
345
346 SECTION("Valid certificate succeeds") {
347 tls_config client_tls;
348 client_tls.enabled = true;
349 client_tls.ca_path = certs.ca_cert;
350 client_tls.verify_peer = true;
351 client_tls.min_version = tls_config::tls_version::v1_2;
352
353 auto result = tls_test_client::connect(
354 "localhost", port, server.ae_title(), "TLS_SCU", client_tls);
355
356 REQUIRE(result.is_ok());
357 auto& assoc = result.value();
358 (void)assoc.release(default_timeout());
359 }
360
361 SECTION("Wrong CA fails validation") {
362 // Skip if other_ca doesn't exist
363 if (!std::filesystem::exists(certs.other_ca_cert)) {
364 WARN("other_ca.crt not found, skipping wrong CA test");
365 server.stop();
366 return;
367 }
368
369 tls_config client_tls;
370 client_tls.enabled = true;
371 client_tls.ca_path = certs.other_ca_cert; // Wrong CA
372 client_tls.verify_peer = true;
373 client_tls.min_version = tls_config::tls_version::v1_2;
374
375 auto result = tls_test_client::connect(
376 "localhost", port, server.ae_title(), "TLS_SCU", client_tls);
377
378 // Should fail due to certificate verification failure
379 // Note: Actual behavior depends on implementation
380 // Some implementations may still connect but with warnings
381 if (result.is_ok()) {
382 auto& assoc = result.value();
383 assoc.abort();
384 }
385 }
386
387 SECTION("TLS configuration validation") {
388 // Test invalid TLS configuration
389 tls_config invalid_tls;
390 invalid_tls.enabled = true;
391 invalid_tls.cert_path = "/nonexistent/cert.pem";
392 invalid_tls.key_path = "/nonexistent/key.pem";
393
394 auto result = network_adapter::configure_tls(invalid_tls);
395 // Should fail validation
396 REQUIRE(result.is_err());
397 }
398
399 server.stop();
400}

References kcenon::pacs::integration::tls_config::ca_path, kcenon::pacs::integration::tls_config::cert_path, kcenon::pacs::integration_test::default_timeout(), kcenon::pacs::integration::tls_config::enabled, kcenon::pacs::integration_test::find_available_port(), kcenon::pacs::integration::tls_config::key_path, kcenon::pacs::integration::tls_config::min_version, and kcenon::pacs::integration::tls_config::verify_peer.

Here is the call graph for this function:

◆ TEST_CASE() [5/6]

TEST_CASE ( "TLS configuration validation" ,
"" [tls][config] )

Definition at line 681 of file test_tls_integration.cpp.

681 {
682 SECTION("Disabled TLS is always valid") {
683 tls_config cfg;
684 cfg.enabled = false;
685
686 REQUIRE(cfg.is_valid() == true);
687 }
688
689 SECTION("Enabled TLS requires cert and key") {
690 tls_config cfg;
691 cfg.enabled = true;
692
693 // Missing both cert and key
694 REQUIRE(cfg.is_valid() == false);
695
696 // Only cert
697 cfg.cert_path = "/some/cert.pem";
698 REQUIRE(cfg.is_valid() == false);
699
700 // Both cert and key
701 cfg.key_path = "/some/key.pem";
702 REQUIRE(cfg.is_valid() == true);
703 }
704
705 SECTION("CA path is optional") {
706 tls_config cfg;
707 cfg.enabled = true;
708 cfg.cert_path = "/some/cert.pem";
709 cfg.key_path = "/some/key.pem";
710
711 // Valid without CA
712 REQUIRE(cfg.is_valid() == true);
713
714 // Still valid with CA
715 cfg.ca_path = "/some/ca.pem";
716 REQUIRE(cfg.is_valid() == true);
717 }
718}
bool is_valid() const noexcept
Check if TLS configuration is valid.

References kcenon::pacs::integration::tls_config::ca_path, kcenon::pacs::integration::tls_config::cert_path, kcenon::pacs::integration::tls_config::enabled, kcenon::pacs::integration::tls_config::is_valid(), and kcenon::pacs::integration::tls_config::key_path.

Here is the call graph for this function:

◆ TEST_CASE() [6/6]

TEST_CASE ( "TLS version negotiation" ,
"" [tls][version] )

Definition at line 513 of file test_tls_integration.cpp.

513 {
514 auto certs = get_test_certificates();
515
516 if (!certs.all_exist()) {
517 SKIP("Test certificates not available");
518 }
519
520 SECTION("TLS 1.2 connection") {
521 tls_config server_tls;
522 server_tls.enabled = true;
523 server_tls.cert_path = certs.server_cert;
524 server_tls.key_path = certs.server_key;
525 server_tls.ca_path = certs.ca_cert;
526 server_tls.verify_peer = false;
527 server_tls.min_version = tls_config::tls_version::v1_2;
528
529 auto port = find_available_port();
530 tls_test_server server(port, "TLS12_SCP", server_tls);
531
532 if (!server.is_tls_valid()) {
533 SKIP("TLS not properly configured");
534 }
535
536 server.register_service(std::make_shared<verification_scp>());
537 REQUIRE(server.start());
538
539 tls_config client_tls;
540 client_tls.enabled = true;
541 client_tls.ca_path = certs.ca_cert;
542 client_tls.verify_peer = true;
543 client_tls.min_version = tls_config::tls_version::v1_2;
544
545 auto result = tls_test_client::connect(
546 "localhost", port, server.ae_title(), "TLS12_SCU", client_tls);
547
548 REQUIRE(result.is_ok());
549 auto& assoc = result.value();
550 (void)assoc.release(default_timeout());
551 server.stop();
552 }
553
554 SECTION("TLS 1.3 connection") {
555 tls_config server_tls;
556 server_tls.enabled = true;
557 server_tls.cert_path = certs.server_cert;
558 server_tls.key_path = certs.server_key;
559 server_tls.ca_path = certs.ca_cert;
560 server_tls.verify_peer = false;
561 server_tls.min_version = tls_config::tls_version::v1_3;
562
563 auto port = find_available_port();
564 tls_test_server server(port, "TLS13_SCP", server_tls);
565
566 if (!server.is_tls_valid()) {
567 SKIP("TLS not properly configured");
568 }
569
570 server.register_service(std::make_shared<verification_scp>());
571 REQUIRE(server.start());
572
573 tls_config client_tls;
574 client_tls.enabled = true;
575 client_tls.ca_path = certs.ca_cert;
576 client_tls.verify_peer = true;
577 client_tls.min_version = tls_config::tls_version::v1_3;
578
579 auto result = tls_test_client::connect(
580 "localhost", port, server.ae_title(), "TLS13_SCU", client_tls);
581
582 // TLS 1.3 may not be supported on all systems
583 if (result.is_ok()) {
584 auto& assoc = result.value();
585 (void)assoc.release(default_timeout());
586 } else {
587 INFO("TLS 1.3 not supported: " << result.error().message);
588 }
589
590 server.stop();
591 }
592}

References kcenon::pacs::integration::tls_config::ca_path, kcenon::pacs::integration::tls_config::cert_path, kcenon::pacs::integration_test::default_timeout(), kcenon::pacs::integration::tls_config::enabled, kcenon::pacs::integration_test::find_available_port(), kcenon::pacs::integration::tls_config::key_path, kcenon::pacs::integration::tls_config::min_version, and kcenon::pacs::integration::tls_config::verify_peer.

Here is the call graph for this function: