changeset 4432:fcbac3e8ac1c

dicom tls for scu
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 04 Jan 2021 15:59:32 +0100
parents b7f27b116685
children 4f92f2b3a3ae
files OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake OrthancFramework/SharedLibrary/NOTES.txt OrthancFramework/Sources/DicomNetworking/DicomAssociation.cpp OrthancFramework/Sources/DicomNetworking/DicomAssociation.h OrthancFramework/Sources/DicomNetworking/DicomServer.cpp OrthancFramework/Sources/DicomNetworking/DicomServer.h OrthancFramework/Sources/DicomNetworking/Internals/DicomTls.cpp OrthancFramework/Sources/DicomNetworking/Internals/DicomTls.h
diffstat 8 files changed, 271 insertions(+), 85 deletions(-) [+]
line wrap: on
line diff
--- 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()
--- 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)
 ===============================
--- 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(&params_, /*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;
--- 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 <dcmtk/dcmnet/dimse.h>
@@ -61,6 +69,10 @@
     T_ASC_Parameters*                         params_;
     T_ASC_Association*                        assoc_;
 
+#if ORTHANC_ENABLE_SSL == 1
+    std::unique_ptr<DcmTLSTransportLayer>     tls_;
+#endif
+
     void Initialize();
 
     void CheckConnecting(const DicomAssociationParameters& parameters,
--- 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 <boost/thread.hpp>
 
 #if ORTHANC_ENABLE_SSL == 1
-#  include <dcmtk/dcmtls/tlslayer.h>
+#  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 */
--- 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();
 
--- /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
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#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<DcmTLSTransportLayer> 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();
+    }
+  }
+}
--- /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
+ * <http://www.gnu.org/licenses/>.
+ **/
+
+
+#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 <dcmtk/dcmnet/dimse.h>
+#include <dcmtk/dcmtls/tlslayer.h>
+
+
+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
+  }
+}