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,10 +104,18 @@ namespace ix
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                ; // FIXME
 | 
					                ; // FIXME
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else if (mbedtls_x509_crt_parse_file(&_cacert, _tlsOptions.caFile.c_str()) < 0)
 | 
					            else {
 | 
				
			||||||
            {
 | 
					                if (_tlsOptions.isUsingInMemoryCAs()) {
 | 
				
			||||||
                errMsg = "Cannot parse CA file '" + _tlsOptions.caFile + "'";
 | 
					                    const char *buffer = _tlsOptions.caFile.c_str();
 | 
				
			||||||
                return false;
 | 
					                    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);
 | 
					            mbedtls_ssl_conf_ca_chain(&_conf, &_cacert, NULL);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -195,6 +195,61 @@ namespace ix
 | 
				
			|||||||
        return ctx;
 | 
					        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
 | 
					     * Check whether a hostname matches a pattern
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
@@ -402,21 +457,27 @@ namespace ix
 | 
				
			|||||||
                    return false;
 | 
					                    return false;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					            } 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();
 | 
				
			||||||
 | 
					                        errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" + _tlsOptions.caFile +
 | 
				
			||||||
 | 
					                                 "\") failed: ";
 | 
				
			||||||
 | 
					                        errMsg += ERR_error_string(sslErr, nullptr);
 | 
				
			||||||
 | 
					                        return false;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    SSL_CTX_set_verify(_ssl_context,
 | 
				
			||||||
 | 
					                                       SSL_VERIFY_PEER,
 | 
				
			||||||
 | 
					                                       [](int preverify, X509_STORE_CTX*) -> int { return preverify; });
 | 
				
			||||||
 | 
					                    SSL_CTX_set_verify_depth(_ssl_context, 4);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else if (SSL_CTX_load_verify_locations(
 | 
					 | 
				
			||||||
                         _ssl_context, _tlsOptions.caFile.c_str(), NULL) != 1)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                auto sslErr = ERR_get_error();
 | 
					 | 
				
			||||||
                errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" + _tlsOptions.caFile +
 | 
					 | 
				
			||||||
                         "\") failed: ";
 | 
					 | 
				
			||||||
                errMsg += ERR_error_string(sslErr, nullptr);
 | 
					 | 
				
			||||||
                return false;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            SSL_CTX_set_verify(_ssl_context,
 | 
					 | 
				
			||||||
                               SSL_VERIFY_PEER,
 | 
					 | 
				
			||||||
                               [](int preverify, X509_STORE_CTX*) -> int { return preverify; });
 | 
					 | 
				
			||||||
            SSL_CTX_set_verify_depth(_ssl_context, 4);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else
 | 
					        else
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@@ -526,26 +587,31 @@ namespace ix
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
                else
 | 
					                else
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    const char* root_ca_file = _tlsOptions.caFile.c_str();
 | 
					                    if (_tlsOptions.isUsingInMemoryCAs()) {
 | 
				
			||||||
                    STACK_OF(X509_NAME) * rootCAs;
 | 
					                        // Load from memory
 | 
				
			||||||
                    rootCAs = SSL_load_client_CA_file(root_ca_file);
 | 
					                        openSSLAddCARootsFromString(_tlsOptions.caFile);
 | 
				
			||||||
                    if (rootCAs == NULL)
 | 
					                    } else {
 | 
				
			||||||
                    {
 | 
					                        const char* root_ca_file = _tlsOptions.caFile.c_str();
 | 
				
			||||||
                        auto sslErr = ERR_get_error();
 | 
					                        STACK_OF(X509_NAME) * rootCAs;
 | 
				
			||||||
                        errMsg = "OpenSSL failed - SSL_load_client_CA_file('" + _tlsOptions.caFile +
 | 
					                        rootCAs = SSL_load_client_CA_file(root_ca_file);
 | 
				
			||||||
                                 "') failed: ";
 | 
					                        if (rootCAs == NULL)
 | 
				
			||||||
                        errMsg += ERR_error_string(sslErr, nullptr);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    else
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        SSL_CTX_set_client_CA_list(_ssl_context, rootCAs);
 | 
					 | 
				
			||||||
                        if (SSL_CTX_load_verify_locations(_ssl_context, root_ca_file, nullptr) != 1)
 | 
					 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
                            auto sslErr = ERR_get_error();
 | 
					                            auto sslErr = ERR_get_error();
 | 
				
			||||||
                            errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" +
 | 
					                            errMsg = "OpenSSL failed - SSL_load_client_CA_file('" + _tlsOptions.caFile +
 | 
				
			||||||
                                     _tlsOptions.caFile + "\") failed: ";
 | 
					                                     "') failed: ";
 | 
				
			||||||
                            errMsg += ERR_error_string(sslErr, nullptr);
 | 
					                            errMsg += ERR_error_string(sslErr, nullptr);
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					                        else
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            SSL_CTX_set_client_CA_list(_ssl_context, rootCAs);
 | 
				
			||||||
 | 
					                            if (SSL_CTX_load_verify_locations(_ssl_context, root_ca_file, nullptr) != 1)
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
 | 
					                                auto sslErr = ERR_get_error();
 | 
				
			||||||
 | 
					                                errMsg = "OpenSSL failed - SSL_CTX_load_verify_locations(\"" +
 | 
				
			||||||
 | 
					                                         _tlsOptions.caFile + "\") failed: ";
 | 
				
			||||||
 | 
					                                errMsg += ERR_error_string(sslErr, nullptr);
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -40,6 +40,7 @@ namespace ix
 | 
				
			|||||||
        void openSSLInitialize();
 | 
					        void openSSLInitialize();
 | 
				
			||||||
        std::string getSSLError(int ret);
 | 
					        std::string getSSLError(int ret);
 | 
				
			||||||
        SSL_CTX* openSSLCreateContext(std::string& errMsg);
 | 
					        SSL_CTX* openSSLCreateContext(std::string& errMsg);
 | 
				
			||||||
 | 
					        bool openSSLAddCARootsFromString(const std::string roots);
 | 
				
			||||||
        bool openSSLClientHandshake(const std::string& hostname,
 | 
					        bool openSSLClientHandshake(const std::string& hostname,
 | 
				
			||||||
                                    std::string& errMsg,
 | 
					                                    std::string& errMsg,
 | 
				
			||||||
                                    const CancellationRequest& isCancellationRequested);
 | 
					                                    const CancellationRequest& isCancellationRequested);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -58,6 +58,10 @@ namespace ix
 | 
				
			|||||||
        return caFile == kTLSCAFileUseSystemDefaults;
 | 
					        return caFile == kTLSCAFileUseSystemDefaults;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool SocketTLSOptions::isUsingInMemoryCAs() const {
 | 
				
			||||||
 | 
					        return caFile.find("-----BEGIN CERTIFICATE-----") != std::string::npos;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bool SocketTLSOptions::isPeerVerifyDisabled() const
 | 
					    bool SocketTLSOptions::isPeerVerifyDisabled() const
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return caFile == kTLSCAFileDisableVerify;
 | 
					        return caFile == kTLSCAFileDisableVerify;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,6 +36,8 @@ namespace ix
 | 
				
			|||||||
        bool hasCertAndKey() const;
 | 
					        bool hasCertAndKey() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        bool isUsingSystemDefaults() const;
 | 
					        bool isUsingSystemDefaults() const;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        bool isUsingInMemoryCAs() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        bool isPeerVerifyDisabled() const;
 | 
					        bool isPeerVerifyDisabled() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user