xref: /openbmc/bmcweb/http/mutual_tls.hpp (revision a8d8f9d8)
1 #pragma once
2 
3 #include "logging.hpp"
4 #include "persistent_data.hpp"
5 
6 #include <openssl/crypto.h>
7 #include <openssl/ssl.h>
8 
9 #include <boost/asio/ip/address.hpp>
10 #include <boost/asio/ssl/verify_context.hpp>
11 
12 #include <memory>
13 
14 inline std::shared_ptr<persistent_data::UserSession>
15     verifyMtlsUser(const boost::asio::ip::address& clientIp,
16                    boost::asio::ssl::verify_context& ctx)
17 {
18     // do nothing if TLS is disabled
19     if (!persistent_data::SessionStore::getInstance()
20              .getAuthMethodsConfig()
21              .tls)
22     {
23         BMCWEB_LOG_DEBUG << "TLS auth_config is disabled";
24         return nullptr;
25     }
26 
27     X509_STORE_CTX* cts = ctx.native_handle();
28     if (cts == nullptr)
29     {
30         BMCWEB_LOG_DEBUG << "Cannot get native TLS handle.";
31         return nullptr;
32     }
33 
34     // Get certificate
35     X509* peerCert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
36     if (peerCert == nullptr)
37     {
38         BMCWEB_LOG_DEBUG << "Cannot get current TLS certificate.";
39         return nullptr;
40     }
41 
42     // Check if certificate is OK
43     int ctxError = X509_STORE_CTX_get_error(cts);
44     if (ctxError != X509_V_OK)
45     {
46         BMCWEB_LOG_INFO << "Last TLS error is: " << ctxError;
47         return nullptr;
48     }
49     // Check that we have reached final certificate in chain
50     int32_t depth = X509_STORE_CTX_get_error_depth(cts);
51     if (depth != 0)
52 
53     {
54         BMCWEB_LOG_DEBUG << "Certificate verification in progress (depth "
55                          << depth << "), waiting to reach final depth";
56         return nullptr;
57     }
58 
59     BMCWEB_LOG_DEBUG << "Certificate verification of final depth";
60 
61     // Verify KeyUsage
62     bool isKeyUsageDigitalSignature = false;
63     bool isKeyUsageKeyAgreement = false;
64 
65     ASN1_BIT_STRING* usage = static_cast<ASN1_BIT_STRING*>(
66         X509_get_ext_d2i(peerCert, NID_key_usage, nullptr, nullptr));
67 
68     if (usage == nullptr)
69     {
70         BMCWEB_LOG_DEBUG << "TLS usage is null";
71         return nullptr;
72     }
73 
74     for (int i = 0; i < usage->length; i++)
75     {
76         // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
77         unsigned char usageChar = usage->data[i];
78         if (KU_DIGITAL_SIGNATURE & usageChar)
79         {
80             isKeyUsageDigitalSignature = true;
81         }
82         if (KU_KEY_AGREEMENT & usageChar)
83         {
84             isKeyUsageKeyAgreement = true;
85         }
86     }
87     ASN1_BIT_STRING_free(usage);
88 
89     if (!isKeyUsageDigitalSignature || !isKeyUsageKeyAgreement)
90     {
91         BMCWEB_LOG_DEBUG << "Certificate ExtendedKeyUsage does "
92                             "not allow provided certificate to "
93                             "be used for user authentication";
94         return nullptr;
95     }
96 
97     // Determine that ExtendedKeyUsage includes Client Auth
98 
99     stack_st_ASN1_OBJECT* extUsage = static_cast<stack_st_ASN1_OBJECT*>(
100         X509_get_ext_d2i(peerCert, NID_ext_key_usage, nullptr, nullptr));
101 
102     if (extUsage == nullptr)
103     {
104         BMCWEB_LOG_DEBUG << "TLS extUsage is null";
105         return nullptr;
106     }
107 
108     bool isExKeyUsageClientAuth = false;
109     for (int i = 0; i < sk_ASN1_OBJECT_num(extUsage); i++)
110     {
111         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
112         int nid = OBJ_obj2nid(sk_ASN1_OBJECT_value(extUsage, i));
113         if (NID_client_auth == nid)
114         {
115             isExKeyUsageClientAuth = true;
116             break;
117         }
118     }
119     sk_ASN1_OBJECT_free(extUsage);
120 
121     // Certificate has to have proper key usages set
122     if (!isExKeyUsageClientAuth)
123     {
124         BMCWEB_LOG_DEBUG << "Certificate ExtendedKeyUsage does "
125                             "not allow provided certificate to "
126                             "be used for user authentication";
127         return nullptr;
128     }
129     std::string sslUser;
130     // Extract username contained in CommonName
131     sslUser.resize(256, '\0');
132 
133     int status = X509_NAME_get_text_by_NID(X509_get_subject_name(peerCert),
134                                            NID_commonName, sslUser.data(),
135                                            static_cast<int>(sslUser.size()));
136 
137     if (status == -1)
138     {
139         BMCWEB_LOG_DEBUG << "TLS cannot get username to create session";
140         return nullptr;
141     }
142 
143     size_t lastChar = sslUser.find('\0');
144     if (lastChar == std::string::npos || lastChar == 0)
145     {
146         BMCWEB_LOG_DEBUG << "Invalid TLS user name";
147         return nullptr;
148     }
149     sslUser.resize(lastChar);
150     std::string unsupportedClientId;
151     return persistent_data::SessionStore::getInstance().generateUserSession(
152         sslUser, clientIp, unsupportedClientId,
153         persistent_data::PersistenceType::TIMEOUT);
154 }
155