xref: /openbmc/phosphor-certificate-manager/x509_utils.cpp (revision 5d4f7932b3456f34745d344af5f5e2512557b772)
13c478144SLei YU #include "config.h"
23c478144SLei YU 
3e869bb63SNan Zhou #include "x509_utils.hpp"
4e869bb63SNan Zhou 
5e869bb63SNan Zhou #include <openssl/asn1.h>
6e869bb63SNan Zhou #include <openssl/bio.h>
7e869bb63SNan Zhou #include <openssl/err.h>
8e869bb63SNan Zhou #include <openssl/evp.h>
9e869bb63SNan Zhou #include <openssl/pem.h>
10e869bb63SNan Zhou #include <openssl/ssl3.h>
11e869bb63SNan Zhou #include <openssl/x509_vfy.h>
12e869bb63SNan Zhou 
13e869bb63SNan Zhou #include <phosphor-logging/elog-errors.hpp>
14e869bb63SNan Zhou #include <phosphor-logging/elog.hpp>
15f2646271SRavi Teja #include <phosphor-logging/lg2.hpp>
16e869bb63SNan Zhou #include <xyz/openbmc_project/Certs/error.hpp>
17e869bb63SNan Zhou #include <xyz/openbmc_project/Common/error.hpp>
18e869bb63SNan Zhou 
19223e4604SPatrick Williams #include <cstdio>
20223e4604SPatrick Williams #include <ctime>
21223e4604SPatrick Williams #include <exception>
22223e4604SPatrick Williams #include <memory>
23223e4604SPatrick Williams 
24e869bb63SNan Zhou namespace phosphor::certs
25e869bb63SNan Zhou {
26e869bb63SNan Zhou 
27e869bb63SNan Zhou namespace
28e869bb63SNan Zhou {
29e869bb63SNan Zhou 
30e869bb63SNan Zhou using ::phosphor::logging::elog;
31e869bb63SNan Zhou using ::sdbusplus::xyz::openbmc_project::Certs::Error::InvalidCertificate;
32e869bb63SNan Zhou using ::sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
33e869bb63SNan Zhou using Reason = ::phosphor::logging::xyz::openbmc_project::Certs::
34e869bb63SNan Zhou     InvalidCertificate::REASON;
35e869bb63SNan Zhou 
36e869bb63SNan Zhou // RAII support for openSSL functions.
37e869bb63SNan Zhou using X509StorePtr = std::unique_ptr<X509_STORE, decltype(&::X509_STORE_free)>;
38e869bb63SNan Zhou using X509StoreCtxPtr =
39e869bb63SNan Zhou     std::unique_ptr<X509_STORE_CTX, decltype(&::X509_STORE_CTX_free)>;
40e869bb63SNan Zhou using X509Ptr = std::unique_ptr<X509, decltype(&::X509_free)>;
41e869bb63SNan Zhou using BIOMemPtr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
42e869bb63SNan Zhou using ASN1TimePtr = std::unique_ptr<ASN1_TIME, decltype(&ASN1_STRING_free)>;
43e869bb63SNan Zhou using SSLCtxPtr = std::unique_ptr<SSL_CTX, decltype(&::SSL_CTX_free)>;
44e869bb63SNan Zhou 
45e869bb63SNan Zhou // Trust chain related errors.`
isTrustChainError(int error)46e869bb63SNan Zhou constexpr bool isTrustChainError(int error)
47e869bb63SNan Zhou {
48e869bb63SNan Zhou     return error == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
49e869bb63SNan Zhou            error == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN ||
50e869bb63SNan Zhou            error == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY ||
51e869bb63SNan Zhou            error == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT ||
52e869bb63SNan Zhou            error == X509_V_ERR_CERT_UNTRUSTED ||
53e869bb63SNan Zhou            error == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE;
54e869bb63SNan Zhou }
55e869bb63SNan Zhou } // namespace
56e869bb63SNan Zhou 
getX509Store(const std::string & certSrcPath)57e869bb63SNan Zhou X509StorePtr getX509Store(const std::string& certSrcPath)
58e869bb63SNan Zhou {
59e869bb63SNan Zhou     // Create an empty X509_STORE structure for certificate validation.
60e869bb63SNan Zhou     X509StorePtr x509Store(X509_STORE_new(), &X509_STORE_free);
61e869bb63SNan Zhou     if (!x509Store)
62e869bb63SNan Zhou     {
63f2646271SRavi Teja         lg2::error("Error occurred during X509_STORE_new call");
64e869bb63SNan Zhou         elog<InternalFailure>();
65e869bb63SNan Zhou     }
66e869bb63SNan Zhou 
67e869bb63SNan Zhou     OpenSSL_add_all_algorithms();
68e869bb63SNan Zhou 
69e869bb63SNan Zhou     // ADD Certificate Lookup method.
70e869bb63SNan Zhou     // lookup will be cleaned up automatically when the holding Store goes away.
71e869bb63SNan Zhou     auto lookup = X509_STORE_add_lookup(x509Store.get(), X509_LOOKUP_file());
72e869bb63SNan Zhou 
73e869bb63SNan Zhou     if (!lookup)
74e869bb63SNan Zhou     {
75f2646271SRavi Teja         lg2::error("Error occurred during X509_STORE_add_lookup call");
76e869bb63SNan Zhou         elog<InternalFailure>();
77e869bb63SNan Zhou     }
78e869bb63SNan Zhou     // Load the Certificate file into X509 Store.
79e869bb63SNan Zhou     if (int errCode = X509_LOOKUP_load_file(lookup, certSrcPath.c_str(),
80e869bb63SNan Zhou                                             X509_FILETYPE_PEM);
81e869bb63SNan Zhou         errCode != 1)
82e869bb63SNan Zhou     {
83f2646271SRavi Teja         lg2::error(
84f2646271SRavi Teja             "Error occurred during X509_LOOKUP_load_file call, FILE:{FILE}",
85f2646271SRavi Teja             "FILE", certSrcPath);
86e869bb63SNan Zhou         elog<InvalidCertificate>(Reason("Invalid certificate file format"));
87e869bb63SNan Zhou     }
88e869bb63SNan Zhou     return x509Store;
89e869bb63SNan Zhou }
90e869bb63SNan Zhou 
loadCert(const std::string & filePath)91e869bb63SNan Zhou X509Ptr loadCert(const std::string& filePath)
92e869bb63SNan Zhou {
93e869bb63SNan Zhou     // Read Certificate file
94e869bb63SNan Zhou     X509Ptr cert(X509_new(), ::X509_free);
95e869bb63SNan Zhou     if (!cert)
96e869bb63SNan Zhou     {
97f2646271SRavi Teja         lg2::error(
98f2646271SRavi Teja             "Error occurred during X509_new call, FILE:{FILE}, ERRCODE:{ERRCODE}",
99f2646271SRavi Teja             "FILE", filePath, "ERRCODE", ERR_get_error());
100e869bb63SNan Zhou         elog<InternalFailure>();
101e869bb63SNan Zhou     }
102e869bb63SNan Zhou 
103e869bb63SNan Zhou     BIOMemPtr bioCert(BIO_new_file(filePath.c_str(), "rb"), ::BIO_free);
104e869bb63SNan Zhou     if (!bioCert)
105e869bb63SNan Zhou     {
106f2646271SRavi Teja         lg2::error("Error occurred during BIO_new_file call, FILE:{FILE}",
107f2646271SRavi Teja                    "FILE", filePath);
108e869bb63SNan Zhou         elog<InternalFailure>();
109e869bb63SNan Zhou     }
110e869bb63SNan Zhou 
111e869bb63SNan Zhou     X509* x509 = cert.get();
112e869bb63SNan Zhou     if (!PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr))
113e869bb63SNan Zhou     {
114f2646271SRavi Teja         lg2::error("Error occurred during PEM_read_bio_X509 call, FILE:{FILE}",
115f2646271SRavi Teja                    "FILE", filePath);
116e869bb63SNan Zhou         elog<InternalFailure>();
117e869bb63SNan Zhou     }
118e869bb63SNan Zhou     return cert;
119e869bb63SNan Zhou }
120e869bb63SNan Zhou 
121e869bb63SNan Zhou // Checks that notBefore is not earlier than the unix epoch given that
122e869bb63SNan Zhou // the corresponding DBus interface is uint64_t.
validateCertificateStartDate(X509 & cert)123e869bb63SNan Zhou void validateCertificateStartDate(X509& cert)
124e869bb63SNan Zhou {
125e869bb63SNan Zhou     int days = 0;
126e869bb63SNan Zhou     int secs = 0;
127e869bb63SNan Zhou 
128e869bb63SNan Zhou     ASN1TimePtr epoch(ASN1_TIME_new(), ASN1_STRING_free);
129e869bb63SNan Zhou     // Set time to 00:00am GMT, Jan 1 1970; format: YYYYMMDDHHMMSSZ
130e869bb63SNan Zhou     ASN1_TIME_set_string(epoch.get(), "19700101000000Z");
131e869bb63SNan Zhou 
132e869bb63SNan Zhou     ASN1_TIME* notBefore = X509_get_notBefore(&cert);
133e869bb63SNan Zhou     ASN1_TIME_diff(&days, &secs, epoch.get(), notBefore);
134e869bb63SNan Zhou 
135e869bb63SNan Zhou     if (days < 0 || secs < 0)
136e869bb63SNan Zhou     {
137f2646271SRavi Teja         lg2::error("Certificate valid date starts before the Unix Epoch");
138e869bb63SNan Zhou         elog<InvalidCertificate>(
139e869bb63SNan Zhou             Reason("NotBefore should after 19700101000000Z"));
140e869bb63SNan Zhou     }
141e869bb63SNan Zhou }
142e869bb63SNan Zhou 
validateCertificateAgainstStore(X509_STORE & x509Store,X509 & cert)143e869bb63SNan Zhou void validateCertificateAgainstStore(X509_STORE& x509Store, X509& cert)
144e869bb63SNan Zhou {
145e869bb63SNan Zhou     int errCode = X509_V_OK;
146e869bb63SNan Zhou     X509StoreCtxPtr storeCtx(X509_STORE_CTX_new(), ::X509_STORE_CTX_free);
147e869bb63SNan Zhou     if (!storeCtx)
148e869bb63SNan Zhou     {
149f2646271SRavi Teja         lg2::error("Error occurred during X509_STORE_CTX_new call");
150e869bb63SNan Zhou         elog<InternalFailure>();
151e869bb63SNan Zhou     }
152e869bb63SNan Zhou 
153e869bb63SNan Zhou     errCode = X509_STORE_CTX_init(storeCtx.get(), &x509Store, &cert, nullptr);
154e869bb63SNan Zhou     if (errCode != 1)
155e869bb63SNan Zhou     {
156f2646271SRavi Teja         lg2::error("Error occurred during X509_STORE_CTX_init call");
157e869bb63SNan Zhou         elog<InternalFailure>();
158e869bb63SNan Zhou     }
159e869bb63SNan Zhou 
160e869bb63SNan Zhou     // Set time to current time.
161e869bb63SNan Zhou     auto locTime = time(nullptr);
162e869bb63SNan Zhou 
163e869bb63SNan Zhou     X509_STORE_CTX_set_time(storeCtx.get(), X509_V_FLAG_USE_CHECK_TIME,
164e869bb63SNan Zhou                             locTime);
165e869bb63SNan Zhou 
166e869bb63SNan Zhou     errCode = X509_verify_cert(storeCtx.get());
167e869bb63SNan Zhou     if (errCode == 1)
168e869bb63SNan Zhou     {
169e869bb63SNan Zhou         errCode = X509_V_OK;
170e869bb63SNan Zhou     }
171e869bb63SNan Zhou     else if (errCode == 0)
172e869bb63SNan Zhou     {
173e869bb63SNan Zhou         errCode = X509_STORE_CTX_get_error(storeCtx.get());
174f2646271SRavi Teja         lg2::info(
175e869bb63SNan Zhou             "Error occurred during X509_verify_cert call, checking for known "
176f2646271SRavi Teja             "error, ERRCODE:{ERRCODE}, ERROR_STR:{ERROR_STR}",
177f2646271SRavi Teja             "ERRCODE", errCode, "ERROR_STR",
178f2646271SRavi Teja             X509_verify_cert_error_string(errCode));
179e869bb63SNan Zhou     }
180e869bb63SNan Zhou     else
181e869bb63SNan Zhou     {
182f2646271SRavi Teja         lg2::error("Error occurred during X509_verify_cert call");
183e869bb63SNan Zhou         elog<InternalFailure>();
184e869bb63SNan Zhou     }
185e869bb63SNan Zhou 
186e869bb63SNan Zhou     // Allow certificate upload, for "certificate is not yet valid" and
187e869bb63SNan Zhou     // trust chain related errors.
1883c478144SLei YU     // If ALLOW_EXPIRED is defined, allow expired certificate so that it
1893c478144SLei YU     // could be replaced
1903c478144SLei YU     bool isOK = (errCode == X509_V_OK) ||
191e869bb63SNan Zhou                 (errCode == X509_V_ERR_CERT_NOT_YET_VALID) ||
1923c478144SLei YU                 isTrustChainError(errCode) ||
1933c478144SLei YU                 (allowExpired && errCode == X509_V_ERR_CERT_HAS_EXPIRED);
1943c478144SLei YU 
1953c478144SLei YU     if (!isOK)
196e869bb63SNan Zhou     {
197e869bb63SNan Zhou         if (errCode == X509_V_ERR_CERT_HAS_EXPIRED)
198e869bb63SNan Zhou         {
199f2646271SRavi Teja             lg2::error("Expired certificate ");
200e869bb63SNan Zhou             elog<InvalidCertificate>(Reason("Expired Certificate"));
201e869bb63SNan Zhou         }
202*5d4f7932SManojkiran Eda         // Logging general error here.
203f2646271SRavi Teja         lg2::error(
204f2646271SRavi Teja             "Certificate validation failed, ERRCODE:{ERRCODE}, ERROR_STR:{ERROR_STR}",
205f2646271SRavi Teja             "ERRCODE", errCode, "ERROR_STR",
206f2646271SRavi Teja             X509_verify_cert_error_string(errCode));
207e869bb63SNan Zhou         elog<InvalidCertificate>(Reason("Certificate validation failed"));
208e869bb63SNan Zhou     }
209e869bb63SNan Zhou }
210e869bb63SNan Zhou 
validateCertificateInSSLContext(X509 & cert)211e869bb63SNan Zhou void validateCertificateInSSLContext(X509& cert)
212e869bb63SNan Zhou {
213e869bb63SNan Zhou     const SSL_METHOD* method = TLS_method();
214e869bb63SNan Zhou     SSLCtxPtr ctx(SSL_CTX_new(method), SSL_CTX_free);
215e869bb63SNan Zhou     if (SSL_CTX_use_certificate(ctx.get(), &cert) != 1)
216e869bb63SNan Zhou     {
217f2646271SRavi Teja         lg2::error("Certificate is not usable, ERRCODE:{ERRCODE}", "ERRCODE",
218f2646271SRavi Teja                    ERR_get_error());
219e869bb63SNan Zhou         elog<InvalidCertificate>(Reason("Certificate is not usable"));
220e869bb63SNan Zhou     }
221e869bb63SNan Zhou }
222e869bb63SNan Zhou 
generateCertId(X509 & cert)223e869bb63SNan Zhou std::string generateCertId(X509& cert)
224e869bb63SNan Zhou {
225e869bb63SNan Zhou     unsigned long subjectNameHash = X509_subject_name_hash(&cert);
226e869bb63SNan Zhou     unsigned long issuerSerialHash = X509_issuer_and_serial_hash(&cert);
227e3d47cd4SNan Zhou     static constexpr auto certIdLength = 17;
228e3d47cd4SNan Zhou     char idBuff[certIdLength];
229e869bb63SNan Zhou 
230e3d47cd4SNan Zhou     snprintf(idBuff, certIdLength, "%08lx%08lx", subjectNameHash,
231e869bb63SNan Zhou              issuerSerialHash);
232e869bb63SNan Zhou 
233e869bb63SNan Zhou     return {idBuff};
234e869bb63SNan Zhou }
2356ec13c8fSNan Zhou 
parseCert(const std::string & pem)2366ec13c8fSNan Zhou std::unique_ptr<X509, decltype(&::X509_free)> parseCert(const std::string& pem)
2376ec13c8fSNan Zhou {
2386ec13c8fSNan Zhou     if (pem.size() > INT_MAX)
2396ec13c8fSNan Zhou     {
240f2646271SRavi Teja         lg2::error("Error occurred during parseCert: PEM is too long");
2416ec13c8fSNan Zhou         elog<InvalidCertificate>(Reason("Invalid PEM: too long"));
2426ec13c8fSNan Zhou     }
2436ec13c8fSNan Zhou     X509Ptr cert(X509_new(), ::X509_free);
2446ec13c8fSNan Zhou     if (!cert)
2456ec13c8fSNan Zhou     {
246f2646271SRavi Teja         lg2::error("Error occurred during X509_new call, ERRCODE:{ERRCODE}",
247f2646271SRavi Teja                    "ERRCODE", ERR_get_error());
2486ec13c8fSNan Zhou         elog<InternalFailure>();
2496ec13c8fSNan Zhou     }
2506ec13c8fSNan Zhou 
2516ec13c8fSNan Zhou     BIOMemPtr bioCert(BIO_new_mem_buf(pem.data(), static_cast<int>(pem.size())),
2526ec13c8fSNan Zhou                       ::BIO_free);
2536ec13c8fSNan Zhou     X509* x509 = cert.get();
2546ec13c8fSNan Zhou     if (!PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr))
2556ec13c8fSNan Zhou     {
256f2646271SRavi Teja         lg2::error("Error occurred during PEM_read_bio_X509 call, PEM:{PEM}",
257f2646271SRavi Teja                    "PEM", pem);
2586ec13c8fSNan Zhou         elog<InternalFailure>();
2596ec13c8fSNan Zhou     }
2606ec13c8fSNan Zhou     return cert;
2616ec13c8fSNan Zhou }
262e869bb63SNan Zhou } // namespace phosphor::certs
263