xref: /openbmc/bmcweb/http/mutual_tls.cpp (revision 4d7b5ddb3a2b6cc42b7bbc0c710f297e6df4fd55)
140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3724985ffSEd Tanous #include "mutual_tls.hpp"
4724985ffSEd Tanous 
5*4d7b5ddbSMalik Akbar Hashemi Rafsanjani #include "bmcweb_config.h"
6*4d7b5ddbSMalik Akbar Hashemi Rafsanjani 
7*4d7b5ddbSMalik Akbar Hashemi Rafsanjani #include "identity.hpp"
8*4d7b5ddbSMalik Akbar Hashemi Rafsanjani #include "mutual_tls_private.hpp"
941fe81c2SEd Tanous #include "sessions.hpp"
1041fe81c2SEd Tanous 
11*4d7b5ddbSMalik Akbar Hashemi Rafsanjani #include <bit>
1241fe81c2SEd Tanous #include <cstddef>
1341fe81c2SEd Tanous #include <cstdint>
1441fe81c2SEd Tanous #include <optional>
1541fe81c2SEd Tanous #include <string>
1641fe81c2SEd Tanous 
17724985ffSEd Tanous extern "C"
18724985ffSEd Tanous {
19*4d7b5ddbSMalik Akbar Hashemi Rafsanjani #include <openssl/asn1.h>
2041fe81c2SEd Tanous #include <openssl/obj_mac.h>
21*4d7b5ddbSMalik Akbar Hashemi Rafsanjani #include <openssl/objects.h>
2241fe81c2SEd Tanous #include <openssl/types.h>
2341fe81c2SEd Tanous #include <openssl/x509.h>
24724985ffSEd Tanous #include <openssl/x509_vfy.h>
2541fe81c2SEd Tanous #include <openssl/x509v3.h>
26724985ffSEd Tanous }
27724985ffSEd Tanous 
28724985ffSEd Tanous #include "logging.hpp"
29724985ffSEd Tanous #include "mutual_tls_meta.hpp"
30724985ffSEd Tanous 
31724985ffSEd Tanous #include <boost/asio/ip/address.hpp>
32724985ffSEd Tanous #include <boost/asio/ssl/verify_context.hpp>
33724985ffSEd Tanous 
34724985ffSEd Tanous #include <memory>
35724985ffSEd Tanous #include <string_view>
36724985ffSEd Tanous 
getCommonNameFromCert(X509 * cert)37*4d7b5ddbSMalik Akbar Hashemi Rafsanjani std::string getCommonNameFromCert(X509* cert)
38*4d7b5ddbSMalik Akbar Hashemi Rafsanjani {
39*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     std::string commonName;
40*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     // Extract username contained in CommonName
41*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     commonName.resize(256, '\0');
42*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     int length = X509_NAME_get_text_by_NID(
43*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         X509_get_subject_name(cert), NID_commonName, commonName.data(),
44*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         static_cast<int>(commonName.size()));
45*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     if (length <= 0)
46*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     {
47*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         BMCWEB_LOG_DEBUG("TLS cannot get common name to create session");
48*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         return "";
49*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     }
50*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     commonName.resize(static_cast<size_t>(length));
51*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     return commonName;
52*4d7b5ddbSMalik Akbar Hashemi Rafsanjani }
53*4d7b5ddbSMalik Akbar Hashemi Rafsanjani 
isUPNMatch(std::string_view upn,std::string_view hostname)54*4d7b5ddbSMalik Akbar Hashemi Rafsanjani bool isUPNMatch(std::string_view upn, std::string_view hostname)
55*4d7b5ddbSMalik Akbar Hashemi Rafsanjani {
56*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     // UPN format: <username>@<domain> (e.g. user@domain.com)
57*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     // https://learn.microsoft.com/en-us/windows/win32/ad/naming-properties#userprincipalname
58*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     size_t upnDomainPos = upn.find('@');
59*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     if (upnDomainPos == std::string_view::npos)
60*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     {
61*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         return false;
62*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     }
63*4d7b5ddbSMalik Akbar Hashemi Rafsanjani 
64*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     // The hostname should match the domain part of the UPN
65*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     std::string_view upnDomain = upn.substr(upnDomainPos + 1);
66*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     while (true)
67*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     {
68*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         std::string_view upnDomainMatching = upnDomain;
69*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         size_t dotUPNPos = upnDomain.find_last_of('.');
70*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         if (dotUPNPos != std::string_view::npos)
71*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         {
72*4d7b5ddbSMalik Akbar Hashemi Rafsanjani             upnDomainMatching = upnDomain.substr(dotUPNPos + 1);
73*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         }
74*4d7b5ddbSMalik Akbar Hashemi Rafsanjani 
75*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         std::string_view hostDomainMatching = hostname;
76*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         size_t dotHostPos = hostname.find_last_of('.');
77*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         if (dotHostPos != std::string_view::npos)
78*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         {
79*4d7b5ddbSMalik Akbar Hashemi Rafsanjani             hostDomainMatching = hostname.substr(dotHostPos + 1);
80*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         }
81*4d7b5ddbSMalik Akbar Hashemi Rafsanjani 
82*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         if (upnDomainMatching != hostDomainMatching)
83*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         {
84*4d7b5ddbSMalik Akbar Hashemi Rafsanjani             return false;
85*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         }
86*4d7b5ddbSMalik Akbar Hashemi Rafsanjani 
87*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         if (dotUPNPos == std::string_view::npos)
88*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         {
89*4d7b5ddbSMalik Akbar Hashemi Rafsanjani             return true;
90*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         }
91*4d7b5ddbSMalik Akbar Hashemi Rafsanjani 
92*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         upnDomain = upnDomain.substr(0, dotUPNPos);
93*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         hostname = hostname.substr(0, dotHostPos);
94*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     }
95*4d7b5ddbSMalik Akbar Hashemi Rafsanjani }
96*4d7b5ddbSMalik Akbar Hashemi Rafsanjani 
getUPNFromCert(X509 * peerCert,std::string_view hostname)97*4d7b5ddbSMalik Akbar Hashemi Rafsanjani std::string getUPNFromCert(X509* peerCert, std::string_view hostname)
98*4d7b5ddbSMalik Akbar Hashemi Rafsanjani {
99*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     GENERAL_NAMES* gs = static_cast<GENERAL_NAMES*>(
100*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         X509_get_ext_d2i(peerCert, NID_subject_alt_name, nullptr, nullptr));
101*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     if (gs == nullptr)
102*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     {
103*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         return "";
104*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     }
105*4d7b5ddbSMalik Akbar Hashemi Rafsanjani 
106*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     std::string ret;
107*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     for (int i = 0; i < sk_GENERAL_NAME_num(gs); i++)
108*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     {
109*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         GENERAL_NAME* g = sk_GENERAL_NAME_value(gs, i);
110*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         if (g->type != GEN_OTHERNAME)
111*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         {
112*4d7b5ddbSMalik Akbar Hashemi Rafsanjani             continue;
113*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         }
114*4d7b5ddbSMalik Akbar Hashemi Rafsanjani 
115*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         // NOLINTBEGIN(cppcoreguidelines-pro-type-union-access)
116*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         int nid = OBJ_obj2nid(g->d.otherName->type_id);
117*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         if (nid != NID_ms_upn)
118*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         {
119*4d7b5ddbSMalik Akbar Hashemi Rafsanjani             continue;
120*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         }
121*4d7b5ddbSMalik Akbar Hashemi Rafsanjani 
122*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         int type = g->d.otherName->value->type;
123*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         if (type != V_ASN1_UTF8STRING)
124*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         {
125*4d7b5ddbSMalik Akbar Hashemi Rafsanjani             continue;
126*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         }
127*4d7b5ddbSMalik Akbar Hashemi Rafsanjani 
128*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         char* upnChar =
129*4d7b5ddbSMalik Akbar Hashemi Rafsanjani             std::bit_cast<char*>(g->d.otherName->value->value.utf8string->data);
130*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         unsigned int upnLen = static_cast<unsigned int>(
131*4d7b5ddbSMalik Akbar Hashemi Rafsanjani             g->d.otherName->value->value.utf8string->length);
132*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         // NOLINTEND(cppcoreguidelines-pro-type-union-access)
133*4d7b5ddbSMalik Akbar Hashemi Rafsanjani 
134*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         std::string upn = std::string(upnChar, upnLen);
135*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         if (!isUPNMatch(upn, hostname))
136*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         {
137*4d7b5ddbSMalik Akbar Hashemi Rafsanjani             continue;
138*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         }
139*4d7b5ddbSMalik Akbar Hashemi Rafsanjani 
140*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         size_t upnDomainPos = upn.find('@');
141*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         ret = upn.substr(0, upnDomainPos);
142*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         break;
143*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     }
144*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     GENERAL_NAMES_free(gs);
145*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     return ret;
146*4d7b5ddbSMalik Akbar Hashemi Rafsanjani }
147*4d7b5ddbSMalik Akbar Hashemi Rafsanjani 
getMetaUserNameFromCert(X509 * cert)148*4d7b5ddbSMalik Akbar Hashemi Rafsanjani std::string getMetaUserNameFromCert(X509* cert)
149*4d7b5ddbSMalik Akbar Hashemi Rafsanjani {
150*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     // Meta Inc. CommonName parsing
151*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     std::optional<std::string_view> sslUserMeta =
152*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         mtlsMetaParseSslUser(getCommonNameFromCert(cert));
153*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     if (!sslUserMeta)
154*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     {
155*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         return "";
156*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     }
157*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     return std::string{*sslUserMeta};
158*4d7b5ddbSMalik Akbar Hashemi Rafsanjani }
159*4d7b5ddbSMalik Akbar Hashemi Rafsanjani 
getUsernameFromCert(X509 * cert)160*4d7b5ddbSMalik Akbar Hashemi Rafsanjani std::string getUsernameFromCert(X509* cert)
161724985ffSEd Tanous {
162724985ffSEd Tanous     const persistent_data::AuthConfigMethods& authMethodsConfig =
163724985ffSEd Tanous         persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
164724985ffSEd Tanous     switch (authMethodsConfig.mTLSCommonNameParsingMode)
165724985ffSEd Tanous     {
166724985ffSEd Tanous         case persistent_data::MTLSCommonNameParseMode::Invalid:
167724985ffSEd Tanous         case persistent_data::MTLSCommonNameParseMode::Whole:
168724985ffSEd Tanous         {
169724985ffSEd Tanous             // Not yet supported
170724985ffSEd Tanous             return "";
171724985ffSEd Tanous         }
172*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         case persistent_data::MTLSCommonNameParseMode::UserPrincipalName:
173*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         {
174*4d7b5ddbSMalik Akbar Hashemi Rafsanjani             std::string hostname = getHostName();
175*4d7b5ddbSMalik Akbar Hashemi Rafsanjani             if (hostname.empty())
176*4d7b5ddbSMalik Akbar Hashemi Rafsanjani             {
177*4d7b5ddbSMalik Akbar Hashemi Rafsanjani                 BMCWEB_LOG_WARNING("Failed to get hostname");
178*4d7b5ddbSMalik Akbar Hashemi Rafsanjani                 return "";
179*4d7b5ddbSMalik Akbar Hashemi Rafsanjani             }
180*4d7b5ddbSMalik Akbar Hashemi Rafsanjani             return getUPNFromCert(cert, hostname);
181*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         }
182724985ffSEd Tanous         case persistent_data::MTLSCommonNameParseMode::CommonName:
183724985ffSEd Tanous         {
184*4d7b5ddbSMalik Akbar Hashemi Rafsanjani             return getCommonNameFromCert(cert);
185724985ffSEd Tanous         }
186724985ffSEd Tanous         case persistent_data::MTLSCommonNameParseMode::Meta:
187724985ffSEd Tanous         {
188*4d7b5ddbSMalik Akbar Hashemi Rafsanjani             if constexpr (BMCWEB_META_TLS_COMMON_NAME_PARSING)
189724985ffSEd Tanous             {
190*4d7b5ddbSMalik Akbar Hashemi Rafsanjani                 return getMetaUserNameFromCert(cert);
191724985ffSEd Tanous             }
192724985ffSEd Tanous         }
1934f467963SEd Tanous         default:
1944f467963SEd Tanous         {
195724985ffSEd Tanous             return "";
196724985ffSEd Tanous         }
1974f467963SEd Tanous     }
1984f467963SEd Tanous }
199724985ffSEd Tanous 
verifyMtlsUser(const boost::asio::ip::address & clientIp,boost::asio::ssl::verify_context & ctx)200504af5a0SPatrick Williams std::shared_ptr<persistent_data::UserSession> verifyMtlsUser(
201504af5a0SPatrick Williams     const boost::asio::ip::address& clientIp,
202724985ffSEd Tanous     boost::asio::ssl::verify_context& ctx)
203724985ffSEd Tanous {
204724985ffSEd Tanous     // do nothing if TLS is disabled
205724985ffSEd Tanous     if (!persistent_data::SessionStore::getInstance()
206724985ffSEd Tanous              .getAuthMethodsConfig()
207724985ffSEd Tanous              .tls)
208724985ffSEd Tanous     {
209724985ffSEd Tanous         BMCWEB_LOG_DEBUG("TLS auth_config is disabled");
210724985ffSEd Tanous         return nullptr;
211724985ffSEd Tanous     }
212724985ffSEd Tanous 
213724985ffSEd Tanous     X509_STORE_CTX* cts = ctx.native_handle();
214724985ffSEd Tanous     if (cts == nullptr)
215724985ffSEd Tanous     {
216724985ffSEd Tanous         BMCWEB_LOG_DEBUG("Cannot get native TLS handle.");
217724985ffSEd Tanous         return nullptr;
218724985ffSEd Tanous     }
219724985ffSEd Tanous 
220724985ffSEd Tanous     // Get certificate
221724985ffSEd Tanous     X509* peerCert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
222724985ffSEd Tanous     if (peerCert == nullptr)
223724985ffSEd Tanous     {
224724985ffSEd Tanous         BMCWEB_LOG_DEBUG("Cannot get current TLS certificate.");
225724985ffSEd Tanous         return nullptr;
226724985ffSEd Tanous     }
227724985ffSEd Tanous 
228724985ffSEd Tanous     // Check if certificate is OK
229724985ffSEd Tanous     int ctxError = X509_STORE_CTX_get_error(cts);
230724985ffSEd Tanous     if (ctxError != X509_V_OK)
231724985ffSEd Tanous     {
232724985ffSEd Tanous         BMCWEB_LOG_INFO("Last TLS error is: {}", ctxError);
233724985ffSEd Tanous         return nullptr;
234724985ffSEd Tanous     }
235724985ffSEd Tanous 
236724985ffSEd Tanous     // Check that we have reached final certificate in chain
237724985ffSEd Tanous     int32_t depth = X509_STORE_CTX_get_error_depth(cts);
238724985ffSEd Tanous     if (depth != 0)
239724985ffSEd Tanous     {
240724985ffSEd Tanous         BMCWEB_LOG_DEBUG(
241724985ffSEd Tanous             "Certificate verification in progress (depth {}), waiting to reach final depth",
242724985ffSEd Tanous             depth);
243724985ffSEd Tanous         return nullptr;
244724985ffSEd Tanous     }
245724985ffSEd Tanous 
246724985ffSEd Tanous     BMCWEB_LOG_DEBUG("Certificate verification of final depth");
247724985ffSEd Tanous 
248724985ffSEd Tanous     if (X509_check_purpose(peerCert, X509_PURPOSE_SSL_CLIENT, 0) != 1)
249724985ffSEd Tanous     {
250724985ffSEd Tanous         BMCWEB_LOG_DEBUG(
251724985ffSEd Tanous             "Chain does not allow certificate to be used for SSL client authentication");
252724985ffSEd Tanous         return nullptr;
253724985ffSEd Tanous     }
254724985ffSEd Tanous 
255*4d7b5ddbSMalik Akbar Hashemi Rafsanjani     std::string sslUser = getUsernameFromCert(peerCert);
256724985ffSEd Tanous     if (sslUser.empty())
257724985ffSEd Tanous     {
258*4d7b5ddbSMalik Akbar Hashemi Rafsanjani         BMCWEB_LOG_WARNING("Failed to get user from peer certificate");
259724985ffSEd Tanous         return nullptr;
260724985ffSEd Tanous     }
261724985ffSEd Tanous 
262724985ffSEd Tanous     std::string unsupportedClientId;
263724985ffSEd Tanous     return persistent_data::SessionStore::getInstance().generateUserSession(
264724985ffSEd Tanous         sslUser, clientIp, unsupportedClientId,
265724985ffSEd Tanous         persistent_data::SessionType::MutualTLS);
266724985ffSEd Tanous }
267