# HG changeset patch # User Sebastien Jodogne # Date 1609760565 -3600 # Node ID f5d44e30b4297748e09d4b2d5029771a4e04db13 # Parent 48ff722fad1f3c32d2b9f3cfa1d0e37a4213e62d testing DICOM TLS in Orthanc SCP diff -r 48ff722fad1f -r f5d44e30b429 OrthancFramework/Resources/CMake/DcmtkConfiguration.cmake --- a/OrthancFramework/Resources/CMake/DcmtkConfiguration.cmake Sat Jan 02 14:53:24 2021 +0100 +++ b/OrthancFramework/Resources/CMake/DcmtkConfiguration.cmake Mon Jan 04 12:42:45 2021 +0100 @@ -155,12 +155,12 @@ if (HAVE_SSL_CTX_GET0_PARAM) message("Have SSL_CTX_get0_param(): yes") set_source_files_properties(${DCMTK_SOURCES} - PROPERTIES COMPILE_DEFINITIONS "WITH_OPENSSL;HAVE_SSL_CTX_GET0_PARAM") + PROPERTIES COMPILE_DEFINITIONS "HAVE_SSL_CTX_GET0_PARAM") else() message("Have SSL_CTX_get0_param(): no") - set_source_files_properties(${DCMTK_SOURCES} - PROPERTIES COMPILE_DEFINITIONS "WITH_OPENSSL") - endif() + endif() + + add_definitions(-DWITH_OPENSSL=1) endif() diff -r 48ff722fad1f -r f5d44e30b429 OrthancFramework/Sources/DicomNetworking/DicomServer.cpp --- a/OrthancFramework/Sources/DicomNetworking/DicomServer.cpp Sat Jan 02 14:53:24 2021 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/DicomServer.cpp Mon Jan 04 12:42:45 2021 +0100 @@ -26,13 +26,18 @@ #include "../Logging.h" #include "../MultiThreading/RunnableWorkersPool.h" #include "../OrthancException.h" +#include "../SystemToolbox.h" #include "../Toolbox.h" #include "Internals/CommandDispatcher.h" #include +#if ORTHANC_ENABLE_SSL == 1 +# include +#endif + #if defined(__linux__) -#include +# include #endif @@ -43,10 +48,15 @@ boost::thread thread_; T_ASC_Network *network_; std::unique_ptr workers_; + +#if ORTHANC_ENABLE_SSL == 1 + std::unique_ptr tls_; +#endif }; - void DicomServer::ServerThread(DicomServer* server) + void DicomServer::ServerThread(DicomServer* server, + bool useDicomTls) { CLOG(INFO, DICOM) << "DICOM server started"; @@ -54,7 +64,8 @@ { /* receive an association and acknowledge or reject it. If the association was */ /* acknowledged, offer corresponding services and invoke one or more if required. */ - std::unique_ptr dispatcher(Internals::AcceptAssociation(*server, server->pimpl_->network_)); + std::unique_ptr dispatcher( + Internals::AcceptAssociation(*server, server->pimpl_->network_, useDicomTls)); try { @@ -349,6 +360,82 @@ } } + +#if ORTHANC_ENABLE_SSL == 1 + +#if DCMTK_VERSION_NUMBER < 364 +# define DCF_Filetype_PEM SSL_FILETYPE_PEM +#endif + + // New in Orthanc 1.9.0 + void DicomServer::InitializeDicomTls() + { + // TODO - Configuration options + const std::string cf = "/tmp/j/Client.crt"; // This is the "--add-cert-file" ("+cf") option from DCMTK command-line tools + const std::string key = "/tmp/j/Server.key"; // This is the first argument of "+tls" option + const std::string cert = "/tmp/j/Server.crt"; // This is the second argument of "+tls" option + + if (!SystemToolbox::IsRegularFile(cf)) + { + throw OrthancException(ErrorCode_InexistentFile, "Cannot read file with trusted certificates for DICOM TLS: " + cf); + } + + if (!SystemToolbox::IsRegularFile(key)) + { + throw OrthancException(ErrorCode_InexistentFile, "Cannot read file with private key for DICOM TLS: " + key); + } + + if (!SystemToolbox::IsRegularFile(cert)) + { + throw OrthancException(ErrorCode_InexistentFile, "Cannot read file with server certificate for DICOM TLS: " + cert); + } + + CLOG(INFO, DICOM) << "Initializing DICOM TLS"; + pimpl_->tls_.reset(new DcmTLSTransportLayer(NET_ACCEPTOR /*opt_networkRole*/, NULL /*opt_readSeedFile*/, + OFFalse /*initializeOpenSSL, done by Orthanc::Toolbox::InitializeOpenSsl()*/)); + + if (pimpl_->tls_->addTrustedCertificateFile(cf.c_str(), DCF_Filetype_PEM /*opt_keyFileFormat*/) != TCS_ok) + { + throw OrthancException(ErrorCode_BadFileFormat, "Cannot parse PEM file with trusted certificates for DICOM TLS: " + cf); + } + + if (pimpl_->tls_->setPrivateKeyFile(key.c_str(), DCF_Filetype_PEM /*opt_keyFileFormat*/) != TCS_ok) + { + throw OrthancException(ErrorCode_BadFileFormat, "Cannot parse PEM file with private key for DICOM TLS: " + key); + } + + if (pimpl_->tls_->setCertificateFile(cert.c_str(), DCF_Filetype_PEM /*opt_keyFileFormat*/) != TCS_ok) + { + throw OrthancException(ErrorCode_BadFileFormat, "Cannot parse PEM file with server certificate for DICOM TLS: " + cert); + } + + if (!pimpl_->tls_->checkPrivateKeyMatchesCertificate()) + { + throw OrthancException(ErrorCode_BadFileFormat, "The private key doesn't match the server certificate: " + key + " vs. " + cert); + } + +#if DCMTK_VERSION_NUMBER >= 364 + if (pimpl_->tls_->setTLSProfile(TSP_Profile_BCP195 /*opt_tlsProfile*/) != TCS_ok) + { + throw OrthancException(ErrorCode_InternalError, "Cannot set the DICOM TLS profile"); + } + + if (pimpl_->tls_->activateCipherSuites()) + { + throw OrthancException(ErrorCode_InternalError, "Cannot activate the cipher suites for DICOM TLS"); + } +#endif + + pimpl_->tls_->setCertificateVerification(DCV_requireCertificate /*opt_certVerification*/); + + if (ASC_setTransportLayer(pimpl_->network_, pimpl_->tls_.get(), 0).bad()) + { + throw OrthancException(ErrorCode_InternalError, "Cannot enable DICOM TLS in the server"); + } + } +#endif + + void DicomServer::Start() { if (modalities_ == NULL) @@ -365,12 +452,40 @@ if (cond.bad()) { throw OrthancException(ErrorCode_DicomPortInUse, - " (port = " + boost::lexical_cast(port_) + ") cannot create network: " + std::string(cond.text())); + " (port = " + boost::lexical_cast(port_) + + ") cannot create network: " + std::string(cond.text())); + } + + bool useDicomTls = false; // TODO - Read from configuration option + +#if ORTHANC_ENABLE_SSL == 1 + if (useDicomTls) + { + try + { + InitializeDicomTls(); + } + catch (OrthancException&) + { + pimpl_->tls_.reset(NULL); + ASC_dropNetwork(&pimpl_->network_); + throw; + } + } +#endif + + if (useDicomTls) + { + CLOG(INFO, DICOM) << "Orthanc SCP will use DICOM TLS"; + } + else + { + CLOG(INFO, DICOM) << "Orthanc SCP will *not* use DICOM TLS"; } continue_ = true; pimpl_->workers_.reset(new RunnableWorkersPool(4)); // Use 4 workers - TODO as a parameter? - pimpl_->thread_ = boost::thread(ServerThread, this); + pimpl_->thread_ = boost::thread(ServerThread, this, useDicomTls); } @@ -387,6 +502,10 @@ pimpl_->workers_.reset(NULL); +#if ORTHANC_ENABLE_SSL == 1 + pimpl_->tls_.reset(NULL); +#endif + /* drop the network, i.e. free memory of T_ASC_Network* structure. This call */ /* is the counterpart of ASC_initializeNetwork(...) which was called above. */ OFCondition cond = ASC_dropNetwork(&pimpl_->network_); diff -r 48ff722fad1f -r f5d44e30b429 OrthancFramework/Sources/DicomNetworking/DicomServer.h --- a/OrthancFramework/Sources/DicomNetworking/DicomServer.h Sat Jan 02 14:53:24 2021 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/DicomServer.h Mon Jan 04 12:42:45 2021 +0100 @@ -26,6 +26,10 @@ # error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be set to 1 #endif +#if !defined(ORTHANC_ENABLE_SSL) +# error The macro ORTHANC_ENABLE_SSL must be defined +#endif + #include "IFindRequestHandlerFactory.h" #include "IMoveRequestHandlerFactory.h" #include "IGetRequestHandlerFactory.h" @@ -77,7 +81,12 @@ IStorageCommitmentRequestHandlerFactory* storageCommitmentFactory_; IApplicationEntityFilter* applicationEntityFilter_; - static void ServerThread(DicomServer* server); + static void ServerThread(DicomServer* server, + bool useDicomTls); + +#if ORTHANC_ENABLE_SSL == 1 + void InitializeDicomTls(); +#endif public: DicomServer(); diff -r 48ff722fad1f -r f5d44e30b429 OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp --- a/OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp Sat Jan 02 14:53:24 2021 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp Mon Jan 04 12:42:45 2021 +0100 @@ -253,7 +253,9 @@ - CommandDispatcher* AcceptAssociation(const DicomServer& server, T_ASC_Network *net) + CommandDispatcher* AcceptAssociation(const DicomServer& server, + T_ASC_Network *net, + bool useDicomTls) { DcmAssociationConfiguration asccfg; char buf[BUFSIZ]; @@ -265,7 +267,7 @@ cond = ASC_receiveAssociation(net, &assoc, /*opt_maxPDU*/ ASC_DEFAULTMAXPDU, NULL, NULL, - /*opt_secureConnection*/ OFFalse, + useDicomTls /*opt_secureConnection*/, DUL_NOBLOCK, 1); if (cond == DUL_NOASSOCIATIONREQUEST) diff -r 48ff722fad1f -r f5d44e30b429 OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.h --- a/OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.h Sat Jan 02 14:53:24 2021 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.h Mon Jan 04 12:42:45 2021 +0100 @@ -65,7 +65,8 @@ }; CommandDispatcher* AcceptAssociation(const DicomServer& server, - T_ASC_Network *net); + T_ASC_Network *net, + bool useDicomTls); OFCondition EchoScp(T_ASC_Association* assoc, T_DIMSE_Message* msg,