| #pragma once |
| |
| #include "logging.hpp" |
| #include "persistent_data.hpp" |
| |
| #include <openssl/crypto.h> |
| #include <openssl/ssl.h> |
| |
| #include <boost/asio/ip/address.hpp> |
| #include <boost/asio/ssl/verify_context.hpp> |
| |
| #include <memory> |
| #include <span> |
| |
| inline std::shared_ptr<persistent_data::UserSession> |
| verifyMtlsUser(const boost::asio::ip::address& clientIp, |
| boost::asio::ssl::verify_context& ctx) |
| { |
| // do nothing if TLS is disabled |
| if (!persistent_data::SessionStore::getInstance() |
| .getAuthMethodsConfig() |
| .tls) |
| { |
| BMCWEB_LOG_DEBUG("TLS auth_config is disabled"); |
| return nullptr; |
| } |
| |
| X509_STORE_CTX* cts = ctx.native_handle(); |
| if (cts == nullptr) |
| { |
| BMCWEB_LOG_DEBUG("Cannot get native TLS handle."); |
| return nullptr; |
| } |
| |
| // Get certificate |
| X509* peerCert = X509_STORE_CTX_get_current_cert(ctx.native_handle()); |
| if (peerCert == nullptr) |
| { |
| BMCWEB_LOG_DEBUG("Cannot get current TLS certificate."); |
| return nullptr; |
| } |
| |
| // Check if certificate is OK |
| int ctxError = X509_STORE_CTX_get_error(cts); |
| if (ctxError != X509_V_OK) |
| { |
| BMCWEB_LOG_INFO("Last TLS error is: {}", ctxError); |
| return nullptr; |
| } |
| // 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 {}), waiting to reach final depth", |
| depth); |
| return nullptr; |
| } |
| |
| 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, nullptr, nullptr)); |
| |
| if ((usage == nullptr) || (usage->data == nullptr)) |
| { |
| BMCWEB_LOG_DEBUG("TLS usage is null"); |
| return nullptr; |
| } |
| |
| for (auto usageChar : |
| std::span(usage->data, static_cast<size_t>(usage->length))) |
| { |
| if (KU_DIGITAL_SIGNATURE & usageChar) |
| { |
| isKeyUsageDigitalSignature = true; |
| } |
| if (KU_KEY_AGREEMENT & usageChar) |
| { |
| isKeyUsageKeyAgreement = true; |
| } |
| } |
| ASN1_BIT_STRING_free(usage); |
| |
| if (!isKeyUsageDigitalSignature || !isKeyUsageKeyAgreement) |
| { |
| BMCWEB_LOG_DEBUG("Certificate ExtendedKeyUsage does " |
| "not allow provided certificate to " |
| "be used for user authentication"); |
| return nullptr; |
| } |
| |
| // 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, nullptr, nullptr)); |
| |
| if (extUsage == nullptr) |
| { |
| BMCWEB_LOG_DEBUG("TLS extUsage is null"); |
| return nullptr; |
| } |
| |
| bool isExKeyUsageClientAuth = false; |
| for (int i = 0; i < sk_ASN1_OBJECT_num(extUsage); i++) |
| { |
| // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) |
| int nid = OBJ_obj2nid(sk_ASN1_OBJECT_value(extUsage, i)); |
| if (NID_client_auth == nid) |
| { |
| isExKeyUsageClientAuth = true; |
| break; |
| } |
| } |
| sk_ASN1_OBJECT_free(extUsage); |
| |
| // 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 nullptr; |
| } |
| 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) |
| { |
| BMCWEB_LOG_DEBUG("TLS cannot get username to create session"); |
| return nullptr; |
| } |
| |
| size_t lastChar = sslUser.find('\0'); |
| if (lastChar == std::string::npos || lastChar == 0) |
| { |
| BMCWEB_LOG_DEBUG("Invalid TLS user name"); |
| return nullptr; |
| } |
| sslUser.resize(lastChar); |
| std::string unsupportedClientId; |
| return persistent_data::SessionStore::getInstance().generateUserSession( |
| sslUser, clientIp, unsupportedClientId, |
| persistent_data::PersistenceType::TIMEOUT); |
| } |