Mercurial > hg > orthanc
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 } |