blob: b091ed2c13bec0bfd3aba0400ac559d0336a6ec1 [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 "identity.hpp"
6#include "mutual_tls_private.hpp"
Ed Tanous41fe81c2024-09-02 15:08:41 -07007#include "sessions.hpp"
8
Malik Akbar Hashemi Rafsanjani4d7b5dd2025-02-26 13:14:30 -08009#include <bit>
Ed Tanous41fe81c2024-09-02 15:08:41 -070010#include <cstddef>
11#include <cstdint>
12#include <optional>
13#include <string>
14
Ed Tanous724985f2024-06-05 09:19:06 -070015extern "C"
16{
Malik Akbar Hashemi Rafsanjani4d7b5dd2025-02-26 13:14:30 -080017#include <openssl/asn1.h>
Ed Tanous41fe81c2024-09-02 15:08:41 -070018#include <openssl/obj_mac.h>
Malik Akbar Hashemi Rafsanjani4d7b5dd2025-02-26 13:14:30 -080019#include <openssl/objects.h>
Ed Tanous41fe81c2024-09-02 15:08:41 -070020#include <openssl/types.h>
21#include <openssl/x509.h>
Ed Tanous724985f2024-06-05 09:19:06 -070022#include <openssl/x509_vfy.h>
Ed Tanous41fe81c2024-09-02 15:08:41 -070023#include <openssl/x509v3.h>
Ed Tanous724985f2024-06-05 09:19:06 -070024}
25
26#include "logging.hpp"
Ed Tanous724985f2024-06-05 09:19:06 -070027
28#include <boost/asio/ip/address.hpp>
29#include <boost/asio/ssl/verify_context.hpp>
30
31#include <memory>
32#include <string_view>
33
Malik Akbar Hashemi Rafsanjani4d7b5dd2025-02-26 13:14:30 -080034std::string getCommonNameFromCert(X509* cert)
35{
36 std::string commonName;
37 // Extract username contained in CommonName
38 commonName.resize(256, '\0');
39 int length = X509_NAME_get_text_by_NID(
40 X509_get_subject_name(cert), NID_commonName, commonName.data(),
41 static_cast<int>(commonName.size()));
42 if (length <= 0)
43 {
44 BMCWEB_LOG_DEBUG("TLS cannot get common name to create session");
45 return "";
46 }
47 commonName.resize(static_cast<size_t>(length));
48 return commonName;
49}
50
51bool isUPNMatch(std::string_view upn, std::string_view hostname)
52{
53 // UPN format: <username>@<domain> (e.g. user@domain.com)
54 // https://learn.microsoft.com/en-us/windows/win32/ad/naming-properties#userprincipalname
55 size_t upnDomainPos = upn.find('@');
56 if (upnDomainPos == std::string_view::npos)
57 {
58 return false;
59 }
60
61 // The hostname should match the domain part of the UPN
62 std::string_view upnDomain = upn.substr(upnDomainPos + 1);
63 while (true)
64 {
65 std::string_view upnDomainMatching = upnDomain;
66 size_t dotUPNPos = upnDomain.find_last_of('.');
67 if (dotUPNPos != std::string_view::npos)
68 {
69 upnDomainMatching = upnDomain.substr(dotUPNPos + 1);
70 }
71
72 std::string_view hostDomainMatching = hostname;
73 size_t dotHostPos = hostname.find_last_of('.');
74 if (dotHostPos != std::string_view::npos)
75 {
76 hostDomainMatching = hostname.substr(dotHostPos + 1);
77 }
78
79 if (upnDomainMatching != hostDomainMatching)
80 {
81 return false;
82 }
83
84 if (dotUPNPos == std::string_view::npos)
85 {
86 return true;
87 }
88
89 upnDomain = upnDomain.substr(0, dotUPNPos);
90 hostname = hostname.substr(0, dotHostPos);
91 }
92}
93
94std::string getUPNFromCert(X509* peerCert, std::string_view hostname)
95{
96 GENERAL_NAMES* gs = static_cast<GENERAL_NAMES*>(
97 X509_get_ext_d2i(peerCert, NID_subject_alt_name, nullptr, nullptr));
98 if (gs == nullptr)
99 {
100 return "";
101 }
102
103 std::string ret;
104 for (int i = 0; i < sk_GENERAL_NAME_num(gs); i++)
105 {
106 GENERAL_NAME* g = sk_GENERAL_NAME_value(gs, i);
107 if (g->type != GEN_OTHERNAME)
108 {
109 continue;
110 }
111
112 // NOLINTBEGIN(cppcoreguidelines-pro-type-union-access)
113 int nid = OBJ_obj2nid(g->d.otherName->type_id);
114 if (nid != NID_ms_upn)
115 {
116 continue;
117 }
118
119 int type = g->d.otherName->value->type;
120 if (type != V_ASN1_UTF8STRING)
121 {
122 continue;
123 }
124
125 char* upnChar =
126 std::bit_cast<char*>(g->d.otherName->value->value.utf8string->data);
127 unsigned int upnLen = static_cast<unsigned int>(
128 g->d.otherName->value->value.utf8string->length);
129 // NOLINTEND(cppcoreguidelines-pro-type-union-access)
130
131 std::string upn = std::string(upnChar, upnLen);
132 if (!isUPNMatch(upn, hostname))
133 {
134 continue;
135 }
136
137 size_t upnDomainPos = upn.find('@');
138 ret = upn.substr(0, upnDomainPos);
139 break;
140 }
141 GENERAL_NAMES_free(gs);
142 return ret;
143}
144
Malik Akbar Hashemi Rafsanjani4d7b5dd2025-02-26 13:14:30 -0800145std::string getUsernameFromCert(X509* cert)
Ed Tanous724985f2024-06-05 09:19:06 -0700146{
147 const persistent_data::AuthConfigMethods& authMethodsConfig =
148 persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
149 switch (authMethodsConfig.mTLSCommonNameParsingMode)
150 {
151 case persistent_data::MTLSCommonNameParseMode::Invalid:
152 case persistent_data::MTLSCommonNameParseMode::Whole:
Ed Tanous724985f2024-06-05 09:19:06 -0700153 {
154 // Not yet supported
155 return "";
156 }
Malik Akbar Hashemi Rafsanjani4d7b5dd2025-02-26 13:14:30 -0800157 case persistent_data::MTLSCommonNameParseMode::UserPrincipalName:
158 {
159 std::string hostname = getHostName();
160 if (hostname.empty())
161 {
162 BMCWEB_LOG_WARNING("Failed to get hostname");
163 return "";
164 }
165 return getUPNFromCert(cert, hostname);
166 }
Ed Tanous724985f2024-06-05 09:19:06 -0700167 case persistent_data::MTLSCommonNameParseMode::CommonName:
168 {
Malik Akbar Hashemi Rafsanjani4d7b5dd2025-02-26 13:14:30 -0800169 return getCommonNameFromCert(cert);
Ed Tanous724985f2024-06-05 09:19:06 -0700170 }
Ed Tanous4f467962024-08-06 10:14:26 -0700171 default:
172 {
173 return "";
174 }
Ed Tanous724985f2024-06-05 09:19:06 -0700175 }
Ed Tanous724985f2024-06-05 09:19:06 -0700176}
177
Patrick Williams504af5a2025-02-03 14:29:03 -0500178std::shared_ptr<persistent_data::UserSession> verifyMtlsUser(
179 const boost::asio::ip::address& clientIp,
180 boost::asio::ssl::verify_context& ctx)
Ed Tanous724985f2024-06-05 09:19:06 -0700181{
182 // do nothing if TLS is disabled
183 if (!persistent_data::SessionStore::getInstance()
184 .getAuthMethodsConfig()
185 .tls)
186 {
187 BMCWEB_LOG_DEBUG("TLS auth_config is disabled");
188 return nullptr;
189 }
190
191 X509_STORE_CTX* cts = ctx.native_handle();
192 if (cts == nullptr)
193 {
194 BMCWEB_LOG_DEBUG("Cannot get native TLS handle.");
195 return nullptr;
196 }
197
198 // Get certificate
199 X509* peerCert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
200 if (peerCert == nullptr)
201 {
202 BMCWEB_LOG_DEBUG("Cannot get current TLS certificate.");
203 return nullptr;
204 }
205
206 // Check if certificate is OK
207 int ctxError = X509_STORE_CTX_get_error(cts);
208 if (ctxError != X509_V_OK)
209 {
210 BMCWEB_LOG_INFO("Last TLS error is: {}", ctxError);
211 return nullptr;
212 }
213
214 // Check that we have reached final certificate in chain
215 int32_t depth = X509_STORE_CTX_get_error_depth(cts);
216 if (depth != 0)
217 {
218 BMCWEB_LOG_DEBUG(
219 "Certificate verification in progress (depth {}), waiting to reach final depth",
220 depth);
221 return nullptr;
222 }
223
224 BMCWEB_LOG_DEBUG("Certificate verification of final depth");
225
226 if (X509_check_purpose(peerCert, X509_PURPOSE_SSL_CLIENT, 0) != 1)
227 {
228 BMCWEB_LOG_DEBUG(
229 "Chain does not allow certificate to be used for SSL client authentication");
230 return nullptr;
231 }
232
Malik Akbar Hashemi Rafsanjani4d7b5dd2025-02-26 13:14:30 -0800233 std::string sslUser = getUsernameFromCert(peerCert);
Ed Tanous724985f2024-06-05 09:19:06 -0700234 if (sslUser.empty())
235 {
Malik Akbar Hashemi Rafsanjani4d7b5dd2025-02-26 13:14:30 -0800236 BMCWEB_LOG_WARNING("Failed to get user from peer certificate");
Ed Tanous724985f2024-06-05 09:19:06 -0700237 return nullptr;
238 }
239
240 std::string unsupportedClientId;
241 return persistent_data::SessionStore::getInstance().generateUserSession(
242 sslUser, clientIp, unsupportedClientId,
243 persistent_data::SessionType::MutualTLS);
244}