blob: ee1ef2c176ac4d5b285f86dbc52fe4672aed2fc6 [file] [log] [blame]
/**
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "controllers/SSLContextService.h"
#ifdef OPENSSL_SUPPORT
#include <openssl/err.h>
#include <openssl/ssl.h>
#endif
#include <string>
#include <memory>
#include <set>
#include "core/Property.h"
#include "io/validation.h"
#include "properties/Configure.h"
#include "utils/ScopeGuard.h"
namespace org {
namespace apache {
namespace nifi {
namespace minifi {
namespace controllers {
void SSLContextService::initialize() {
if (initialized_)
return;
std::lock_guard<std::mutex> lock(initialization_mutex_);
ControllerService::initialize();
initializeTLS();
initialized_ = true;
}
bool SSLContextService::configure_ssl_context(SSL_CTX *ctx) {
if (!IsNullOrEmpty(certificate)) {
if (isFileTypeP12(certificate)) {
BIO* fp = BIO_new(BIO_s_file());
if (fp == nullptr) {
logging::LOG_ERROR(logger_) << "Failed create new file BIO, " << getLatestOpenSSLErrorString();
return false;
}
utils::ScopeGuard fp_guard([fp]() { BIO_free(fp); });
if (BIO_read_filename(fp, certificate.c_str()) <= 0) {
logging::LOG_ERROR(logger_) << "Failed to read certificate file " << certificate << ", " << getLatestOpenSSLErrorString();
return false;
}
PKCS12* p12 = d2i_PKCS12_bio(fp, nullptr);
if (p12 == nullptr) {
logging::LOG_ERROR(logger_) << "Failed to DER decode certificate file " << certificate << ", " << getLatestOpenSSLErrorString();
return false;
}
utils::ScopeGuard p12_guard([p12]() { PKCS12_free(p12); });
EVP_PKEY* pkey = nullptr;
X509* cert = nullptr;
STACK_OF(X509)* ca = nullptr;
if (!PKCS12_parse(p12, passphrase_.c_str(), &pkey, &cert, &ca)) {
logging::LOG_ERROR(logger_) << "Failed to parse certificate file " << certificate << " as PKCS#12, " << getLatestOpenSSLErrorString();
return false;
}
utils::ScopeGuard certs_guard([pkey, cert, ca]() {
EVP_PKEY_free(pkey);
X509_free(cert);
sk_X509_pop_free(ca, X509_free);
});
if (SSL_CTX_use_certificate(ctx, cert) != 1) {
logging::LOG_ERROR(logger_) << "Failed to set certificate from " << certificate << ", " << getLatestOpenSSLErrorString();
return false;
}
while (ca != nullptr && sk_X509_num(ca) > 0) {
X509 *cacert = sk_X509_pop(ca);
utils::ScopeGuard cacert_guard([cacert]() { X509_free(cacert); });
if (SSL_CTX_add_extra_chain_cert(ctx, cacert) != 1) {
logging::LOG_ERROR(logger_) << "Failed to set additional certificate from " << certificate << ", " << getLatestOpenSSLErrorString();
return false;
}
cacert_guard.disable();
}
if (SSL_CTX_use_PrivateKey(ctx, pkey) != 1) {
logging::LOG_ERROR(logger_) << "Failed to set private key from " << certificate << ", " << getLatestOpenSSLErrorString();
return false;
}
} else {
if (SSL_CTX_use_certificate_chain_file(ctx, certificate.c_str()) <= 0) {
logging::LOG_ERROR(logger_) << "Could not create load certificate " << certificate << ", " << getLatestOpenSSLErrorString();
return false;
}
if (!IsNullOrEmpty(passphrase_)) {
SSL_CTX_set_default_passwd_cb_userdata(ctx, &passphrase_);
SSL_CTX_set_default_passwd_cb(ctx, minifi::io::tls::pemPassWordCb);
}
if (!IsNullOrEmpty(private_key_)) {
int retp = SSL_CTX_use_PrivateKey_file(ctx, private_key_.c_str(), SSL_FILETYPE_PEM);
if (retp != 1) {
logging::LOG_ERROR(logger_) << "Could not create load private key, " << retp << " on " << private_key_ << ", " << getLatestOpenSSLErrorString();
return false;
}
}
}
if (!SSL_CTX_check_private_key(ctx)) {
logging::LOG_ERROR(logger_) << "Private key does not match the public certificate, " << getLatestOpenSSLErrorString();
return false;
}
}
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, nullptr);
int retp = SSL_CTX_load_verify_locations(ctx, ca_certificate_.c_str(), 0);
if (retp == 0) {
logging::LOG_ERROR(logger_) << "Can not load CA certificate, Exiting, " << getLatestOpenSSLErrorString();
return false;
}
return true;
}
/**
* If OpenSSL is not installed we may still continue operations. Nullptr will
* be returned and it will be up to the caller to determine if this failure is
* recoverable.
*/
std::unique_ptr<SSLContext> SSLContextService::createSSLContext() {
#ifdef OPENSSL_SUPPORT
SSL_library_init();
const SSL_METHOD *method;
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
method = TLSv1_2_client_method();
SSL_CTX *ctx = SSL_CTX_new(method);
if (ctx == nullptr) {
return nullptr;
}
if (!configure_ssl_context(ctx)) {
SSL_CTX_free(ctx);
return nullptr;
}
return std::unique_ptr<SSLContext>(new SSLContext(ctx));
#else
return nullptr;
#endif
}
const std::string &SSLContextService::getCertificateFile() {
std::lock_guard<std::mutex> lock(initialization_mutex_);
return certificate;
}
const std::string &SSLContextService::getPassphrase() {
std::lock_guard<std::mutex> lock(initialization_mutex_);
return passphrase_;
}
const std::string &SSLContextService::getPassphraseFile() {
std::lock_guard<std::mutex> lock(initialization_mutex_);
return passphrase_file_;
}
const std::string &SSLContextService::getPrivateKeyFile() {
std::lock_guard<std::mutex> lock(initialization_mutex_);
return private_key_;
}
const std::string &SSLContextService::getCACertificate() {
std::lock_guard<std::mutex> lock(initialization_mutex_);
return ca_certificate_;
}
void SSLContextService::onEnable() {
valid_ = true;
core::Property property("Client Certificate", "Client Certificate");
core::Property privKey("Private Key", "Private Key file");
core::Property passphrase_prop("Passphrase", "Client passphrase. Either a file or unencrypted text");
core::Property caCert("CA Certificate", "CA certificate file");
std::string default_dir;
if (nullptr != configuration_)
configuration_->get(Configure::nifi_default_directory, default_dir);
logger_->log_trace("onEnable()");
bool has_certificate_property = getProperty(property.getName(), certificate);
if (has_certificate_property) {
std::ifstream cert_file(certificate);
if (!cert_file.good()) {
logger_->log_warn("Cannot open certificate file %s", certificate);
std::string test_cert = default_dir + certificate;
std::ifstream cert_file_test(test_cert);
if (cert_file_test.good()) {
certificate = test_cert;
logger_->log_info("Using certificate file %s", certificate);
} else {
logger_->log_error("Cannot open certificate file %s", test_cert);
valid_ = false;
}
cert_file_test.close();
}
cert_file.close();
} else {
logger_->log_debug("Certificate empty");
}
if (has_certificate_property && !isFileTypeP12(certificate)) {
if (getProperty(privKey.getName(), private_key_)) {
std::ifstream priv_file(private_key_);
if (!priv_file.good()) {
logger_->log_warn("Cannot open private key file %s", private_key_);
std::string test_priv = default_dir + private_key_;
std::ifstream private_file_test(test_priv);
if (private_file_test.good()) {
private_key_ = test_priv;
logger_->log_info("Using private key file %s", private_key_);
} else {
logger_->log_error("Cannot open private key file %s", test_priv);
valid_ = false;
}
private_file_test.close();
}
priv_file.close();
} else {
logger_->log_debug("Private key empty");
}
}
if (!getProperty(passphrase_prop.getName(), passphrase_)) {
logger_->log_debug("No pass phrase for %s", certificate);
} else {
std::ifstream passphrase_file(passphrase_);
if (passphrase_file.good()) {
passphrase_file_ = passphrase_;
// we should read it from the file
passphrase_.assign((std::istreambuf_iterator<char>(passphrase_file)), std::istreambuf_iterator<char>());
} else {
std::string test_passphrase = default_dir + passphrase_;
std::ifstream passphrase_file_test(test_passphrase);
if (passphrase_file_test.good()) {
passphrase_ = test_passphrase;
passphrase_file_ = test_passphrase;
passphrase_.assign((std::istreambuf_iterator<char>(passphrase_file_test)), std::istreambuf_iterator<char>());
} else {
// not an invalid file since we support a passphrase of unencrypted text
}
passphrase_file_test.close();
}
passphrase_file.close();
}
// load CA certificates
if (!getProperty(caCert.getName(), ca_certificate_)) {
logger_->log_error("Can not load CA certificate.");
} else {
std::ifstream cert_file(ca_certificate_);
if (!cert_file.good()) {
std::string test_ca_cert = default_dir + ca_certificate_;
std::ifstream ca_cert_file_file_test(test_ca_cert);
if (ca_cert_file_file_test.good()) {
ca_certificate_ = test_ca_cert;
} else {
valid_ = false;
}
ca_cert_file_file_test.close();
}
cert_file.close();
}
}
void SSLContextService::initializeTLS() {
core::Property property("Client Certificate", "Client Certificate");
core::Property privKey("Private Key", "Private Key file");
core::Property passphrase_prop("Passphrase", "Client passphrase. Either a file or unencrypted text");
core::Property caCert("CA Certificate", "CA certificate file");
std::set<core::Property> supportedProperties;
supportedProperties.insert(property);
supportedProperties.insert(privKey);
supportedProperties.insert(passphrase_prop);
supportedProperties.insert(caCert);
setSupportedProperties(supportedProperties);
}
} /* namespace controllers */
} /* namespace minifi */
} /* namespace nifi */
} /* namespace apache */
} /* namespace org */