TLS based user auth implementation
Implemented TLS based user auth. It utilizes certificates stored by
Phosphor Certificate Manager in storage mode, to verify that user
that tries to log in, has a certificate signed by a trusted CA.
More about this can be read in redfish-tls-user-authentication.md design
document.
Tested that it does not break current authentication methods, when not
using TLS Auth - user should not see difference between versions. TLS Auth
itself allows user in when certificate is signed by trusted CA and valid, and
stops working immediatley after it is removed. User is not let in when provided
certificate is not between notBefore and notAfter dates. Session is tested to
not be created when user does not exist in the system (courtesy of earlier
UserManagement usage commits).
Signed-off-by: Kowalski, Kamil <kamil.kowalski@intel.com>
Change-Id: I6bcaff018fe3105f77d3c10f69765e0011af8dab
Signed-off-by: Zbigniew Kurzynski <zbigniew.kurzynski@intel.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 32c6fad..b93f342 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -77,6 +77,12 @@
'/redfish/v1/Systems/system/'."
OFF
)
+option (
+ BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
+ "Enables authenticating users through TLS client certificates.
+ The BMCWEB_INSECURE_DISABLE_SSL must be OFF for this option to take effect."
+ OFF
+)
# Insecure options. Every option that starts with a BMCWEB_INSECURE flag should
# not be enabled by default for any platform, unless the author fully
@@ -108,6 +114,14 @@
OFF
)
+
+
+if (BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION AND BMCWEB_INSECURE_DISABLE_SSL)
+ message("SSL Must be enabled to allow SSL authentication")
+ set(BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION OFF)
+endif()
+
+
include (CTest)
set (CMAKE_CXX_STANDARD 17)
@@ -344,6 +358,7 @@
target_compile_definitions (
bmcweb PRIVATE $<$<BOOL:${BMCWEB_ENABLE_KVM}>: -DBMCWEB_ENABLE_KVM>
+ $<$<BOOL:${BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION}>: -DBMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION>
$<$<BOOL:${BMCWEB_ENABLE_VM_WEBSOCKET}>: -DBMCWEB_ENABLE_VM_WEBSOCKET>
$<$<BOOL:${BMCWEB_ENABLE_DBUS_REST}>: -DBMCWEB_ENABLE_DBUS_REST>
$<$<BOOL:${BMCWEB_ENABLE_REDFISH}>: -DBMCWEB_ENABLE_REDFISH>
diff --git a/http/http_connection.h b/http/http_connection.h
index 7cab789..78805a6 100644
--- a/http/http_connection.h
+++ b/http/http_connection.h
@@ -263,6 +263,141 @@
// mechanism
parser->body_limit(httpReqBodyLimit);
req.emplace(parser->get());
+
+#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
+ adaptor.set_verify_callback(
+ [this](bool preverified, boost::asio::ssl::verify_context& ctx) {
+ // We always return true to allow full auth flow
+ if (!preverified)
+ {
+ return true;
+ }
+
+ X509_STORE_CTX* cts = ctx.native_handle();
+ if (cts == nullptr)
+ {
+ return true;
+ }
+
+ // Get certificate
+ X509* peerCert =
+ X509_STORE_CTX_get_current_cert(ctx.native_handle());
+ if (peerCert == nullptr)
+ {
+ return true;
+ }
+
+ // Check if certificate is OK
+ int error = X509_STORE_CTX_get_error(cts);
+ if (error != X509_V_OK)
+ {
+ return true;
+ }
+ // Check that we have reached final certificate in chain
+ int32_t depth = X509_STORE_CTX_get_error_depth(cts);
+ if (depth != 0)
+
+ {
+ BMCWEB_LOG_DEBUG
+ << "Certificate verification in progress (depth "
+ << depth << "), waiting to reach final depth";
+ return true;
+ }
+
+ BMCWEB_LOG_DEBUG << "Certificate verification of final depth";
+
+ // Verify KeyUsage
+ bool isKeyUsageDigitalSignature = false;
+ bool isKeyUsageKeyAgreement = false;
+
+ ASN1_BIT_STRING* usage = static_cast<ASN1_BIT_STRING*>(
+ X509_get_ext_d2i(peerCert, NID_key_usage, NULL, NULL));
+
+ if (usage == nullptr)
+ {
+ return true;
+ }
+
+ for (int i = 0; i < usage->length; i++)
+ {
+ if (KU_DIGITAL_SIGNATURE & usage->data[i])
+ {
+ isKeyUsageDigitalSignature = true;
+ }
+ if (KU_KEY_AGREEMENT & usage->data[i])
+ {
+ isKeyUsageKeyAgreement = true;
+ }
+ }
+
+ if (!isKeyUsageDigitalSignature || !isKeyUsageKeyAgreement)
+ {
+ BMCWEB_LOG_DEBUG << "Certificate ExtendedKeyUsage does "
+ "not allow provided certificate to "
+ "be used for user authentication";
+ return true;
+ }
+
+ // Determine that ExtendedKeyUsage includes Client Auth
+
+ stack_st_ASN1_OBJECT* extUsage =
+ static_cast<stack_st_ASN1_OBJECT*>(X509_get_ext_d2i(
+ peerCert, NID_ext_key_usage, NULL, NULL));
+
+ if (extUsage == nullptr)
+ {
+ return true;
+ }
+
+ bool isExKeyUsageClientAuth = false;
+ for (int i = 0; i < sk_ASN1_OBJECT_num(extUsage); i++)
+ {
+ if (NID_client_auth ==
+ OBJ_obj2nid(sk_ASN1_OBJECT_value(extUsage, i)))
+ {
+ isExKeyUsageClientAuth = true;
+ break;
+ }
+ }
+
+ // Certificate has to have proper key usages set
+ if (!isExKeyUsageClientAuth)
+ {
+ BMCWEB_LOG_DEBUG << "Certificate ExtendedKeyUsage does "
+ "not allow provided certificate to "
+ "be used for user authentication";
+ return true;
+ }
+ std::string sslUser;
+ // Extract username contained in CommonName
+ sslUser.resize(256, '\0');
+
+ int status = X509_NAME_get_text_by_NID(
+ X509_get_subject_name(peerCert), NID_commonName,
+ sslUser.data(), static_cast<int>(sslUser.size()));
+
+ if (status == -1)
+ {
+ return true;
+ }
+
+ size_t lastChar = sslUser.find('\0');
+ if (lastChar == std::string::npos || lastChar == 0)
+ {
+ return true;
+ }
+ sslUser.resize(lastChar - 1);
+
+ session =
+ persistent_data::SessionStore::getInstance()
+ .generateUserSession(
+ sslUser,
+ crow::persistent_data::PersistenceType::TIMEOUT);
+
+ return true;
+ });
+#endif // BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
+
#ifdef BMCWEB_ENABLE_DEBUG
connectionCount++;
BMCWEB_LOG_DEBUG << this << " Connection open, total "
@@ -344,6 +479,16 @@
req->middlewareContext = static_cast<void*>(&ctx);
req->ioService = static_cast<decltype(req->ioService)>(
&adaptor.get_executor().context());
+
+#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
+ if (auto sp = session.lock())
+ {
+ BMCWEB_LOG_DEBUG << "TLS session: " << sp->uniqueId
+ << " will be used for this request.";
+ req->session = sp;
+ }
+#endif // BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
+
detail::middlewareCallHelper<
0U, decltype(ctx), decltype(*middlewares), Middlewares...>(
*middlewares, *req, res, ctx);
@@ -391,12 +536,18 @@
}
void close()
{
-
if constexpr (std::is_same_v<Adaptor,
boost::beast::ssl_stream<
boost::asio::ip::tcp::socket>>)
{
adaptor.next_layer().close();
+#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
+ if (auto sp = session.lock())
+ {
+ BMCWEB_LOG_DEBUG << "Removing TLS session: " << sp->uniqueId;
+ persistent_data::SessionStore::getInstance().removeSession(sp);
+ }
+#endif // BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
}
else
{
@@ -652,6 +803,9 @@
std::optional<crow::Request> req;
crow::Response res;
+#ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
+ std::weak_ptr<crow::persistent_data::UserSession> session;
+#endif // BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
const std::string& serverName;
diff --git a/include/ssl_key_handler.hpp b/include/ssl_key_handler.hpp
index d634d63..2bd1f59 100644
--- a/include/ssl_key_handler.hpp
+++ b/include/ssl_key_handler.hpp
@@ -16,6 +16,7 @@
namespace ensuressl
{
+constexpr char const *trustStorePath = "/etc/ssl/certs/authority";
static void initOpenssl();
static EVP_PKEY *createEcKey();
@@ -312,7 +313,11 @@
boost::asio::ssl::context::no_tlsv1 |
boost::asio::ssl::context::no_tlsv1_1);
- // m_ssl_context.set_verify_mode(boost::asio::ssl::verify_peer);
+ mSslContext->set_verify_mode(boost::asio::ssl::verify_peer);
+
+ BMCWEB_LOG_DEBUG << "Using default TrustStore location: " << trustStorePath;
+ mSslContext->add_verify_path(trustStorePath);
+
mSslContext->use_certificate_file(ssl_pem_file,
boost::asio::ssl::context::pem);
mSslContext->use_private_key_file(ssl_pem_file,