blob: 209c929928597cc1471ee5d1879049faf967551b [file] [log] [blame]
Ed Tanous40e9b922024-09-10 13:50:16 -07001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright OpenBMC Authors
Ed Tanous724985f2024-06-05 09:19:06 -07003#include "mutual_tls.hpp"
4
Malik Akbar Hashemi Rafsanjani4d7b5dd2025-02-26 13:14:30 -08005#include "bmcweb_config.h"
6
7#include "identity.hpp"
8#include "mutual_tls_private.hpp"
Ed Tanous41fe81c2024-09-02 15:08:41 -07009#include "sessions.hpp"
10
Malik Akbar Hashemi Rafsanjani4d7b5dd2025-02-26 13:14:30 -080011#include <bit>
Ed Tanous41fe81c2024-09-02 15:08:41 -070012#include <cstddef>
13#include <cstdint>
14#include <optional>
15#include <string>
16
Ed Tanous724985f2024-06-05 09:19:06 -070017extern "C"
18{
Malik Akbar Hashemi Rafsanjani4d7b5dd2025-02-26 13:14:30 -080019#include <openssl/asn1.h>
Ed Tanous41fe81c2024-09-02 15:08:41 -070020#include <openssl/obj_mac.h>
Malik Akbar Hashemi Rafsanjani4d7b5dd2025-02-26 13:14:30 -080021#include <openssl/objects.h>
Ed Tanous41fe81c2024-09-02 15:08:41 -070022#include <openssl/types.h>
23#include <openssl/x509.h>
Ed Tanous724985f2024-06-05 09:19:06 -070024#include <openssl/x509_vfy.h>
Ed Tanous41fe81c2024-09-02 15:08:41 -070025#include <openssl/x509v3.h>
Ed Tanous724985f2024-06-05 09:19:06 -070026}
27
28#include "logging.hpp"
29#include "mutual_tls_meta.hpp"
Ed Tanous724985f2024-06-05 09:19:06 -070030
31#include <boost/asio/ip/address.hpp>
32#include <boost/asio/ssl/verify_context.hpp>
33
34#include <memory>
35#include <string_view>
36
Malik Akbar Hashemi Rafsanjani4d7b5dd2025-02-26 13:14:30 -080037std::string getCommonNameFromCert(X509* cert)
38{
39 std::string commonName;
40 // Extract username contained in CommonName
41 commonName.resize(256, '\0');
42 int length = X509_NAME_get_text_by_NID(
43 X509_get_subject_name(cert), NID_commonName, commonName.data(),
44 static_cast<int>(commonName.size()));
45 if (length <= 0)
46 {
47 BMCWEB_LOG_DEBUG("TLS cannot get common name to create session");
48 return "";
49 }
50 commonName.resize(static_cast<size_t>(length));
51 return commonName;
52}
53
54bool isUPNMatch(std::string_view upn, std::string_view hostname)
55{
56 // UPN format: <username>@<domain> (e.g. user@domain.com)
57 // https://learn.microsoft.com/en-us/windows/win32/ad/naming-properties#userprincipalname
58 size_t upnDomainPos = upn.find('@');
59 if (upnDomainPos == std::string_view::npos)
60 {
61 return false;
62 }
63
64 // The hostname should match the domain part of the UPN
65 std::string_view upnDomain = upn.substr(upnDomainPos + 1);
66 while (true)
67 {
68 std::string_view upnDomainMatching = upnDomain;
69 size_t dotUPNPos = upnDomain.find_last_of('.');
70 if (dotUPNPos != std::string_view::npos)
71 {
72 upnDomainMatching = upnDomain.substr(dotUPNPos + 1);
73 }
74
75 std::string_view hostDomainMatching = hostname;
76 size_t dotHostPos = hostname.find_last_of('.');
77 if (dotHostPos != std::string_view::npos)
78 {
79 hostDomainMatching = hostname.substr(dotHostPos + 1);
80 }
81
82 if (upnDomainMatching != hostDomainMatching)
83 {
84 return false;
85 }
86
87 if (dotUPNPos == std::string_view::npos)
88 {
89 return true;
90 }
91
92 upnDomain = upnDomain.substr(0, dotUPNPos);
93 hostname = hostname.substr(0, dotHostPos);
94 }
95}
96
97std::string getUPNFromCert(X509* peerCert, std::string_view hostname)
98{
99 GENERAL_NAMES* gs = static_cast<GENERAL_NAMES*>(
100 X509_get_ext_d2i(peerCert, NID_subject_alt_name, nullptr, nullptr));
101 if (gs == nullptr)
102 {
103 return "";
104 }
105
106 std::string ret;
107 for (int i = 0; i < sk_GENERAL_NAME_num(gs); i++)
108 {
109 GENERAL_NAME* g = sk_GENERAL_NAME_value(gs, i);
110 if (g->type != GEN_OTHERNAME)
111 {
112 continue;
113 }
114
115 // NOLINTBEGIN(cppcoreguidelines-pro-type-union-access)
116 int nid = OBJ_obj2nid(g->d.otherName->type_id);
117 if (nid != NID_ms_upn)
118 {
119 continue;
120 }
121
122 int type = g->d.otherName->value->type;
123 if (type != V_ASN1_UTF8STRING)
124 {
125 continue;
126 }
127
128 char* upnChar =
129 std::bit_cast<char*>(g->d.otherName->value->value.utf8string->data);
130 unsigned int upnLen = static_cast<unsigned int>(
131 g->d.otherName->value->value.utf8string->length);
132 // NOLINTEND(cppcoreguidelines-pro-type-union-access)
133
134 std::string upn = std::string(upnChar, upnLen);
135 if (!isUPNMatch(upn, hostname))
136 {
137 continue;
138 }
139
140 size_t upnDomainPos = upn.find('@');
141 ret = upn.substr(0, upnDomainPos);
142 break;
143 }
144 GENERAL_NAMES_free(gs);
145 return ret;
146}
147
148std::string getMetaUserNameFromCert(X509* cert)
149{
150 // Meta Inc. CommonName parsing
151 std::optional<std::string_view> sslUserMeta =
152 mtlsMetaParseSslUser(getCommonNameFromCert(cert));
153 if (!sslUserMeta)
154 {
155 return "";
156 }
157 return std::string{*sslUserMeta};
158}
159
160std::string getUsernameFromCert(X509* cert)
Ed Tanous724985f2024-06-05 09:19:06 -0700161{
162 const persistent_data::AuthConfigMethods& authMethodsConfig =
163 persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
164 switch (authMethodsConfig.mTLSCommonNameParsingMode)
165 {
166 case persistent_data::MTLSCommonNameParseMode::Invalid:
167 case persistent_data::MTLSCommonNameParseMode::Whole:
Ed Tanous724985f2024-06-05 09:19:06 -0700168 {
169 // Not yet supported
170 return "";
171 }
Malik Akbar Hashemi Rafsanjani4d7b5dd2025-02-26 13:14:30 -0800172 case persistent_data::MTLSCommonNameParseMode::UserPrincipalName:
173 {
174 std::string hostname = getHostName();
175 if (hostname.empty())
176 {
177 BMCWEB_LOG_WARNING("Failed to get hostname");
178 return "";
179 }
180 return getUPNFromCert(cert, hostname);
181 }
Ed Tanous724985f2024-06-05 09:19:06 -0700182 case persistent_data::MTLSCommonNameParseMode::CommonName:
183 {
Malik Akbar Hashemi Rafsanjani4d7b5dd2025-02-26 13:14:30 -0800184 return getCommonNameFromCert(cert);
Ed Tanous724985f2024-06-05 09:19:06 -0700185 }
186 case persistent_data::MTLSCommonNameParseMode::Meta:
187 {
Malik Akbar Hashemi Rafsanjani4d7b5dd2025-02-26 13:14:30 -0800188 if constexpr (BMCWEB_META_TLS_COMMON_NAME_PARSING)
Ed Tanous724985f2024-06-05 09:19:06 -0700189 {
Malik Akbar Hashemi Rafsanjani4d7b5dd2025-02-26 13:14:30 -0800190 return getMetaUserNameFromCert(cert);
Ed Tanous724985f2024-06-05 09:19:06 -0700191 }
Ed Tanous724985f2024-06-05 09:19:06 -0700192 }
Ed Tanous4f467962024-08-06 10:14:26 -0700193 default:
194 {
195 return "";
196 }
Ed Tanous724985f2024-06-05 09:19:06 -0700197 }
Ed Tanous724985f2024-06-05 09:19:06 -0700198}
199
Patrick Williams504af5a2025-02-03 14:29:03 -0500200std::shared_ptr<persistent_data::UserSession> verifyMtlsUser(
201 const boost::asio::ip::address& clientIp,
202 boost::asio::ssl::verify_context& ctx)
Ed Tanous724985f2024-06-05 09:19:06 -0700203{
204 // do nothing if TLS is disabled
205 if (!persistent_data::SessionStore::getInstance()
206 .getAuthMethodsConfig()
207 .tls)
208 {
209 BMCWEB_LOG_DEBUG("TLS auth_config is disabled");
210 return nullptr;
211 }
212
213 X509_STORE_CTX* cts = ctx.native_handle();
214 if (cts == nullptr)
215 {
216 BMCWEB_LOG_DEBUG("Cannot get native TLS handle.");
217 return nullptr;
218 }
219
220 // Get certificate
221 X509* peerCert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
222 if (peerCert == nullptr)
223 {
224 BMCWEB_LOG_DEBUG("Cannot get current TLS certificate.");
225 return nullptr;
226 }
227
228 // Check if certificate is OK
229 int ctxError = X509_STORE_CTX_get_error(cts);
230 if (ctxError != X509_V_OK)
231 {
232 BMCWEB_LOG_INFO("Last TLS error is: {}", ctxError);
233 return nullptr;
234 }
235
236 // Check that we have reached final certificate in chain
237 int32_t depth = X509_STORE_CTX_get_error_depth(cts);
238 if (depth != 0)
239 {
240 BMCWEB_LOG_DEBUG(
241 "Certificate verification in progress (depth {}), waiting to reach final depth",
242 depth);
243 return nullptr;
244 }
245
246 BMCWEB_LOG_DEBUG("Certificate verification of final depth");
247
248 if (X509_check_purpose(peerCert, X509_PURPOSE_SSL_CLIENT, 0) != 1)
249 {
250 BMCWEB_LOG_DEBUG(
251 "Chain does not allow certificate to be used for SSL client authentication");
252 return nullptr;
253 }
254
Malik Akbar Hashemi Rafsanjani4d7b5dd2025-02-26 13:14:30 -0800255 std::string sslUser = getUsernameFromCert(peerCert);
Ed Tanous724985f2024-06-05 09:19:06 -0700256 if (sslUser.empty())
257 {
Malik Akbar Hashemi Rafsanjani4d7b5dd2025-02-26 13:14:30 -0800258 BMCWEB_LOG_WARNING("Failed to get user from peer certificate");
Ed Tanous724985f2024-06-05 09:19:06 -0700259 return nullptr;
260 }
261
262 std::string unsupportedClientId;
263 return persistent_data::SessionStore::getInstance().generateUserSession(
264 sslUser, clientIp, unsupportedClientId,
265 persistent_data::SessionType::MutualTLS);
266}