blob: 7978771c5dee986db1f235fedec86c8e23bcdfb3 [file] [log] [blame]
Lei YU3c478142022-06-27 14:42:47 +08001#include "config.h"
2
Nan Zhoue869bb62021-12-30 11:34:42 -08003#include "x509_utils.hpp"
4
5#include <openssl/asn1.h>
6#include <openssl/bio.h>
7#include <openssl/err.h>
8#include <openssl/evp.h>
9#include <openssl/pem.h>
10#include <openssl/ssl3.h>
11#include <openssl/x509_vfy.h>
12
Nan Zhoue869bb62021-12-30 11:34:42 -080013#include <phosphor-logging/elog-errors.hpp>
14#include <phosphor-logging/elog.hpp>
15#include <phosphor-logging/log.hpp>
16#include <xyz/openbmc_project/Certs/error.hpp>
17#include <xyz/openbmc_project/Common/error.hpp>
18
Patrick Williams223e4602023-05-10 07:51:11 -050019#include <cstdio>
20#include <ctime>
21#include <exception>
22#include <memory>
23
Nan Zhoue869bb62021-12-30 11:34:42 -080024namespace phosphor::certs
25{
26
27namespace
28{
29
30using ::phosphor::logging::elog;
31using ::phosphor::logging::entry;
32using ::phosphor::logging::level;
33using ::phosphor::logging::log;
34using ::sdbusplus::xyz::openbmc_project::Certs::Error::InvalidCertificate;
35using ::sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
36using Reason = ::phosphor::logging::xyz::openbmc_project::Certs::
37 InvalidCertificate::REASON;
38
39// RAII support for openSSL functions.
40using X509StorePtr = std::unique_ptr<X509_STORE, decltype(&::X509_STORE_free)>;
41using X509StoreCtxPtr =
42 std::unique_ptr<X509_STORE_CTX, decltype(&::X509_STORE_CTX_free)>;
43using X509Ptr = std::unique_ptr<X509, decltype(&::X509_free)>;
44using BIOMemPtr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
45using ASN1TimePtr = std::unique_ptr<ASN1_TIME, decltype(&ASN1_STRING_free)>;
46using SSLCtxPtr = std::unique_ptr<SSL_CTX, decltype(&::SSL_CTX_free)>;
47
48// Trust chain related errors.`
49constexpr bool isTrustChainError(int error)
50{
51 return error == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
52 error == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN ||
53 error == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY ||
54 error == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT ||
55 error == X509_V_ERR_CERT_UNTRUSTED ||
56 error == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE;
57}
58} // namespace
59
60X509StorePtr getX509Store(const std::string& certSrcPath)
61{
62 // Create an empty X509_STORE structure for certificate validation.
63 X509StorePtr x509Store(X509_STORE_new(), &X509_STORE_free);
64 if (!x509Store)
65 {
66 log<level::ERR>("Error occurred during X509_STORE_new call");
67 elog<InternalFailure>();
68 }
69
70 OpenSSL_add_all_algorithms();
71
72 // ADD Certificate Lookup method.
73 // lookup will be cleaned up automatically when the holding Store goes away.
74 auto lookup = X509_STORE_add_lookup(x509Store.get(), X509_LOOKUP_file());
75
76 if (!lookup)
77 {
78 log<level::ERR>("Error occurred during X509_STORE_add_lookup call");
79 elog<InternalFailure>();
80 }
81 // Load the Certificate file into X509 Store.
82 if (int errCode = X509_LOOKUP_load_file(lookup, certSrcPath.c_str(),
83 X509_FILETYPE_PEM);
84 errCode != 1)
85 {
86 log<level::ERR>("Error occurred during X509_LOOKUP_load_file call",
87 entry("FILE=%s", certSrcPath.c_str()));
88 elog<InvalidCertificate>(Reason("Invalid certificate file format"));
89 }
90 return x509Store;
91}
92
93X509Ptr loadCert(const std::string& filePath)
94{
95 // Read Certificate file
96 X509Ptr cert(X509_new(), ::X509_free);
97 if (!cert)
98 {
99 log<level::ERR>("Error occurred during X509_new call",
100 entry("FILE=%s", filePath.c_str()),
101 entry("ERRCODE=%lu", ERR_get_error()));
102 elog<InternalFailure>();
103 }
104
105 BIOMemPtr bioCert(BIO_new_file(filePath.c_str(), "rb"), ::BIO_free);
106 if (!bioCert)
107 {
108 log<level::ERR>("Error occurred during BIO_new_file call",
109 entry("FILE=%s", filePath.c_str()));
110 elog<InternalFailure>();
111 }
112
113 X509* x509 = cert.get();
114 if (!PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr))
115 {
116 log<level::ERR>("Error occurred during PEM_read_bio_X509 call",
117 entry("FILE=%s", filePath.c_str()));
118 elog<InternalFailure>();
119 }
120 return cert;
121}
122
123// Checks that notBefore is not earlier than the unix epoch given that
124// the corresponding DBus interface is uint64_t.
125void validateCertificateStartDate(X509& cert)
126{
127 int days = 0;
128 int secs = 0;
129
130 ASN1TimePtr epoch(ASN1_TIME_new(), ASN1_STRING_free);
131 // Set time to 00:00am GMT, Jan 1 1970; format: YYYYMMDDHHMMSSZ
132 ASN1_TIME_set_string(epoch.get(), "19700101000000Z");
133
134 ASN1_TIME* notBefore = X509_get_notBefore(&cert);
135 ASN1_TIME_diff(&days, &secs, epoch.get(), notBefore);
136
137 if (days < 0 || secs < 0)
138 {
139 log<level::ERR>("Certificate valid date starts before the Unix Epoch");
140 elog<InvalidCertificate>(
141 Reason("NotBefore should after 19700101000000Z"));
142 }
143}
144
145void validateCertificateAgainstStore(X509_STORE& x509Store, X509& cert)
146{
147 int errCode = X509_V_OK;
148 X509StoreCtxPtr storeCtx(X509_STORE_CTX_new(), ::X509_STORE_CTX_free);
149 if (!storeCtx)
150 {
151 log<level::ERR>("Error occurred during X509_STORE_CTX_new call");
152 elog<InternalFailure>();
153 }
154
155 errCode = X509_STORE_CTX_init(storeCtx.get(), &x509Store, &cert, nullptr);
156 if (errCode != 1)
157 {
158 log<level::ERR>("Error occurred during X509_STORE_CTX_init call");
159 elog<InternalFailure>();
160 }
161
162 // Set time to current time.
163 auto locTime = time(nullptr);
164
165 X509_STORE_CTX_set_time(storeCtx.get(), X509_V_FLAG_USE_CHECK_TIME,
166 locTime);
167
168 errCode = X509_verify_cert(storeCtx.get());
169 if (errCode == 1)
170 {
171 errCode = X509_V_OK;
172 }
173 else if (errCode == 0)
174 {
175 errCode = X509_STORE_CTX_get_error(storeCtx.get());
176 log<level::INFO>(
177 "Error occurred during X509_verify_cert call, checking for known "
178 "error",
179 entry("ERRCODE=%d", errCode),
180 entry("ERROR_STR=%s", X509_verify_cert_error_string(errCode)));
181 }
182 else
183 {
184 log<level::ERR>("Error occurred during X509_verify_cert call");
185 elog<InternalFailure>();
186 }
187
188 // Allow certificate upload, for "certificate is not yet valid" and
189 // trust chain related errors.
Lei YU3c478142022-06-27 14:42:47 +0800190 // If ALLOW_EXPIRED is defined, allow expired certificate so that it
191 // could be replaced
192 bool isOK = (errCode == X509_V_OK) ||
193 (errCode == X509_V_ERR_CERT_NOT_YET_VALID) ||
194 isTrustChainError(errCode) ||
195 (allowExpired && errCode == X509_V_ERR_CERT_HAS_EXPIRED);
196
197 if (!isOK)
Nan Zhoue869bb62021-12-30 11:34:42 -0800198 {
199 if (errCode == X509_V_ERR_CERT_HAS_EXPIRED)
200 {
201 log<level::ERR>("Expired certificate ");
202 elog<InvalidCertificate>(Reason("Expired Certificate"));
203 }
204 // Loging general error here.
205 log<level::ERR>(
206 "Certificate validation failed", entry("ERRCODE=%d", errCode),
207 entry("ERROR_STR=%s", X509_verify_cert_error_string(errCode)));
208 elog<InvalidCertificate>(Reason("Certificate validation failed"));
209 }
210}
211
212void validateCertificateInSSLContext(X509& cert)
213{
214 const SSL_METHOD* method = TLS_method();
215 SSLCtxPtr ctx(SSL_CTX_new(method), SSL_CTX_free);
216 if (SSL_CTX_use_certificate(ctx.get(), &cert) != 1)
217 {
218 log<level::ERR>("Certificate is not usable",
219 entry("ERRCODE=%x", ERR_get_error()));
220 elog<InvalidCertificate>(Reason("Certificate is not usable"));
221 }
222}
223
224std::string generateCertId(X509& cert)
225{
226 unsigned long subjectNameHash = X509_subject_name_hash(&cert);
227 unsigned long issuerSerialHash = X509_issuer_and_serial_hash(&cert);
Nan Zhoue3d47cd2022-09-16 03:41:53 +0000228 static constexpr auto certIdLength = 17;
229 char idBuff[certIdLength];
Nan Zhoue869bb62021-12-30 11:34:42 -0800230
Nan Zhoue3d47cd2022-09-16 03:41:53 +0000231 snprintf(idBuff, certIdLength, "%08lx%08lx", subjectNameHash,
Nan Zhoue869bb62021-12-30 11:34:42 -0800232 issuerSerialHash);
233
234 return {idBuff};
235}
Nan Zhou6ec13c82021-12-30 11:34:50 -0800236
237std::unique_ptr<X509, decltype(&::X509_free)> parseCert(const std::string& pem)
238{
239 if (pem.size() > INT_MAX)
240 {
241 log<level::ERR>("Error occurred during parseCert: PEM is too long");
242 elog<InvalidCertificate>(Reason("Invalid PEM: too long"));
243 }
244 X509Ptr cert(X509_new(), ::X509_free);
245 if (!cert)
246 {
247 log<level::ERR>("Error occurred during X509_new call",
248 entry("ERRCODE=%lu", ERR_get_error()));
249 elog<InternalFailure>();
250 }
251
252 BIOMemPtr bioCert(BIO_new_mem_buf(pem.data(), static_cast<int>(pem.size())),
253 ::BIO_free);
254 X509* x509 = cert.get();
255 if (!PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr))
256 {
257 log<level::ERR>("Error occurred during PEM_read_bio_X509 call",
258 entry("PEM=%s", pem.c_str()));
259 elog<InternalFailure>();
260 }
261 return cert;
262}
Nan Zhoue869bb62021-12-30 11:34:42 -0800263} // namespace phosphor::certs