# HG changeset patch # User Sebastien Jodogne # Date 1609772372 -3600 # Node ID fcbac3e8ac1c451c9e98bb17aed47c2ed7d5327b # Parent b7f27b1166853deb211b6521837d6445df13d700 dicom tls for scu diff -r b7f27b116685 -r fcbac3e8ac1c OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake --- a/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake Mon Jan 04 14:34:44 2021 +0100 +++ b/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake Mon Jan 04 15:59:32 2021 +0100 @@ -547,6 +547,12 @@ ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/RemoteModalityParameters.cpp ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/TimeoutDicomConnectionManager.cpp ) + + if (ENABLE_SSL) + list(APPEND ORTHANC_DICOM_SOURCES_INTERNAL + ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/Internals/DicomTls.cpp + ) + endif() else() add_definitions(-DORTHANC_ENABLE_DCMTK_NETWORKING=0) endif() diff -r b7f27b116685 -r fcbac3e8ac1c OrthancFramework/SharedLibrary/NOTES.txt --- a/OrthancFramework/SharedLibrary/NOTES.txt Mon Jan 04 14:34:44 2021 +0100 +++ b/OrthancFramework/SharedLibrary/NOTES.txt Mon Jan 04 15:59:32 2021 +0100 @@ -36,6 +36,11 @@ $ ninja -j4 +!! For some reason, the linking step works on Ubuntu 16.04, but *not* + on Ubuntu 18.04. It looks as it the symbols from the C++ standard + were missing. + + Cross-compilation to Windows 32 (using MinGW) =============================== diff -r b7f27b116685 -r fcbac3e8ac1c OrthancFramework/Sources/DicomNetworking/DicomAssociation.cpp --- a/OrthancFramework/Sources/DicomNetworking/DicomAssociation.cpp Mon Jan 04 14:34:44 2021 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/DicomAssociation.cpp Mon Jan 04 15:59:32 2021 +0100 @@ -116,6 +116,10 @@ void DicomAssociation::CloseInternal() { +#if ORTHANC_ENABLE_SSL == 1 + tls_.reset(NULL); // Transport layer must be destroyed before the association itself +#endif + if (assoc_ != NULL) { ASC_releaseAssociation(assoc_); @@ -249,7 +253,8 @@ assert(net_ == NULL && params_ == NULL && - assoc_ == NULL); + assoc_ == NULL && + tls_.get() == NULL); if (proposed_.empty()) { @@ -267,6 +272,26 @@ CheckConnecting(parameters, ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ acseTimeout, &net_)); CheckConnecting(parameters, ASC_createAssociationParameters(¶ms_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU)); +#if ORTHANC_ENABLE_SSL == 1 + if (false) // TODO - Configuration option + { + try + { + assert(net_ != NULL && + params_ != NULL); + + // TODO - Configuration options + tls_.reset(Internals::InitializeDicomTls(net_, NET_REQUESTOR, + "/tmp/j/Client.key", "/tmp/j/Client.crt", "/tmp/j/Server.crt")); + } + catch (OrthancException&) + { + CloseInternal(); + throw; + } + } +#endif + // Set this application's title and the called application's title in the params CheckConnecting(parameters, ASC_setAPTitles( params_, parameters.GetLocalApplicationEntityTitle().c_str(), @@ -290,7 +315,7 @@ CheckConnecting(parameters, ASC_setPresentationAddresses(params_, localHost, remoteHostAndPort)); // Set various options - CheckConnecting(parameters, ASC_setTransportLayerType(params_, /*opt_secureConnection*/ false)); + CheckConnecting(parameters, ASC_setTransportLayerType(params_, (tls_.get() != NULL) /*opt_secureConnection*/)); // Setup the list of proposed presentation contexts unsigned int presentationContextId = 1; diff -r b7f27b116685 -r fcbac3e8ac1c OrthancFramework/Sources/DicomNetworking/DicomAssociation.h --- a/OrthancFramework/Sources/DicomNetworking/DicomAssociation.h Mon Jan 04 14:34:44 2021 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/DicomAssociation.h Mon Jan 04 15:59:32 2021 +0100 @@ -26,6 +26,14 @@ # 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 + +#if ORTHANC_ENABLE_SSL == 1 +# include "Internals/DicomTls.h" +#endif + #include "DicomAssociationParameters.h" #include @@ -61,6 +69,10 @@ T_ASC_Parameters* params_; T_ASC_Association* assoc_; +#if ORTHANC_ENABLE_SSL == 1 + std::unique_ptr tls_; +#endif + void Initialize(); void CheckConnecting(const DicomAssociationParameters& parameters, diff -r b7f27b116685 -r fcbac3e8ac1c OrthancFramework/Sources/DicomNetworking/DicomServer.cpp --- a/OrthancFramework/Sources/DicomNetworking/DicomServer.cpp Mon Jan 04 14:34:44 2021 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/DicomServer.cpp Mon Jan 04 15:59:32 2021 +0100 @@ -33,7 +33,7 @@ #include #if ORTHANC_ENABLE_SSL == 1 -# include +# include "Internals/DicomTls.h" #endif #if defined(__linux__) @@ -361,81 +361,6 @@ } -#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) @@ -459,15 +384,18 @@ bool useDicomTls = false; // TODO - Read from configuration option #if ORTHANC_ENABLE_SSL == 1 + assert(pimpl_->tls_.get() == NULL); + if (useDicomTls) { try { - InitializeDicomTls(); + // TODO - Configuration options + pimpl_->tls_.reset(Internals::InitializeDicomTls(pimpl_->network_, NET_ACCEPTOR, + "/tmp/j/Server.key", "/tmp/j/Server.crt", "/tmp/j/Client.crt")); } catch (OrthancException&) { - pimpl_->tls_.reset(NULL); ASC_dropNetwork(&pimpl_->network_); throw; } @@ -503,7 +431,7 @@ pimpl_->workers_.reset(NULL); #if ORTHANC_ENABLE_SSL == 1 - pimpl_->tls_.reset(NULL); + pimpl_->tls_.reset(NULL); // Transport layer must be destroyed before the association itself #endif /* drop the network, i.e. free memory of T_ASC_Network* structure. This call */ diff -r b7f27b116685 -r fcbac3e8ac1c OrthancFramework/Sources/DicomNetworking/DicomServer.h --- a/OrthancFramework/Sources/DicomNetworking/DicomServer.h Mon Jan 04 14:34:44 2021 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/DicomServer.h Mon Jan 04 15:59:32 2021 +0100 @@ -84,10 +84,6 @@ static void ServerThread(DicomServer* server, bool useDicomTls); -#if ORTHANC_ENABLE_SSL == 1 - void InitializeDicomTls(); -#endif - public: DicomServer(); diff -r b7f27b116685 -r fcbac3e8ac1c OrthancFramework/Sources/DicomNetworking/Internals/DicomTls.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancFramework/Sources/DicomNetworking/Internals/DicomTls.cpp Mon Jan 04 15:59:32 2021 +0100 @@ -0,0 +1,161 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + +#include "../../PrecompiledHeaders.h" +#include "DicomTls.h" + +#include "../../Logging.h" +#include "../../OrthancException.h" +#include "../../SystemToolbox.h" + + +#if DCMTK_VERSION_NUMBER < 364 +# define DCF_Filetype_PEM SSL_FILETYPE_PEM +# if OPENSSL_VERSION_NUMBER >= 0x0090700fL +// This seems to correspond to TSP_Profile_AES: https://support.dcmtk.org/docs/tlsciphr_8h.html +static std::string opt_ciphersuites(TLS1_TXT_RSA_WITH_AES_128_SHA ":" SSL3_TXT_RSA_DES_192_CBC3_SHA); +# else +// This seems to correspond to TSP_Profile_Basic in DCMTK >= 3.6.4: https://support.dcmtk.org/docs/tlsciphr_8h.html +static std::string opt_ciphersuites(SSL3_TXT_RSA_DES_192_CBC3_SHA); +# endif +#endif + + +namespace Orthanc +{ + namespace Internals + { + DcmTLSTransportLayer* InitializeDicomTls(T_ASC_Network *network, + T_ASC_NetworkRole role, + const std::string& ownPrivateKeyFile, + const std::string& ownCertificateFile, + const std::string& trustedCertificatesFile) + { + if (network == NULL) + { + throw OrthancException(ErrorCode_NullPointer); + } + + if (role != NET_ACCEPTOR && + role != NET_REQUESTOR) + { + throw OrthancException(ErrorCode_ParameterOutOfRange, "Unknown role"); + } + + if (!SystemToolbox::IsRegularFile(trustedCertificatesFile)) + { + throw OrthancException(ErrorCode_InexistentFile, "Cannot read file with trusted certificates for DICOM TLS: " + + trustedCertificatesFile); + } + + if (!SystemToolbox::IsRegularFile(ownPrivateKeyFile)) + { + throw OrthancException(ErrorCode_InexistentFile, "Cannot read file with own private key for DICOM TLS: " + + ownPrivateKeyFile); + } + + if (!SystemToolbox::IsRegularFile(ownCertificateFile)) + { + throw OrthancException(ErrorCode_InexistentFile, "Cannot read file with own certificate for DICOM TLS: " + + ownCertificateFile); + } + + CLOG(INFO, DICOM) << "Initializing DICOM TLS for Orthanc " + << (role == NET_ACCEPTOR ? "SCP" : "SCU"); + +#if DCMTK_VERSION_NUMBER >= 364 + const T_ASC_NetworkRole tmpRole = role; +#else + int tmpRole; + switch (role) + { + case NET_ACCEPTOR: + tmpRole = DICOM_APPLICATION_ACCEPTOR; + break; + + case NET_REQUESTOR: + tmpRole = DICOM_APPLICATION_REQUESTOR; + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } +#endif + + std::unique_ptr tls( + new DcmTLSTransportLayer(tmpRole /*opt_networkRole*/, NULL /*opt_readSeedFile*/, + OFFalse /*initializeOpenSSL, done by Orthanc::Toolbox::InitializeOpenSsl()*/)); + + if (tls->addTrustedCertificateFile(trustedCertificatesFile.c_str(), DCF_Filetype_PEM /*opt_keyFileFormat*/) != TCS_ok) + { + throw OrthancException(ErrorCode_BadFileFormat, "Cannot parse PEM file with trusted certificates for DICOM TLS: " + + trustedCertificatesFile); + } + + if (tls->setPrivateKeyFile(ownPrivateKeyFile.c_str(), DCF_Filetype_PEM /*opt_keyFileFormat*/) != TCS_ok) + { + throw OrthancException(ErrorCode_BadFileFormat, "Cannot parse PEM file with private key for DICOM TLS: " + + ownPrivateKeyFile); + } + + if (tls->setCertificateFile(ownCertificateFile.c_str(), DCF_Filetype_PEM /*opt_keyFileFormat*/) != TCS_ok) + { + throw OrthancException(ErrorCode_BadFileFormat, "Cannot parse PEM file with own certificate for DICOM TLS: " + + ownCertificateFile); + } + + if (!tls->checkPrivateKeyMatchesCertificate()) + { + throw OrthancException(ErrorCode_BadFileFormat, "The private key doesn't match the own certificate: " + + ownPrivateKeyFile + " vs. " + ownCertificateFile); + } + +#if DCMTK_VERSION_NUMBER >= 364 + if (tls->setTLSProfile(TSP_Profile_BCP195 /*opt_tlsProfile*/) != TCS_ok) + { + throw OrthancException(ErrorCode_InternalError, "Cannot set the DICOM TLS profile"); + } + + if (tls->activateCipherSuites()) + { + throw OrthancException(ErrorCode_InternalError, "Cannot activate the cipher suites for DICOM TLS"); + } +#else + CLOG(INFO, DICOM) << "Using the following cipher suites for DICOM TLS: " << opt_ciphersuites; + if (tls->setCipherSuites(opt_ciphersuites.c_str()) != TCS_ok) + { + throw OrthancException(ErrorCode_InternalError, "Unable to set cipher suites to: " + opt_ciphersuites); + } +#endif + + tls->setCertificateVerification(DCV_requireCertificate /*opt_certVerification*/); + + if (ASC_setTransportLayer(network, tls.get(), 0).bad()) + { + throw OrthancException(ErrorCode_InternalError, "Cannot enable DICOM TLS in the Orthanc " + + std::string(role == NET_ACCEPTOR ? "SCP" : "SCU")); + } + + return tls.release(); + } + } +} diff -r b7f27b116685 -r fcbac3e8ac1c OrthancFramework/Sources/DicomNetworking/Internals/DicomTls.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancFramework/Sources/DicomNetworking/Internals/DicomTls.h Mon Jan 04 15:59:32 2021 +0100 @@ -0,0 +1,53 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2020 Osimis S.A., Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + +#pragma once + +#if ORTHANC_ENABLE_DCMTK_NETWORKING != 1 +# 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 + +#if ORTHANC_ENABLE_SSL != 1 +# error SSL support must be enabled to use this file +#endif + + +#include +#include + + +namespace Orthanc +{ + namespace Internals + { + DcmTLSTransportLayer* InitializeDicomTls( + T_ASC_Network *network, + T_ASC_NetworkRole role, + const std::string& ownPrivateKeyFile, // This is the first argument of "+tls" option from DCMTK command-line tools + const std::string& ownCertificateFile, // This is the second argument of "+tls" option + const std::string& trustedCertificatesFile); // This is the "--add-cert-file" ("+cf") option + } +}