xref: /openbmc/bmcweb/http/mutual_tls.hpp (revision 1b8b02a4)
1 #pragma once
2 
3 #include "logging.hpp"
4 #include "persistent_data.hpp"
5 
6 #include <openssl/crypto.h>
7 #include <openssl/ssl.h>
8 
9 #include <boost/asio/ip/address.hpp>
10 #include <boost/asio/ssl/verify_context.hpp>
11 
12 #include <memory>
13 #include <span>
14 
15 inline std::shared_ptr<persistent_data::UserSession>
16     verifyMtlsUser(const boost::asio::ip::address& clientIp,
17                    boost::asio::ssl::verify_context& ctx)
18 {
19     // do nothing if TLS is disabled
20     if (!persistent_data::SessionStore::getInstance()
21              .getAuthMethodsConfig()
22              .tls)
23     {
24         BMCWEB_LOG_DEBUG("TLS auth_config is disabled");
25         return nullptr;
26     }
27 
28     X509_STORE_CTX* cts = ctx.native_handle();
29     if (cts == nullptr)
30     {
31         BMCWEB_LOG_DEBUG("Cannot get native TLS handle.");
32         return nullptr;
33     }
34 
35     // Get certificate
36     X509* peerCert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
37     if (peerCert == nullptr)
38     {
39         BMCWEB_LOG_DEBUG("Cannot get current TLS certificate.");
40         return nullptr;
41     }
42 
43     // Check if certificate is OK
44     int ctxError = X509_STORE_CTX_get_error(cts);
45     if (ctxError != X509_V_OK)
46     {
47         BMCWEB_LOG_INFO("Last TLS error is: {}", ctxError);
48         return nullptr;
49     }
50     // Check that we have reached final certificate in chain
51     int32_t depth = X509_STORE_CTX_get_error_depth(cts);
52     if (depth != 0)
53 
54     {
55         BMCWEB_LOG_DEBUG(
56             "Certificate verification in progress (depth {}), waiting to reach final depth",
57             depth);
58         return nullptr;
59     }
60 
61     BMCWEB_LOG_DEBUG("Certificate verification of final depth");
62 
63     // Verify KeyUsage
64     bool isKeyUsageDigitalSignature = false;
65     bool isKeyUsageKeyAgreement = false;
66 
67     ASN1_BIT_STRING* usage = static_cast<ASN1_BIT_STRING*>(
68         X509_get_ext_d2i(peerCert, NID_key_usage, nullptr, nullptr));
69 
70     if ((usage == nullptr) || (usage->data == nullptr))
71     {
72         BMCWEB_LOG_DEBUG("TLS usage is null");
73         return nullptr;
74     }
75 
76     for (auto usageChar :
77          std::span(usage->data, static_cast<size_t>(usage->length)))
78     {
79         if (KU_DIGITAL_SIGNATURE & usageChar)
80         {
81             isKeyUsageDigitalSignature = true;
82         }
83         if (KU_KEY_AGREEMENT & usageChar)
84         {
85             isKeyUsageKeyAgreement = true;
86         }
87     }
88     ASN1_BIT_STRING_free(usage);
89 
90     if (!isKeyUsageDigitalSignature || !isKeyUsageKeyAgreement)
91     {
92         BMCWEB_LOG_DEBUG("Certificate ExtendedKeyUsage does "
93                          "not allow provided certificate to "
94                          "be used for user authentication");
95         return nullptr;
96     }
97 
98     // Determine that ExtendedKeyUsage includes Client Auth
99 
100     stack_st_ASN1_OBJECT* extUsage = static_cast<stack_st_ASN1_OBJECT*>(
101         X509_get_ext_d2i(peerCert, NID_ext_key_usage, nullptr, nullptr));
102 
103     if (extUsage == nullptr)
104     {
105         BMCWEB_LOG_DEBUG("TLS extUsage is null");
106         return nullptr;
107     }
108 
109     bool isExKeyUsageClientAuth = false;
110     for (int i = 0; i < sk_ASN1_OBJECT_num(extUsage); i++)
111     {
112         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
113         int nid = OBJ_obj2nid(sk_ASN1_OBJECT_value(extUsage, i));
114         if (NID_client_auth == nid)
115         {
116             isExKeyUsageClientAuth = true;
117             break;
118         }
119     }
120     sk_ASN1_OBJECT_free(extUsage);
121 
122     // Certificate has to have proper key usages set
123     if (!isExKeyUsageClientAuth)
124     {
125         BMCWEB_LOG_DEBUG("Certificate ExtendedKeyUsage does "
126                          "not allow provided certificate to "
127                          "be used for user authentication");
128         return nullptr;
129     }
130     std::string sslUser;
131     // Extract username contained in CommonName
132     sslUser.resize(256, '\0');
133 
134     int status = X509_NAME_get_text_by_NID(X509_get_subject_name(peerCert),
135                                            NID_commonName, sslUser.data(),
136                                            static_cast<int>(sslUser.size()));
137 
138     if (status == -1)
139     {
140         BMCWEB_LOG_DEBUG("TLS cannot get username to create session");
141         return nullptr;
142     }
143 
144     size_t lastChar = sslUser.find('\0');
145     if (lastChar == std::string::npos || lastChar == 0)
146     {
147         BMCWEB_LOG_DEBUG("Invalid TLS user name");
148         return nullptr;
149     }
150     sslUser.resize(lastChar);
151     std::string unsupportedClientId;
152     return persistent_data::SessionStore::getInstance().generateUserSession(
153         sslUser, clientIp, unsupportedClientId,
154         persistent_data::PersistenceType::TIMEOUT);
155 }
156