diff Core/HttpClient.cpp @ 2022:fefbe71c2272

Possibility to use PKCS#11 authentication for hardware security modules with Orthanc peers
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 17 Jun 2016 17:09:50 +0200
parents a0bd8cd55da7
children 7fe860db9664
line wrap: on
line diff
--- a/Core/HttpClient.cpp	Wed Jun 15 17:20:52 2016 +0200
+++ b/Core/HttpClient.cpp	Fri Jun 17 17:09:50 2016 +0200
@@ -43,6 +43,21 @@
 #include <boost/thread/mutex.hpp>
 
 
+#if ORTHANC_PKCS11_ENABLED == 1
+
+#include <openssl/engine.h>
+#include <libp11.h>
+
+// Include the "libengine-pkcs11-openssl" from the libp11 package
+extern "C"
+{
+#pragma GCC diagnostic error "-fpermissive"
+#include <libp11/eng_front.c>
+}
+
+#endif
+
+
 extern "C"
 {
   static CURLcode GetHttpStatus(CURLcode code, CURL* curl, long* status)
@@ -82,10 +97,42 @@
     std::string     httpsCACertificates_;
     std::string     proxy_;
     long            timeout_;
+    bool            pkcs11Initialized_;
+
+#if ORTHANC_PKCS11_ENABLED == 1
+    static ENGINE* LoadPkcs11Engine()
+    {
+      // This function mimics the "ENGINE_load_dynamic" function from
+      // OpenSSL, in file "crypto/engine/eng_dyn.c"
+
+      ENGINE* engine = ENGINE_new();
+      if (!engine)
+      {
+        LOG(ERROR) << "Cannot create an OpenSSL engine for PKCS11";
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      if (!bind_helper(engine) ||
+          !ENGINE_add(engine))
+      {
+        LOG(ERROR) << "Cannot initialize the OpenSSL engine for PKCS11";
+        ENGINE_free(engine);
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      // If the "ENGINE_add" worked, it gets a structural
+      // reference. We release our just-created reference.
+      ENGINE_free(engine);
+
+      assert(!strcmp("pkcs11", PKCS11_ENGINE_ID));
+      return ENGINE_by_id(PKCS11_ENGINE_ID);
+    }
+#endif
 
     GlobalParameters() : 
       httpsVerifyPeers_(true),
-      timeout_(0)
+      timeout_(0),
+      pkcs11Initialized_(false)
     {
     }
 
@@ -144,6 +191,69 @@
       boost::mutex::scoped_lock lock(mutex_);
       return timeout_;
     }
+
+    bool IsPkcs11Initialized()
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      return pkcs11Initialized_;
+    }
+
+
+#if ORTHANC_PKCS11_ENABLED == 1
+    void InitializePkcs11(const std::string& module,
+                          const std::string& pin,
+                          bool verbose)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      if (pkcs11Initialized_)
+      {
+        LOG(ERROR) << "The PKCS11 engine has already been initialized";
+        throw OrthancException(ErrorCode_BadSequenceOfCalls);
+      }
+
+      if (module.empty() ||
+          !Toolbox::IsRegularFile(module))
+      {
+        LOG(ERROR) << "The PKCS11 module must be a path to one shared library (DLL or .so)";
+        throw OrthancException(ErrorCode_InexistentFile);
+      }
+
+      ENGINE* engine = LoadPkcs11Engine();
+      if (!engine)
+      {
+        LOG(ERROR) << "Cannot create an OpenSSL engine for PKCS11";
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      if (!ENGINE_ctrl_cmd_string(engine, "MODULE_PATH", module.c_str(), 0))
+      {
+        LOG(ERROR) << "Cannot configure the OpenSSL dynamic engine for PKCS11";
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      if (verbose)
+      {
+        ENGINE_ctrl_cmd_string(engine, "VERBOSE", NULL, 0);
+      }
+
+      if (!pin.empty() &&
+          !ENGINE_ctrl_cmd_string(engine, "PIN", pin.c_str(), 0)) 
+      {
+        LOG(ERROR) << "Cannot set the PIN code for PKCS11";
+        throw OrthancException(ErrorCode_InternalError);
+      }
+  
+      if (!ENGINE_init(engine))
+      {
+        LOG(ERROR) << "Cannot initialize the OpenSSL dynamic engine for PKCS11";
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      LOG(WARNING) << "The PKCS11 engine has been successfully initialized";
+      pkcs11Initialized_ = true;
+    }
+#endif
   };
 
 
@@ -242,7 +352,8 @@
 
   HttpClient::HttpClient() : 
     pimpl_(new PImpl), 
-    verifyPeers_(true)
+    verifyPeers_(true),
+    pkcs11Enabled_(false)
   {
     Setup();
   }
@@ -269,6 +380,8 @@
                            service.GetCertificateKeyPassword());
     }
 
