comparison 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
comparison
equal deleted inserted replaced
2021:bd143a77eb7a 2022:fefbe71c2272
41 #include <curl/curl.h> 41 #include <curl/curl.h>
42 #include <boost/algorithm/string/predicate.hpp> 42 #include <boost/algorithm/string/predicate.hpp>
43 #include <boost/thread/mutex.hpp> 43 #include <boost/thread/mutex.hpp>
44 44
45 45
46 #if ORTHANC_PKCS11_ENABLED == 1
47
48 #include <openssl/engine.h>
49 #include <libp11.h>
50
51 // Include the "libengine-pkcs11-openssl" from the libp11 package
52 extern "C"
53 {
54 #pragma GCC diagnostic error "-fpermissive"
55 #include <libp11/eng_front.c>
56 }
57
58 #endif
59
60
46 extern "C" 61 extern "C"
47 { 62 {
48 static CURLcode GetHttpStatus(CURLcode code, CURL* curl, long* status) 63 static CURLcode GetHttpStatus(CURLcode code, CURL* curl, long* status)
49 { 64 {
50 if (code == CURLE_OK) 65 if (code == CURLE_OK)
80 boost::mutex mutex_; 95 boost::mutex mutex_;
81 bool httpsVerifyPeers_; 96 bool httpsVerifyPeers_;
82 std::string httpsCACertificates_; 97 std::string httpsCACertificates_;
83 std::string proxy_; 98 std::string proxy_;
84 long timeout_; 99 long timeout_;
100 bool pkcs11Initialized_;
101
102 #if ORTHANC_PKCS11_ENABLED == 1
103 static ENGINE* LoadPkcs11Engine()
104 {
105 // This function mimics the "ENGINE_load_dynamic" function from
106 // OpenSSL, in file "crypto/engine/eng_dyn.c"
107
108 ENGINE* engine = ENGINE_new();
109 if (!engine)
110 {
111 LOG(ERROR) << "Cannot create an OpenSSL engine for PKCS11";
112 throw OrthancException(ErrorCode_InternalError);
113 }
114
115 if (!bind_helper(engine) ||
116 !ENGINE_add(engine))
117 {
118 LOG(ERROR) << "Cannot initialize the OpenSSL engine for PKCS11";
119 ENGINE_free(engine);
120 throw OrthancException(ErrorCode_InternalError);
121 }
122
123 // If the "ENGINE_add" worked, it gets a structural
124 // reference. We release our just-created reference.
125 ENGINE_free(engine);
126
127 assert(!strcmp("pkcs11", PKCS11_ENGINE_ID));
128 return ENGINE_by_id(PKCS11_ENGINE_ID);
129 }
130 #endif
85 131
86 GlobalParameters() : 132 GlobalParameters() :
87 httpsVerifyPeers_(true), 133 httpsVerifyPeers_(true),
88 timeout_(0) 134 timeout_(0),
135 pkcs11Initialized_(false)
89 { 136 {
90 } 137 }
91 138
92 public: 139 public:
93 // Singleton pattern 140 // Singleton pattern
142 long GetDefaultTimeout() 189 long GetDefaultTimeout()
143 { 190 {
144 boost::mutex::scoped_lock lock(mutex_); 191 boost::mutex::scoped_lock lock(mutex_);
145 return timeout_; 192 return timeout_;
146 } 193 }
194
195 bool IsPkcs11Initialized()
196 {
197 boost::mutex::scoped_lock lock(mutex_);
198 return pkcs11Initialized_;
199 }
200
201
202 #if ORTHANC_PKCS11_ENABLED == 1
203 void InitializePkcs11(const std::string& module,
204 const std::string& pin,
205 bool verbose)
206 {
207 boost::mutex::scoped_lock lock(mutex_);
208
209 if (pkcs11Initialized_)
210 {
211 LOG(ERROR) << "The PKCS11 engine has already been initialized";
212 throw OrthancException(ErrorCode_BadSequenceOfCalls);
213 }
214
215 if (module.empty() ||
216 !Toolbox::IsRegularFile(module))
217 {
218 LOG(ERROR) << "The PKCS11 module must be a path to one shared library (DLL or .so)";
219 throw OrthancException(ErrorCode_InexistentFile);
220 }
221
222 ENGINE* engine = LoadPkcs11Engine();
223 if (!engine)
224 {
225 LOG(ERROR) << "Cannot create an OpenSSL engine for PKCS11";
226 throw OrthancException(ErrorCode_InternalError);
227 }
228
229 if (!ENGINE_ctrl_cmd_string(engine, "MODULE_PATH", module.c_str(), 0))
230 {
231 LOG(ERROR) << "Cannot configure the OpenSSL dynamic engine for PKCS11";
232 throw OrthancException(ErrorCode_InternalError);
233 }
234
235 if (verbose)
236 {
237 ENGINE_ctrl_cmd_string(engine, "VERBOSE", NULL, 0);
238 }
239
240 if (!pin.empty() &&
241 !ENGINE_ctrl_cmd_string(engine, "PIN", pin.c_str(), 0))
242 {
243 LOG(ERROR) << "Cannot set the PIN code for PKCS11";
244 throw OrthancException(ErrorCode_InternalError);
245 }
246
247 if (!ENGINE_init(engine))
248 {
249 LOG(ERROR) << "Cannot initialize the OpenSSL dynamic engine for PKCS11";
250 throw OrthancException(ErrorCode_InternalError);
251 }
252
253 LOG(WARNING) << "The PKCS11 engine has been successfully initialized";
254 pkcs11Initialized_ = true;
255 }
256 #endif
147 }; 257 };
148 258
149 259
150 struct HttpClient::PImpl 260 struct HttpClient::PImpl
151 { 261 {
240 } 350 }
241 351
242 352
243 HttpClient::HttpClient() : 353 HttpClient::HttpClient() :
244 pimpl_(new PImpl), 354 pimpl_(new PImpl),
245 verifyPeers_(true) 355 verifyPeers_(true),
356 pkcs11Enabled_(false)
246 { 357 {
247 Setup(); 358 Setup();
248 } 359 }
249 360
250 361
267 SetClientCertificate(service.GetCertificateFile(), 378 SetClientCertificate(service.GetCertificateFile(),
268 service.GetCertificateKeyFile(), 379 service.GetCertificateKeyFile(),
269 service.GetCertificateKeyPassword()); 380 service.GetCertificateKeyPassword());
270 } 381 }
271 382
383 SetPkcs11Enabled(service.IsPkcs11Enabled());
384
272 SetUrl(service.GetUrl() + uri); 385 SetUrl(service.GetUrl() + uri);
273 } 386 }
274 387
275 388
276 HttpClient::~HttpClient() 389 HttpClient::~HttpClient()
327 { 440 {
328 answer.clear(); 441 answer.clear();
329 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_URL, url_.c_str())); 442 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_URL, url_.c_str()));
330 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEDATA, &answer)); 443 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEDATA, &answer));
331 444
445 #if ORTHANC_SSL_ENABLED == 1
332 // Setup HTTPS-related options 446 // Setup HTTPS-related options
333 #if ORTHANC_SSL_ENABLED == 1 447
334 if (verifyPeers_) 448 if (verifyPeers_)
335 { 449 {
336 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CAINFO, caCertificates_.c_str())); 450 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CAINFO, caCertificates_.c_str()));
337 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYHOST, 2)); // libcurl default is strict verifyhost 451 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYHOST, 2)); // libcurl default is strict verifyhost
338 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 1)); 452 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 1));
341 { 455 {
342 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYHOST, 0)); 456 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYHOST, 0));
343 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 0)); 457 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 0));
344 } 458 }
345 #endif 459 #endif
460
461 // Setup the HTTPS client certificate
462 if (!clientCertificateFile_.empty() &&
463 pkcs11Enabled_)
464 {
465 LOG(ERROR) << "Cannot enable both client certificates and PKCS#11 authentication";
466 throw OrthancException(ErrorCode_ParameterOutOfRange);
467 }
468
469 if (pkcs11Enabled_)
470 {
471 #if ORTHANC_PKCS11_ENABLED == 1
472 if (GlobalParameters::GetInstance().IsPkcs11Initialized())
473 {
474 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLENGINE, "pkcs11"));
475 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEYTYPE, "ENG"));
476 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERTTYPE, "ENG"));
477 }
478 else
479 {
480 LOG(ERROR) << "Cannot use PKCS11 for a HTTPS request, because it has not been initialized";
481 throw OrthancException(ErrorCode_BadSequenceOfCalls);
482 }
483 #else
484 LOG(ERROR) << "This version of Orthanc is compiled without support for PKCS11";
485 throw OrthancException(ErrorCode_InternalError);
486 #endif
487 }
488 else if (!clientCertificateFile_.empty())
489 {
490 #if ORTHANC_SSL_ENABLED == 1
491 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERTTYPE, "PEM"));
492 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERT, clientCertificateFile_.c_str()));
493
494 if (!clientCertificateKeyPassword_.empty())
495 {
496 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_KEYPASSWD, clientCertificateKeyPassword_.c_str()));
497 }
498
499 // NB: If no "clientKeyFile_" is provided, the key must be
500 // prepended to the certificate file
501 if (!clientCertificateKeyFile_.empty())
502 {
503 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEYTYPE, "PEM"));
504 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEY, clientCertificateKeyFile_.c_str()));
505 }
506 #else
507 LOG(ERROR) << "This version of Orthanc is compiled without OpenSSL support, cannot use HTTPS client authentication";
508 throw OrthancException(ErrorCode_InternalError);
509 #endif
510 }
346 511
347 // Reset the parameters from previous calls to Apply() 512 // Reset the parameters from previous calls to Apply()
348 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->userHeaders_)); 513 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->userHeaders_));
349 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 0L)); 514 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 0L));
350 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 0L)); 515 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 0L));
374 if (proxy_.size() != 0) 539 if (proxy_.size() != 0)
375 { 540 {
376 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PROXY, proxy_.c_str())); 541 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PROXY, proxy_.c_str()));
377 } 542 }
378 543
379 // Set the HTTPS client certificate
380 if (!clientCertificateFile_.empty())
381 {
382 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERTTYPE, "PEM"));
383 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERT, clientCertificateFile_.c_str()));
384
385 if (!clientCertificateKeyPassword_.empty())
386 {
387 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_KEYPASSWD, clientCertificateKeyPassword_.c_str()));
388 }
389
390 // NB: If no "clientKeyFile_" is provided, the key must be
391 // prepended to the certificate file
392 if (!clientCertificateKeyFile_.empty())
393 {
394 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEYTYPE, "PEM"));
395 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEY, clientCertificateKeyFile_.c_str()));
396 }
397 }
398
399 switch (method_) 544 switch (method_)
400 { 545 {
401 case HttpMethod_Get: 546 case HttpMethod_Get:
402 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 1L)); 547 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 1L));
403 break; 548 break;
538 } 683 }
539 684
540 685
541 void HttpClient::GlobalInitialize() 686 void HttpClient::GlobalInitialize()
542 { 687 {
688 #if ORTHANC_SSL_ENABLED == 1
689 CheckCode(curl_global_init(CURL_GLOBAL_ALL));
690 #else
691 CheckCode(curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_SSL));
692 #endif
693
543 CheckCode(curl_global_init(CURL_GLOBAL_DEFAULT)); 694 CheckCode(curl_global_init(CURL_GLOBAL_DEFAULT));
544 } 695 }
545 696
546 697
547 void HttpClient::GlobalFinalize() 698 void HttpClient::GlobalFinalize()
603 754
604 clientCertificateFile_ = certificateFile; 755 clientCertificateFile_ = certificateFile;
605 clientCertificateKeyFile_ = certificateKeyFile; 756 clientCertificateKeyFile_ = certificateKeyFile;
606 clientCertificateKeyPassword_ = certificateKeyPassword; 757 clientCertificateKeyPassword_ = certificateKeyPassword;
607 } 758 }
759
760
761 void HttpClient::InitializePkcs11(const std::string& module,
762 const std::string& pin,
763 bool verbose)
764 {
765 #if ORTHANC_PKCS11_ENABLED == 1
766 LOG(INFO) << "Initializing PKCS#11 using " << module
767 << (pin.empty() ? "(no PIN provided)" : "(PIN is provided)");
768 GlobalParameters::GetInstance().InitializePkcs11(module, pin, verbose);
769 #else
770 LOG(ERROR) << "This version of Orthanc is compiled without support for PKCS11";
771 throw OrthancException(ErrorCode_InternalError);
772 #endif
773 }
608 } 774 }