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