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