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