+    SetPkcs11Enabled(service.IsPkcs11Enabled());
+
     SetUrl(service.GetUrl() + uri);
   }
 
@@ -329,8 +442,9 @@
     CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_URL, url_.c_str()));
     CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEDATA, &answer));
 
+#if ORTHANC_SSL_ENABLED == 1
     // Setup HTTPS-related options
-#if ORTHANC_SSL_ENABLED == 1
+
     if (verifyPeers_)
     {
       CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CAINFO, caCertificates_.c_str()));
@@ -344,6 +458,57 @@
     }
 #endif
 
+    // Setup the HTTPS client certificate
+    if (!clientCertificateFile_.empty() &&
+        pkcs11Enabled_)
+    {
+      LOG(ERROR) << "Cannot enable both client certificates and PKCS#11 authentication";
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    if (pkcs11Enabled_)
+    {
+#if ORTHANC_PKCS11_ENABLED == 1
+      if (GlobalParameters::GetInstance().IsPkcs11Initialized())
+      {
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLENGINE, "pkcs11"));
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEYTYPE, "ENG"));
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERTTYPE, "ENG"));
+      }
+      else
+      {
+        LOG(ERROR) << "Cannot use PKCS11 for a HTTPS request, because it has not been initialized";
+        throw OrthancException(ErrorCode_BadSequenceOfCalls);
+      }
+#else
+      LOG(ERROR) << "This version of Orthanc is compiled without support for PKCS11";
+      throw OrthancException(ErrorCode_InternalError);
+#endif
+    }
+    else if (!clientCertificateFile_.empty())
+    {
+#if ORTHANC_SSL_ENABLED == 1
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERTTYPE, "PEM"));
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERT, clientCertificateFile_.c_str()));
+
+      if (!clientCertificateKeyPassword_.empty())
+      {
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_KEYPASSWD, clientCertificateKeyPassword_.c_str()));
+      }
+
+      // NB: If no "clientKeyFile_" is provided, the key must be
+      // prepended to the certificate file
+      if (!clientCertificateKeyFile_.empty())
+      {
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEYTYPE, "PEM"));
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEY, clientCertificateKeyFile_.c_str()));
+      }
+#else
+      LOG(ERROR) << "This version of Orthanc is compiled without OpenSSL support, cannot use HTTPS client authentication";
+      throw OrthancException(ErrorCode_InternalError);
+#endif
+    }
+
     // Reset the parameters from previous calls to Apply()
     CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->userHeaders_));
     CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 0L));
@@ -376,26 +541,6 @@
       CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PROXY, proxy_.c_str()));
     }
 
-    // Set the HTTPS client certificate
-    if (!clientCertificateFile_.empty())
-    {
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERTTYPE, "PEM"));
-      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERT, clientCertificateFile_.c_str()));
-
-      if (!clientCertificateKeyPassword_.empty())
-      {
-        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_KEYPASSWD, clientCertificateKeyPassword_.c_str()));
-      }
-
-      // NB: If no "clientKeyFile_" is provided, the key must be
-      // prepended to the certificate file
-      if (!clientCertificateKeyFile_.empty())
-      {
-        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEYTYPE, "PEM"));
-        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEY, clientCertificateKeyFile_.c_str()));
-      }
-    }
-
     switch (method_)
     {
     case HttpMethod_Get:
@@ -540,6 +685,12 @@
   
   void HttpClient::GlobalInitialize()
   {
+#if ORTHANC_SSL_ENABLED == 1
+    CheckCode(curl_global_init(CURL_GLOBAL_ALL));
+#else
+    CheckCode(curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_SSL));
+#endif
+
     CheckCode(curl_global_init(CURL_GLOBAL_DEFAULT));
   }
 
@@ -605,4 +756,19 @@
     clientCertificateKeyFile_ = certificateKeyFile;
     clientCertificateKeyPassword_ = certificateKeyPassword;
   }
+
+
+  void HttpClient::InitializePkcs11(const std::string& module,
+                                    const std::string& pin,
+                                    bool verbose)
+  {
+#if ORTHANC_PKCS11_ENABLED == 1
+    LOG(INFO) << "Initializing PKCS#11 using " << module 
+              << (pin.empty() ? "(no PIN provided)" : "(PIN is provided)");
+    GlobalParameters::GetInstance().InitializePkcs11(module, pin, verbose);    
+#else
+    LOG(ERROR) << "This version of Orthanc is compiled without support for PKCS11";
+    throw OrthancException(ErrorCode_InternalError);
+#endif
+  }
 }