comparison OrthancFramework/Sources/DicomNetworking/DicomServer.cpp @ 4430:f5d44e30b429

testing DICOM TLS in Orthanc SCP
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 04 Jan 2021 12:42:45 +0100
parents 756126cd2219
children fcbac3e8ac1c
comparison
equal deleted inserted replaced
4429:48ff722fad1f 4430:f5d44e30b429
24 #include "DicomServer.h" 24 #include "DicomServer.h"
25 25
26 #include "../Logging.h" 26 #include "../Logging.h"
27 #include "../MultiThreading/RunnableWorkersPool.h" 27 #include "../MultiThreading/RunnableWorkersPool.h"
28 #include "../OrthancException.h" 28 #include "../OrthancException.h"
29 #include "../SystemToolbox.h"
29 #include "../Toolbox.h" 30 #include "../Toolbox.h"
30 #include "Internals/CommandDispatcher.h" 31 #include "Internals/CommandDispatcher.h"
31 32
32 #include <boost/thread.hpp> 33 #include <boost/thread.hpp>
33 34
35 #if ORTHANC_ENABLE_SSL == 1
36 # include <dcmtk/dcmtls/tlslayer.h>
37 #endif
38
34 #if defined(__linux__) 39 #if defined(__linux__)
35 #include <cstdlib> 40 # include <cstdlib>
36 #endif 41 #endif
37 42
38 43
39 namespace Orthanc 44 namespace Orthanc
40 { 45 {
41 struct DicomServer::PImpl 46 struct DicomServer::PImpl
42 { 47 {
43 boost::thread thread_; 48 boost::thread thread_;
44 T_ASC_Network *network_; 49 T_ASC_Network *network_;
45 std::unique_ptr<RunnableWorkersPool> workers_; 50 std::unique_ptr<RunnableWorkersPool> workers_;
51
52 #if ORTHANC_ENABLE_SSL == 1
53 std::unique_ptr<DcmTLSTransportLayer> tls_;
54 #endif
46 }; 55 };
47 56
48 57
49 void DicomServer::ServerThread(DicomServer* server) 58 void DicomServer::ServerThread(DicomServer* server,
59 bool useDicomTls)
50 { 60 {
51 CLOG(INFO, DICOM) << "DICOM server started"; 61 CLOG(INFO, DICOM) << "DICOM server started";
52 62
53 while (server->continue_) 63 while (server->continue_)
54 { 64 {
55 /* receive an association and acknowledge or reject it. If the association was */ 65 /* receive an association and acknowledge or reject it. If the association was */
56 /* acknowledged, offer corresponding services and invoke one or more if required. */ 66 /* acknowledged, offer corresponding services and invoke one or more if required. */
57 std::unique_ptr<Internals::CommandDispatcher> dispatcher(Internals::AcceptAssociation(*server, server->pimpl_->network_)); 67 std::unique_ptr<Internals::CommandDispatcher> dispatcher(
68 Internals::AcceptAssociation(*server, server->pimpl_->network_, useDicomTls));
58 69
59 try 70 try
60 { 71 {
61 if (dispatcher.get() != NULL) 72 if (dispatcher.get() != NULL)
62 { 73 {
347 { 358 {
348 throw OrthancException(ErrorCode_NoApplicationEntityFilter); 359 throw OrthancException(ErrorCode_NoApplicationEntityFilter);
349 } 360 }
350 } 361 }
351 362
363
364 #if ORTHANC_ENABLE_SSL == 1
365
366 #if DCMTK_VERSION_NUMBER < 364
367 # define DCF_Filetype_PEM SSL_FILETYPE_PEM
368 #endif
369
370 // New in Orthanc 1.9.0
371 void DicomServer::InitializeDicomTls()
372 {
373 // TODO - Configuration options
374 const std::string cf = "/tmp/j/Client.crt"; // This is the "--add-cert-file" ("+cf") option from DCMTK command-line tools
375 const std::string key = "/tmp/j/Server.key"; // This is the first argument of "+tls" option
376 const std::string cert = "/tmp/j/Server.crt"; // This is the second argument of "+tls" option
377
378 if (!SystemToolbox::IsRegularFile(cf))
379 {
380 throw OrthancException(ErrorCode_InexistentFile, "Cannot read file with trusted certificates for DICOM TLS: " + cf);
381 }
382
383 if (!SystemToolbox::IsRegularFile(key))
384 {
385 throw OrthancException(ErrorCode_InexistentFile, "Cannot read file with private key for DICOM TLS: " + key);
386 }
387
388 if (!SystemToolbox::IsRegularFile(cert))
389 {
390 throw OrthancException(ErrorCode_InexistentFile, "Cannot read file with server certificate for DICOM TLS: " + cert);
391 }
392
393 CLOG(INFO, DICOM) << "Initializing DICOM TLS";
394 pimpl_->tls_.reset(new DcmTLSTransportLayer(NET_ACCEPTOR /*opt_networkRole*/, NULL /*opt_readSeedFile*/,
395 OFFalse /*initializeOpenSSL, done by Orthanc::Toolbox::InitializeOpenSsl()*/));
396
397 if (pimpl_->tls_->addTrustedCertificateFile(cf.c_str(), DCF_Filetype_PEM /*opt_keyFileFormat*/) != TCS_ok)
398 {
399 throw OrthancException(ErrorCode_BadFileFormat, "Cannot parse PEM file with trusted certificates for DICOM TLS: " + cf);
400 }
401
402 if (pimpl_->tls_->setPrivateKeyFile(key.c_str(), DCF_Filetype_PEM /*opt_keyFileFormat*/) != TCS_ok)
403 {
404 throw OrthancException(ErrorCode_BadFileFormat, "Cannot parse PEM file with private key for DICOM TLS: " + key);
405 }
406
407 if (pimpl_->tls_->setCertificateFile(cert.c_str(), DCF_Filetype_PEM /*opt_keyFileFormat*/) != TCS_ok)
408 {
409 throw OrthancException(ErrorCode_BadFileFormat, "Cannot parse PEM file with server certificate for DICOM TLS: " + cert);
410 }
411
412 if (!pimpl_->tls_->checkPrivateKeyMatchesCertificate())
413 {
414 throw OrthancException(ErrorCode_BadFileFormat, "The private key doesn't match the server certificate: " + key + " vs. " + cert);
415 }
416
417 #if DCMTK_VERSION_NUMBER >= 364
418 if (pimpl_->tls_->setTLSProfile(TSP_Profile_BCP195 /*opt_tlsProfile*/) != TCS_ok)
419 {
420 throw OrthancException(ErrorCode_InternalError, "Cannot set the DICOM TLS profile");
421 }
422
423 if (pimpl_->tls_->activateCipherSuites())
424 {
425 throw OrthancException(ErrorCode_InternalError, "Cannot activate the cipher suites for DICOM TLS");
426 }
427 #endif
428
429 pimpl_->tls_->setCertificateVerification(DCV_requireCertificate /*opt_certVerification*/);
430
431 if (ASC_setTransportLayer(pimpl_->network_, pimpl_->tls_.get(), 0).bad())
432 {
433 throw OrthancException(ErrorCode_InternalError, "Cannot enable DICOM TLS in the server");
434 }
435 }
436 #endif
437
438
352 void DicomServer::Start() 439 void DicomServer::Start()
353 { 440 {
354 if (modalities_ == NULL) 441 if (modalities_ == NULL)
355 { 442 {
356 throw OrthancException(ErrorCode_BadSequenceOfCalls, 443 throw OrthancException(ErrorCode_BadSequenceOfCalls,
363 OFCondition cond = ASC_initializeNetwork 450 OFCondition cond = ASC_initializeNetwork
364 (NET_ACCEPTOR, OFstatic_cast(int, port_), /*opt_acse_timeout*/ 30, &pimpl_->network_); 451 (NET_ACCEPTOR, OFstatic_cast(int, port_), /*opt_acse_timeout*/ 30, &pimpl_->network_);
365 if (cond.bad()) 452 if (cond.bad())
366 { 453 {
367 throw OrthancException(ErrorCode_DicomPortInUse, 454 throw OrthancException(ErrorCode_DicomPortInUse,
368 " (port = " + boost::lexical_cast<std::string>(port_) + ") cannot create network: " + std::string(cond.text())); 455 " (port = " + boost::lexical_cast<std::string>(port_) +
456 ") cannot create network: " + std::string(cond.text()));
457 }
458
459 bool useDicomTls = false; // TODO - Read from configuration option
460
461 #if ORTHANC_ENABLE_SSL == 1
462 if (useDicomTls)
463 {
464 try
465 {
466 InitializeDicomTls();
467 }
468 catch (OrthancException&)
469 {
470 pimpl_->tls_.reset(NULL);
471 ASC_dropNetwork(&pimpl_->network_);
472 throw;
473 }
474 }
475 #endif
476
477 if (useDicomTls)
478 {
479 CLOG(INFO, DICOM) << "Orthanc SCP will use DICOM TLS";
480 }
481 else
482 {
483 CLOG(INFO, DICOM) << "Orthanc SCP will *not* use DICOM TLS";
369 } 484 }
370 485
371 continue_ = true; 486 continue_ = true;
372 pimpl_->workers_.reset(new RunnableWorkersPool(4)); // Use 4 workers - TODO as a parameter? 487 pimpl_->workers_.reset(new RunnableWorkersPool(4)); // Use 4 workers - TODO as a parameter?
373 pimpl_->thread_ = boost::thread(ServerThread, this); 488 pimpl_->thread_ = boost::thread(ServerThread, this, useDicomTls);
374 } 489 }
375 490
376 491
377 void DicomServer::Stop() 492 void DicomServer::Stop()
378 { 493 {
384 { 499 {
385 pimpl_->thread_.join(); 500 pimpl_->thread_.join();
386 } 501 }
387 502
388 pimpl_->workers_.reset(NULL); 503 pimpl_->workers_.reset(NULL);
504
505 #if ORTHANC_ENABLE_SSL == 1
506 pimpl_->tls_.reset(NULL);
507 #endif
389 508
390 /* drop the network, i.e. free memory of T_ASC_Network* structure. This call */ 509 /* drop the network, i.e. free memory of T_ASC_Network* structure. This call */
391 /* is the counterpart of ASC_initializeNetwork(...) which was called above. */ 510 /* is the counterpart of ASC_initializeNetwork(...) which was called above. */
392 OFCondition cond = ASC_dropNetwork(&pimpl_->network_); 511 OFCondition cond = ASC_dropNetwork(&pimpl_->network_);
393 if (cond.bad()) 512 if (cond.bad())