Implement API for adding custom roots via a string (#178)
* Implement API for adding custom roots via a string. SocketTLSOptions API design needs work, but the IXSocketOpenSSL implementation feels good to me. * Improve API design for specifying roots from memory. * Add in-memory root CAs mbedtls implementation. * Fix bug in newer versions of OpenSSL with in-memory certificate handling.
This commit is contained in:
		| @@ -104,11 +104,19 @@ namespace ix | ||||
|             { | ||||
|                 ; // FIXME | ||||
|             } | ||||
|             else if (mbedtls_x509_crt_parse_file(&_cacert, _tlsOptions.caFile.c_str()) < 0) | ||||
|             { | ||||
|             else { | ||||
|                 if (_tlsOptions.isUsingInMemoryCAs()) { | ||||
|                     const char *buffer = _tlsOptions.caFile.c_str(); | ||||
|                     size_t      bufferSize = _tlsOptions.caFile.size() + 1; // Needs to include null terminating character otherwise mbedtls will fail. | ||||
|                     if (mbedtls_x509_crt_parse(&_cacert, (const unsigned char *)buffer, bufferSize) < 0) { | ||||
|                         errMsg = "Cannot parse CA from memory."; | ||||
|                         return false; | ||||
|                     } | ||||
|                 } else if (mbedtls_x509_crt_parse_file(&_cacert, _tlsOptions.caFile.c_str()) < 0) { | ||||
|                     errMsg = "Cannot parse CA file '" + _tlsOptions.caFile + "'"; | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             mbedtls_ssl_conf_ca_chain(&_conf, &_cacert, NULL); | ||||
|         } | ||||
|   | ||||
| @@ -195,6 +195,61 @@ namespace ix | ||||
|         return ctx; | ||||
|     } | ||||
|  | ||||
|     bool SocketOpenSSL::openSSLAddCARootsFromString(const std::string roots) { | ||||
|         // Create certificate store | ||||
|         X509_STORE *certificate_store = SSL_CTX_get_cert_store(_ssl_context); | ||||
|         if (certificate_store == nullptr) | ||||
|             return false; | ||||
|          | ||||
|         // Configure to allow intermediate certs | ||||
|         X509_STORE_set_flags(certificate_store, X509_V_FLAG_TRUSTED_FIRST | X509_V_FLAG_PARTIAL_CHAIN); | ||||
|          | ||||
|         // Create a new buffer and populate it with the roots | ||||
|         BIO *buffer = BIO_new_mem_buf((void *)roots.c_str(), static_cast<int>(roots.length())); | ||||
|         if (buffer == nullptr) | ||||
|             return false; | ||||
|          | ||||
|         // Read each root in the buffer and add to the certificate store | ||||
|         bool success = true; | ||||
|         size_t number_of_roots = 0; | ||||
|          | ||||
|         while (true) { | ||||
|             // Read the next root in the buffer | ||||
|             X509 *root = PEM_read_bio_X509_AUX(buffer, nullptr, nullptr, (void *)""); | ||||
|             if (root == nullptr) { | ||||
|                 // No more certs left in the buffer, we're done. | ||||
|                 ERR_clear_error(); | ||||
|                 break; | ||||
|             } | ||||
|              | ||||
|             // Try adding the root to the certificate store | ||||
|             ERR_clear_error(); | ||||
|             if (!X509_STORE_add_cert(certificate_store, root)) { | ||||
|                 // Failed to add. If the error is unrelated to the x509 lib or the cert already exists, we're safe to continue. | ||||
|                 unsigned long error = ERR_get_error(); | ||||
|                 if (ERR_GET_LIB(error) != ERR_LIB_X509 || ERR_GET_REASON(error) != X509_R_CERT_ALREADY_IN_HASH_TABLE) { | ||||
|                     // Failed. Clean up and bail. | ||||
|                     success = false; | ||||
|                     X509_free(root); | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             // Clean up and loop | ||||
|             X509_free(root); | ||||
|             number_of_roots++; | ||||
|         } | ||||
|          | ||||
|         // Clean up buffer | ||||
|         BIO_free(buffer); | ||||
|          | ||||
|         // Make sure we loaded at least one certificate. | ||||
|         if (number_of_roots == 0) | ||||
|             success = false; | ||||
|          | ||||
|         return success; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Check whether a hostname matches a pattern | ||||
|      */ | ||||
| @@ -402,8 +457,12 @@ namespace ix | ||||
|                     return false; | ||||
|                 } | ||||
| #endif | ||||
|             } | ||||
|             else if (SSL_CTX_load_verify_locations( | ||||
|             } else { | ||||
|                 if (_tlsOptions.isUsingInMemoryCAs()) { | ||||
|                     // Load from memory | ||||
|                     openSSLAddCARootsFromString(_tlsOptions.caFile); | ||||
|                 } else { | ||||
|                     if (SSL_CTX_load_verify_locations( | ||||
|                              _ssl_context, _tlsOptions.caFile.c_str(), NULL) != 1) | ||||
|                     { | ||||
|                         auto sslErr = ERR_get_error(); | ||||
| @@ -418,6 +477,8 @@ namespace ix | ||||
|                                        [](int preverify, X509_STORE_CTX*) -> int { return preverify; }); | ||||
|                     SSL_CTX_set_verify_depth(_ssl_context, 4); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             SSL_CTX_set_verify(_ssl_context, SSL_VERIFY_NONE, nullptr); | ||||
| @@ -526,6 +587,10 @@ namespace ix | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     if (_tlsOptions.isUsingInMemoryCAs()) { | ||||
|                         // Load from memory | ||||
|                         openSSLAddCARootsFromString(_tlsOptions.caFile); | ||||
|                     } else { | ||||
|                         const char* root_ca_file = _tlsOptions.caFile.c_str(); | ||||
|                         STACK_OF(X509_NAME) * rootCAs; | ||||
|                         rootCAs = SSL_load_client_CA_file(root_ca_file); | ||||
| @@ -548,6 +613,7 @@ namespace ix | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 SSL_CTX_set_verify( | ||||
|                     _ssl_context, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); | ||||
|   | ||||
| @@ -40,6 +40,7 @@ namespace ix | ||||
|         void openSSLInitialize(); | ||||
|         std::string getSSLError(int ret); | ||||
|         SSL_CTX* openSSLCreateContext(std::string& errMsg); | ||||
|         bool openSSLAddCARootsFromString(const std::string roots); | ||||
|         bool openSSLClientHandshake(const std::string& hostname, | ||||
|                                     std::string& errMsg, | ||||
|                                     const CancellationRequest& isCancellationRequested); | ||||
|   | ||||
| @@ -58,6 +58,10 @@ namespace ix | ||||
|         return caFile == kTLSCAFileUseSystemDefaults; | ||||
|     } | ||||
|  | ||||
|     bool SocketTLSOptions::isUsingInMemoryCAs() const { | ||||
|         return caFile.find("-----BEGIN CERTIFICATE-----") != std::string::npos; | ||||
|     } | ||||
|  | ||||
|     bool SocketTLSOptions::isPeerVerifyDisabled() const | ||||
|     { | ||||
|         return caFile == kTLSCAFileDisableVerify; | ||||
|   | ||||
| @@ -37,6 +37,8 @@ namespace ix | ||||
|  | ||||
|         bool isUsingSystemDefaults() const; | ||||
|          | ||||
|         bool isUsingInMemoryCAs() const; | ||||
|  | ||||
|         bool isPeerVerifyDisabled() const; | ||||
|  | ||||
|         bool isUsingDefaultCiphers() const; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user