blob: 6dfed96de37ed3d16003626825fe2dd5deb1de10 [file] [log] [blame]
Marri Devender Raocd30c492019-06-12 01:40:17 -05001#include "config.h"
2
Marri Devender Rao6ceec402019-02-01 03:15:19 -06003#include "certificate.hpp"
4
Zbigniew Kurzynskia3bb38f2019-09-17 13:34:25 +02005#include "certs_manager.hpp"
6
Marri Devender Rao6ceec402019-02-01 03:15:19 -06007#include <openssl/bio.h>
8#include <openssl/crypto.h>
9#include <openssl/err.h>
10#include <openssl/evp.h>
11#include <openssl/pem.h>
Nidhin MS811a29e2021-05-13 12:54:32 +053012#include <openssl/ssl.h>
Marri Devender Rao6ceec402019-02-01 03:15:19 -060013#include <openssl/x509v3.h>
14
15#include <fstream>
16#include <phosphor-logging/elog-errors.hpp>
Marri Devender Rao13bf74e2019-03-26 01:52:17 -050017#include <xyz/openbmc_project/Certs/error.hpp>
Marri Devender Rao6ceec402019-02-01 03:15:19 -060018#include <xyz/openbmc_project/Common/error.hpp>
Marri Devender Rao13bf74e2019-03-26 01:52:17 -050019
Nan Zhoue1289ad2021-12-28 11:02:56 -080020namespace phosphor::certs
Marri Devender Rao6ceec402019-02-01 03:15:19 -060021{
Nan Zhoucf06ccd2021-12-28 16:25:45 -080022
23namespace
24{
25namespace fs = std::filesystem;
26using ::phosphor::logging::elog;
27using ::phosphor::logging::entry;
28using ::phosphor::logging::level;
29using ::phosphor::logging::log;
30using ::phosphor::logging::report;
31using InvalidCertificateError =
32 ::sdbusplus::xyz::openbmc_project::Certs::Error::InvalidCertificate;
33using ::phosphor::logging::xyz::openbmc_project::Certs::InvalidCertificate;
34using ::sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
35
Marri Devender Rao6ceec402019-02-01 03:15:19 -060036// RAII support for openSSL functions.
Nan Zhoucf06ccd2021-12-28 16:25:45 -080037using BIOMemPtr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
38using X509StorePtr = std::unique_ptr<X509_STORE, decltype(&::X509_STORE_free)>;
39using X509StoreCtxPtr =
Marri Devender Rao6ceec402019-02-01 03:15:19 -060040 std::unique_ptr<X509_STORE_CTX, decltype(&::X509_STORE_CTX_free)>;
Nan Zhoucf06ccd2021-12-28 16:25:45 -080041using ASN1TimePtr = std::unique_ptr<ASN1_TIME, decltype(&ASN1_STRING_free)>;
42using EVPPkeyPtr = std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>;
43using BufMemPtr = std::unique_ptr<BUF_MEM, decltype(&::BUF_MEM_free)>;
Marri Devender Rao6ceec402019-02-01 03:15:19 -060044
45// Trust chain related errors.`
46#define TRUST_CHAIN_ERR(errnum) \
47 ((errnum == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) || \
48 (errnum == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) || \
49 (errnum == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) || \
Nan Zhou63540132021-11-25 15:47:52 -080050 (errnum == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT) || \
Marri Devender Rao6ceec402019-02-01 03:15:19 -060051 (errnum == X509_V_ERR_CERT_UNTRUSTED) || \
52 (errnum == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE))
53
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -060054// Refer to schema 2018.3
55// http://redfish.dmtf.org/schemas/v1/Certificate.json#/definitions/KeyUsage for
56// supported KeyUsage types in redfish
57// Refer to
58// https://github.com/openssl/openssl/blob/master/include/openssl/x509v3.h for
59// key usage bit fields
60std::map<uint8_t, std::string> keyUsageToRfStr = {
61 {KU_DIGITAL_SIGNATURE, "DigitalSignature"},
62 {KU_NON_REPUDIATION, "NonRepudiation"},
63 {KU_KEY_ENCIPHERMENT, "KeyEncipherment"},
64 {KU_DATA_ENCIPHERMENT, "DataEncipherment"},
65 {KU_KEY_AGREEMENT, "KeyAgreement"},
66 {KU_KEY_CERT_SIGN, "KeyCertSign"},
67 {KU_CRL_SIGN, "CRLSigning"},
68 {KU_ENCIPHER_ONLY, "EncipherOnly"},
69 {KU_DECIPHER_ONLY, "DecipherOnly"}};
70
71// Refer to schema 2018.3
72// http://redfish.dmtf.org/schemas/v1/Certificate.json#/definitions/KeyUsage for
73// supported Extended KeyUsage types in redfish
74std::map<uint8_t, std::string> extendedKeyUsageToRfStr = {
75 {NID_server_auth, "ServerAuthentication"},
76 {NID_client_auth, "ClientAuthentication"},
77 {NID_email_protect, "EmailProtection"},
78 {NID_OCSP_sign, "OCSPSigning"},
79 {NID_ad_timeStamping, "Timestamping"},
80 {NID_code_sign, "CodeSigning"}};
Nan Zhoucf06ccd2021-12-28 16:25:45 -080081} // namespace
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -060082
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +010083std::string Certificate::generateCertId(const std::string& certPath)
Kowalski, Kamildb029c92019-07-08 17:09:39 +020084{
Nan Zhoucf06ccd2021-12-28 16:25:45 -080085 const internal::X509Ptr cert = loadCert(certPath);
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +010086 unsigned long subjectNameHash = X509_subject_name_hash(cert.get());
Zbigniew Lukwinski73d1fbf2020-01-15 15:31:12 +010087 unsigned long issuerSerialHash = X509_issuer_and_serial_hash(cert.get());
88 static constexpr auto CERT_ID_LENGTH = 17;
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +010089 char idBuff[CERT_ID_LENGTH];
90
Zbigniew Lukwinski73d1fbf2020-01-15 15:31:12 +010091 snprintf(idBuff, CERT_ID_LENGTH, "%08lx%08lx", subjectNameHash,
92 issuerSerialHash);
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +010093
94 return std::string(idBuff);
95}
96
97std::string
98 Certificate::generateUniqueFilePath(const std::string& directoryPath)
99{
Nan Zhoucfb58022021-12-28 11:02:26 -0800100 char* filePath = tempnam(directoryPath.c_str(), nullptr);
101 if (filePath == nullptr)
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100102 {
103 log<level::ERR>(
Nan Zhoubf3cf752021-12-28 11:02:07 -0800104 "Error occurred while creating random certificate file path",
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100105 entry("DIR=%s", directoryPath.c_str()));
106 elog<InternalFailure>();
107 }
108 std::string filePathStr(filePath);
109 free(filePath);
110 return filePathStr;
111}
112
113std::string Certificate::generateAuthCertFileX509Path(
114 const std::string& certSrcFilePath, const std::string& certDstDirPath)
115{
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800116 const internal::X509Ptr cert = loadCert(certSrcFilePath);
Kowalski, Kamildb029c92019-07-08 17:09:39 +0200117 unsigned long hash = X509_subject_name_hash(cert.get());
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100118 static constexpr auto CERT_HASH_LENGTH = 9;
119 char hashBuf[CERT_HASH_LENGTH];
Kowalski, Kamildb029c92019-07-08 17:09:39 +0200120
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100121 snprintf(hashBuf, CERT_HASH_LENGTH, "%08lx", hash);
Kowalski, Kamildb029c92019-07-08 17:09:39 +0200122
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100123 const std::string certHash(hashBuf);
Nan Zhou718eef32021-12-28 11:03:30 -0800124 for (size_t i = 0; i < maxNumAuthorityCertificates; ++i)
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100125 {
Zbigniew Lukwinski73d1fbf2020-01-15 15:31:12 +0100126 const std::string certDstFileX509Path =
127 certDstDirPath + "/" + certHash + "." + std::to_string(i);
128 if (!fs::exists(certDstFileX509Path))
129 {
130 return certDstFileX509Path;
131 }
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100132 }
Kowalski, Kamildb029c92019-07-08 17:09:39 +0200133
Zbigniew Lukwinski73d1fbf2020-01-15 15:31:12 +0100134 log<level::ERR>("Authority certificate x509 file path already used",
135 entry("DIR=%s", certDstDirPath.c_str()));
136 elog<InternalFailure>();
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100137}
138
139std::string
140 Certificate::generateAuthCertFilePath(const std::string& certSrcFilePath)
141{
142 // If there is a certificate file path (which means certificate replacement
143 // is doing) use it (do not create new one)
144 if (!certFilePath.empty())
145 {
146 return certFilePath;
147 }
148 // If source certificate file is located in the certificates directory use
149 // it (do not create new one)
150 else if (fs::path(certSrcFilePath).parent_path().string() ==
151 certInstallPath)
152 {
153 return certSrcFilePath;
154 }
155 // Otherwise generate new file name/path
156 else
157 {
158 return generateUniqueFilePath(certInstallPath);
159 }
160}
161
162std::string
163 Certificate::generateCertFilePath(const std::string& certSrcFilePath)
164{
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800165 if (certType == CertificateType::Authority)
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100166 {
167 return generateAuthCertFilePath(certSrcFilePath);
168 }
169 else
170 {
171 return certInstallPath;
172 }
Kowalski, Kamildb029c92019-07-08 17:09:39 +0200173}
174
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600175Certificate::Certificate(sdbusplus::bus::bus& bus, const std::string& objPath,
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800176 CertificateType type, const std::string& installPath,
177 const std::string& uploadPath, Watch* watch,
178 Manager& parent) :
179 internal::CertificateInterface(bus, objPath.c_str(), true),
180 objectPath(objPath), certType(type), certInstallPath(installPath),
181 certWatch(watch), manager(parent)
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600182{
183 auto installHelper = [this](const auto& filePath) {
184 if (!compareKeys(filePath))
185 {
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800186 elog<InvalidCertificateError>(InvalidCertificate::REASON(
187 "Private key does not match the Certificate"));
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600188 };
189 };
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800190 typeFuncMap[CertificateType::Server] = installHelper;
191 typeFuncMap[CertificateType::Client] = installHelper;
192 typeFuncMap[CertificateType::Authority] = [](const std::string&) {};
Marri Devender Raocd30c492019-06-12 01:40:17 -0500193
194 auto appendPrivateKey = [this](const std::string& filePath) {
195 checkAndAppendPrivateKey(filePath);
196 };
197
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800198 appendKeyMap[CertificateType::Server] = appendPrivateKey;
199 appendKeyMap[CertificateType::Client] = appendPrivateKey;
200 appendKeyMap[CertificateType::Authority] = [](const std::string&) {};
Marri Devender Raocd30c492019-06-12 01:40:17 -0500201
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100202 // Generate certificate file path
203 certFilePath = generateCertFilePath(uploadPath);
204
Marri Devender Raocd30c492019-06-12 01:40:17 -0500205 // install the certificate
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100206 install(uploadPath);
Marri Devender Raocd30c492019-06-12 01:40:17 -0500207
Marri Devender Raoedd11312019-02-27 08:45:10 -0600208 this->emit_object_added();
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600209}
210
211Certificate::~Certificate()
212{
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100213 if (!fs::remove(certFilePath))
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600214 {
215 log<level::INFO>("Certificate file not found!",
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100216 entry("PATH=%s", certFilePath.c_str()));
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600217 }
218}
219
Marri Devender Rao13bf74e2019-03-26 01:52:17 -0500220void Certificate::replace(const std::string filePath)
221{
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100222 manager.replaceCertificate(this, filePath);
Marri Devender Rao13bf74e2019-03-26 01:52:17 -0500223}
224
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100225void Certificate::install(const std::string& certSrcFilePath)
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600226{
227 log<level::INFO>("Certificate install ",
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100228 entry("FILEPATH=%s", certSrcFilePath.c_str()));
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600229 auto errCode = X509_V_OK;
230
Marri Devender Raoffad1ef2019-06-03 04:54:12 -0500231 // stop watch for user initiated certificate install
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800232 if (certWatch != nullptr)
Marri Devender Raoffad1ef2019-06-03 04:54:12 -0500233 {
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800234 certWatch->stopWatch();
Marri Devender Raoffad1ef2019-06-03 04:54:12 -0500235 }
236
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600237 // Verify the certificate file
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100238 fs::path file(certSrcFilePath);
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600239 if (!fs::exists(file))
240 {
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100241 log<level::ERR>("File is Missing",
242 entry("FILE=%s", certSrcFilePath.c_str()));
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600243 elog<InternalFailure>();
244 }
245
246 try
247 {
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100248 if (fs::file_size(certSrcFilePath) == 0)
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600249 {
250 // file is empty
251 log<level::ERR>("File is empty",
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100252 entry("FILE=%s", certSrcFilePath.c_str()));
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800253 elog<InvalidCertificateError>(
254 InvalidCertificate::REASON("File is empty"));
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600255 }
256 }
257 catch (const fs::filesystem_error& e)
258 {
259 // Log Error message
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100260 log<level::ERR>(e.what(), entry("FILE=%s", certSrcFilePath.c_str()));
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600261 elog<InternalFailure>();
262 }
263
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600264 // Create an empty X509_STORE structure for certificate validation.
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800265 X509StorePtr x509Store(X509_STORE_new(), &X509_STORE_free);
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600266 if (!x509Store)
267 {
Nan Zhoubf3cf752021-12-28 11:02:07 -0800268 log<level::ERR>("Error occurred during X509_STORE_new call");
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600269 elog<InternalFailure>();
270 }
271
272 OpenSSL_add_all_algorithms();
273
274 // ADD Certificate Lookup method.
Patrick Williams83e6eab2021-12-05 06:32:24 -0600275 // lookup will be cleaned up automatically when the holding Store goes away.
276 auto lookup = X509_STORE_add_lookup(x509Store.get(), X509_LOOKUP_file());
277
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600278 if (!lookup)
279 {
Nan Zhoubf3cf752021-12-28 11:02:07 -0800280 log<level::ERR>("Error occurred during X509_STORE_add_lookup call");
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600281 elog<InternalFailure>();
282 }
283 // Load Certificate file.
Patrick Williams83e6eab2021-12-05 06:32:24 -0600284 errCode = X509_LOOKUP_load_file(lookup, certSrcFilePath.c_str(),
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600285 X509_FILETYPE_PEM);
286 if (errCode != 1)
287 {
Nan Zhoubf3cf752021-12-28 11:02:07 -0800288 log<level::ERR>("Error occurred during X509_LOOKUP_load_file call",
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100289 entry("FILE=%s", certSrcFilePath.c_str()));
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800290 elog<InvalidCertificateError>(
291 InvalidCertificate::REASON("Invalid certificate file format"));
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600292 }
293
Nan Zhoubf3cf752021-12-28 11:02:07 -0800294 // Load Certificate file into the X509 structure.
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800295 internal::X509Ptr cert = loadCert(certSrcFilePath);
296 X509StoreCtxPtr storeCtx(X509_STORE_CTX_new(), ::X509_STORE_CTX_free);
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600297 if (!storeCtx)
298 {
Nan Zhoubf3cf752021-12-28 11:02:07 -0800299 log<level::ERR>("Error occurred during X509_STORE_CTX_new call",
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100300 entry("FILE=%s", certSrcFilePath.c_str()));
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600301 elog<InternalFailure>();
302 }
303
Nan Zhoucfb58022021-12-28 11:02:26 -0800304 errCode = X509_STORE_CTX_init(storeCtx.get(), x509Store.get(), cert.get(),
305 nullptr);
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600306 if (errCode != 1)
307 {
Nan Zhoubf3cf752021-12-28 11:02:07 -0800308 log<level::ERR>("Error occurred during X509_STORE_CTX_init call",
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100309 entry("FILE=%s", certSrcFilePath.c_str()));
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600310 elog<InternalFailure>();
311 }
312
313 // Set time to current time.
314 auto locTime = time(nullptr);
315
316 X509_STORE_CTX_set_time(storeCtx.get(), X509_V_FLAG_USE_CHECK_TIME,
317 locTime);
318
319 errCode = X509_verify_cert(storeCtx.get());
320 if (errCode == 1)
321 {
322 errCode = X509_V_OK;
323 }
324 else if (errCode == 0)
325 {
326 errCode = X509_STORE_CTX_get_error(storeCtx.get());
Marri Devender Rao2e8c3a52019-08-09 01:26:35 -0500327 log<level::INFO>(
Nan Zhoubf3cf752021-12-28 11:02:07 -0800328 "Error occurred during X509_verify_cert call, checking for known "
Marri Devender Rao2e8c3a52019-08-09 01:26:35 -0500329 "error",
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100330 entry("FILE=%s", certSrcFilePath.c_str()),
331 entry("ERRCODE=%d", errCode),
Marri Devender Rao2e8c3a52019-08-09 01:26:35 -0500332 entry("ERROR_STR=%s", X509_verify_cert_error_string(errCode)));
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600333 }
334 else
335 {
Nan Zhoubf3cf752021-12-28 11:02:07 -0800336 log<level::ERR>("Error occurred during X509_verify_cert call",
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100337 entry("FILE=%s", certSrcFilePath.c_str()));
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600338 elog<InternalFailure>();
339 }
340
341 // Allow certificate upload, for "certificate is not yet valid" and
342 // trust chain related errors.
343 if (!((errCode == X509_V_OK) ||
344 (errCode == X509_V_ERR_CERT_NOT_YET_VALID) ||
345 TRUST_CHAIN_ERR(errCode)))
346 {
347 if (errCode == X509_V_ERR_CERT_HAS_EXPIRED)
348 {
Marri Devender Raoc4522d22020-03-12 06:50:17 -0500349 log<level::ERR>("Expired certificate ");
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800350 elog<InvalidCertificateError>(
351 InvalidCertificate::REASON("Expired Certificate"));
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600352 }
Nan Zhoubf3cf752021-12-28 11:02:07 -0800353 // Logging general error here.
Marri Devender Raoc4522d22020-03-12 06:50:17 -0500354 log<level::ERR>(
355 "Certificate validation failed", entry("ERRCODE=%d", errCode),
356 entry("ERROR_STR=%s", X509_verify_cert_error_string(errCode)));
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800357 elog<InvalidCertificateError>(
358 InvalidCertificate::REASON("Certificate validation failed"));
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600359 }
360
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800361 validateCertificateStartDate(*cert);
Marri Devender Raoc4522d22020-03-12 06:50:17 -0500362
Nidhin MS811a29e2021-05-13 12:54:32 +0530363 // Verify that the certificate can be used in a TLS context
364 const SSL_METHOD* method = TLS_method();
365 std::unique_ptr<SSL_CTX, decltype(&::SSL_CTX_free)> ctx(SSL_CTX_new(method),
366 SSL_CTX_free);
367 if (SSL_CTX_use_certificate(ctx.get(), cert.get()) != 1)
368 {
369 log<level::ERR>("Certificate is not usable",
370 entry("ERRCODE=%x", ERR_get_error()));
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800371 elog<InvalidCertificateError>(
372 InvalidCertificate::REASON("Certificate is not usable"));
Nidhin MS811a29e2021-05-13 12:54:32 +0530373 }
374
Marri Devender Raocd30c492019-06-12 01:40:17 -0500375 // Invoke type specific append private key function.
376 auto appendIter = appendKeyMap.find(certType);
377 if (appendIter == appendKeyMap.end())
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600378 {
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800379 log<level::ERR>("Unsupported Type",
380 entry("TYPE=%s", certificateTypeToString(certType)));
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600381 elog<InternalFailure>();
382 }
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100383 appendIter->second(certSrcFilePath);
Marri Devender Raocd30c492019-06-12 01:40:17 -0500384
385 // Invoke type specific compare keys function.
386 auto compIter = typeFuncMap.find(certType);
387 if (compIter == typeFuncMap.end())
388 {
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800389 log<level::ERR>("Unsupported Type",
390 entry("TYPE=%s", certificateTypeToString(certType)));
Marri Devender Raocd30c492019-06-12 01:40:17 -0500391 elog<InternalFailure>();
392 }
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100393 compIter->second(certSrcFilePath);
Kowalski, Kamildb029c92019-07-08 17:09:39 +0200394
Marri Devender Raoffad1ef2019-06-03 04:54:12 -0500395 // Copy the certificate to the installation path
396 // During bootup will be parsing existing file so no need to
397 // copy it.
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100398 if (certSrcFilePath != certFilePath)
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600399 {
Marri Devender Raoffad1ef2019-06-03 04:54:12 -0500400 std::ifstream inputCertFileStream;
401 std::ofstream outputCertFileStream;
402 inputCertFileStream.exceptions(std::ifstream::failbit |
403 std::ifstream::badbit |
404 std::ifstream::eofbit);
405 outputCertFileStream.exceptions(std::ofstream::failbit |
406 std::ofstream::badbit |
407 std::ofstream::eofbit);
Kowalski, Kamildb029c92019-07-08 17:09:39 +0200408
Marri Devender Raoffad1ef2019-06-03 04:54:12 -0500409 try
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600410 {
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100411 inputCertFileStream.open(certSrcFilePath);
412 outputCertFileStream.open(certFilePath, std::ios::out);
Marri Devender Raoffad1ef2019-06-03 04:54:12 -0500413 outputCertFileStream << inputCertFileStream.rdbuf() << std::flush;
414 inputCertFileStream.close();
415 outputCertFileStream.close();
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600416 }
Marri Devender Raoffad1ef2019-06-03 04:54:12 -0500417 catch (const std::exception& e)
418 {
419 log<level::ERR>("Failed to copy certificate",
420 entry("ERR=%s", e.what()),
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100421 entry("SRC=%s", certSrcFilePath.c_str()),
422 entry("DST=%s", certFilePath.c_str()));
Marri Devender Raoffad1ef2019-06-03 04:54:12 -0500423 elog<InternalFailure>();
424 }
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600425 }
Marri Devender Rao8f80c352019-05-13 00:53:01 -0500426
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100427 storageUpdate();
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600428
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100429 // Keep certificate ID
430 certId = generateCertId(certFilePath);
Kowalski, Kamildb029c92019-07-08 17:09:39 +0200431
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600432 // Parse the certificate file and populate properties
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100433 populateProperties(certFilePath);
Marri Devender Raoffad1ef2019-06-03 04:54:12 -0500434
435 // restart watch
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800436 if (certWatch != nullptr)
Marri Devender Raoffad1ef2019-06-03 04:54:12 -0500437 {
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800438 certWatch->startWatch();
Marri Devender Raoffad1ef2019-06-03 04:54:12 -0500439 }
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600440}
441
Nan Zhoucf811c42021-12-02 14:56:17 -0800442// Checks that notBefore is not earlier than the unix epoch given that
443// the corresponding DBus interface is uint64_t.
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800444void Certificate::validateCertificateStartDate(X509& cert)
Marri Devender Raoc4522d22020-03-12 06:50:17 -0500445{
446 int days = 0;
447 int secs = 0;
448
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800449 ASN1TimePtr epoch(ASN1_TIME_new(), ASN1_STRING_free);
Nan Zhoucf811c42021-12-02 14:56:17 -0800450 // Set time to 00:00am GMT, Jan 1 1970; format: YYYYMMDDHHMMSSZ
451 ASN1_TIME_set_string(epoch.get(), "19700101000000Z");
Marri Devender Raoc4522d22020-03-12 06:50:17 -0500452
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800453 ASN1_TIME* notBefore = X509_get_notBefore(&cert);
Nan Zhoucf811c42021-12-02 14:56:17 -0800454 ASN1_TIME_diff(&days, &secs, epoch.get(), notBefore);
Marri Devender Raoc4522d22020-03-12 06:50:17 -0500455
Nan Zhoucf811c42021-12-02 14:56:17 -0800456 if (days < 0 || secs < 0)
Marri Devender Raoc4522d22020-03-12 06:50:17 -0500457 {
Nan Zhoucf811c42021-12-02 14:56:17 -0800458 log<level::ERR>("Certificate valid date starts before the Unix Epoch");
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800459 elog<InvalidCertificateError>(InvalidCertificate::REASON(
460 "NotBefore should after 19700101000000Z"));
Marri Devender Raoc4522d22020-03-12 06:50:17 -0500461 }
462}
463
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600464void Certificate::populateProperties()
465{
Kowalski, Kamildb029c92019-07-08 17:09:39 +0200466 populateProperties(certInstallPath);
467}
468
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100469std::string Certificate::getCertId() const
Kowalski, Kamildb029c92019-07-08 17:09:39 +0200470{
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100471 return certId;
472}
473
474bool Certificate::isSame(const std::string& certPath)
475{
476 return getCertId() == generateCertId(certPath);
477}
478
479void Certificate::storageUpdate()
480{
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800481 if (certType == CertificateType::Authority)
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100482 {
483 // Create symbolic link in the certificate directory
484 std::string certFileX509Path;
485 try
486 {
487 if (!certFilePath.empty() &&
488 fs::is_regular_file(fs::path(certFilePath)))
489 {
490 certFileX509Path =
491 generateAuthCertFileX509Path(certFilePath, certInstallPath);
492 fs::create_symlink(fs::path(certFilePath),
493 fs::path(certFileX509Path));
494 }
495 }
496 catch (const std::exception& e)
497 {
498 log<level::ERR>("Failed to create symlink for certificate",
499 entry("ERR=%s", e.what()),
500 entry("FILE=%s", certFilePath.c_str()),
501 entry("SYMLINK=%s", certFileX509Path.c_str()));
502 elog<InternalFailure>();
503 }
504 }
Kowalski, Kamildb029c92019-07-08 17:09:39 +0200505}
506
507void Certificate::populateProperties(const std::string& certPath)
508{
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800509 internal::X509Ptr cert = loadCert(certPath);
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600510 // Update properties if no error thrown
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800511 BIOMemPtr certBio(BIO_new(BIO_s_mem()), BIO_free);
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600512 PEM_write_bio_X509(certBio.get(), cert.get());
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800513 BufMemPtr certBuf(BUF_MEM_new(), BUF_MEM_free);
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600514 BUF_MEM* buf = certBuf.get();
515 BIO_get_mem_ptr(certBio.get(), &buf);
516 std::string certStr(buf->data, buf->length);
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800517 certificateString(certStr);
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600518
519 static const int maxKeySize = 4096;
520 char subBuffer[maxKeySize] = {0};
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800521 BIOMemPtr subBio(BIO_new(BIO_s_mem()), BIO_free);
Nan Zhoubf3cf752021-12-28 11:02:07 -0800522 // This pointer cannot be freed independently.
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600523 X509_NAME* sub = X509_get_subject_name(cert.get());
Marri Devender Raodec58772019-06-11 03:10:00 -0500524 X509_NAME_print_ex(subBio.get(), sub, 0, XN_FLAG_SEP_COMMA_PLUS);
525 BIO_read(subBio.get(), subBuffer, maxKeySize);
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800526 subject(subBuffer);
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600527
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600528 char issuerBuffer[maxKeySize] = {0};
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800529 BIOMemPtr issuerBio(BIO_new(BIO_s_mem()), BIO_free);
Nan Zhoubf3cf752021-12-28 11:02:07 -0800530 // This pointer cannot be freed independently.
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600531 X509_NAME* issuer_name = X509_get_issuer_name(cert.get());
Marri Devender Raodec58772019-06-11 03:10:00 -0500532 X509_NAME_print_ex(issuerBio.get(), issuer_name, 0, XN_FLAG_SEP_COMMA_PLUS);
533 BIO_read(issuerBio.get(), issuerBuffer, maxKeySize);
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800534 issuer(issuerBuffer);
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600535
536 std::vector<std::string> keyUsageList;
537 ASN1_BIT_STRING* usage;
538
539 // Go through each usage in the bit string and convert to
540 // corresponding string value
541 if ((usage = static_cast<ASN1_BIT_STRING*>(
Nan Zhoucfb58022021-12-28 11:02:26 -0800542 X509_get_ext_d2i(cert.get(), NID_key_usage, nullptr, nullptr))))
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600543 {
544 for (auto i = 0; i < usage->length; ++i)
545 {
546 for (auto& x : keyUsageToRfStr)
547 {
548 if (x.first & usage->data[i])
549 {
550 keyUsageList.push_back(x.second);
551 break;
552 }
553 }
554 }
555 }
556
557 EXTENDED_KEY_USAGE* extUsage;
Nan Zhoucfb58022021-12-28 11:02:26 -0800558 if ((extUsage = static_cast<EXTENDED_KEY_USAGE*>(X509_get_ext_d2i(
559 cert.get(), NID_ext_key_usage, nullptr, nullptr))))
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600560 {
561 for (int i = 0; i < sk_ASN1_OBJECT_num(extUsage); i++)
562 {
563 keyUsageList.push_back(extendedKeyUsageToRfStr[OBJ_obj2nid(
564 sk_ASN1_OBJECT_value(extUsage, i))]);
565 }
566 }
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800567 keyUsage(keyUsageList);
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600568
569 int days = 0;
570 int secs = 0;
571
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800572 ASN1TimePtr epoch(ASN1_TIME_new(), ASN1_STRING_free);
Nan Zhoucf811c42021-12-02 14:56:17 -0800573 // Set time to 00:00am GMT, Jan 1 1970; format: YYYYMMDDHHMMSSZ
574 ASN1_TIME_set_string(epoch.get(), "19700101000000Z");
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600575
Nan Zhoucf811c42021-12-02 14:56:17 -0800576 static const uint64_t dayToSeconds = 24 * 60 * 60;
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600577 ASN1_TIME* notAfter = X509_get_notAfter(cert.get());
578 ASN1_TIME_diff(&days, &secs, epoch.get(), notAfter);
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800579 validNotAfter((days * dayToSeconds) + secs);
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600580
581 ASN1_TIME* notBefore = X509_get_notBefore(cert.get());
582 ASN1_TIME_diff(&days, &secs, epoch.get(), notBefore);
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800583 validNotBefore((days * dayToSeconds) + secs);
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600584}
585
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800586internal::X509Ptr Certificate::loadCert(const std::string& filePath)
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600587{
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600588 // Read Certificate file
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800589 internal::X509Ptr cert(X509_new(), ::X509_free);
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600590 if (!cert)
591 {
Nan Zhoubf3cf752021-12-28 11:02:07 -0800592 log<level::ERR>("Error occurred during X509_new call",
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600593 entry("FILE=%s", filePath.c_str()),
594 entry("ERRCODE=%lu", ERR_get_error()));
595 elog<InternalFailure>();
596 }
597
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800598 BIOMemPtr bioCert(BIO_new_file(filePath.c_str(), "rb"), ::BIO_free);
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600599 if (!bioCert)
600 {
Nan Zhoubf3cf752021-12-28 11:02:07 -0800601 log<level::ERR>("Error occurred during BIO_new_file call",
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600602 entry("FILE=%s", filePath.c_str()));
603 elog<InternalFailure>();
604 }
605
606 X509* x509 = cert.get();
607 if (!PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr))
608 {
Nan Zhoubf3cf752021-12-28 11:02:07 -0800609 log<level::ERR>("Error occurred during PEM_read_bio_X509 call",
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600610 entry("FILE=%s", filePath.c_str()));
611 elog<InternalFailure>();
612 }
613 return cert;
614}
Marri Devender Raocd30c492019-06-12 01:40:17 -0500615
616void Certificate::checkAndAppendPrivateKey(const std::string& filePath)
617{
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800618 BIOMemPtr keyBio(BIO_new(BIO_s_file()), ::BIO_free);
Marri Devender Raocd30c492019-06-12 01:40:17 -0500619 if (!keyBio)
620 {
Nan Zhoubf3cf752021-12-28 11:02:07 -0800621 log<level::ERR>("Error occurred during BIO_s_file call",
Marri Devender Raocd30c492019-06-12 01:40:17 -0500622 entry("FILE=%s", filePath.c_str()));
623 elog<InternalFailure>();
624 }
625 BIO_read_filename(keyBio.get(), filePath.c_str());
626
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800627 EVPPkeyPtr priKey(
Marri Devender Raocd30c492019-06-12 01:40:17 -0500628 PEM_read_bio_PrivateKey(keyBio.get(), nullptr, nullptr, nullptr),
629 ::EVP_PKEY_free);
630 if (!priKey)
631 {
632 log<level::INFO>("Private key not present in file",
633 entry("FILE=%s", filePath.c_str()));
634 fs::path privateKeyFile = fs::path(certInstallPath).parent_path();
Nan Zhou718eef32021-12-28 11:03:30 -0800635 privateKeyFile = privateKeyFile / defaultPrivateKeyFileName;
Marri Devender Raocd30c492019-06-12 01:40:17 -0500636 if (!fs::exists(privateKeyFile))
637 {
638 log<level::ERR>("Private key file is not found",
639 entry("FILE=%s", privateKeyFile.c_str()));
640 elog<InternalFailure>();
641 }
642
643 std::ifstream privKeyFileStream;
644 std::ofstream certFileStream;
645 privKeyFileStream.exceptions(std::ifstream::failbit |
646 std::ifstream::badbit |
647 std::ifstream::eofbit);
648 certFileStream.exceptions(std::ofstream::failbit |
649 std::ofstream::badbit |
650 std::ofstream::eofbit);
651 try
652 {
653 privKeyFileStream.open(privateKeyFile);
654 certFileStream.open(filePath, std::ios::app);
Marri Devender Rao18e51c92019-07-15 04:59:01 -0500655 certFileStream << std::endl; // insert line break
Marri Devender Raocd30c492019-06-12 01:40:17 -0500656 certFileStream << privKeyFileStream.rdbuf() << std::flush;
657 privKeyFileStream.close();
658 certFileStream.close();
659 }
660 catch (const std::exception& e)
661 {
662 log<level::ERR>("Failed to append private key",
663 entry("ERR=%s", e.what()),
664 entry("SRC=%s", privateKeyFile.c_str()),
665 entry("DST=%s", filePath.c_str()));
666 elog<InternalFailure>();
667 }
668 }
669}
670
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600671bool Certificate::compareKeys(const std::string& filePath)
672{
673 log<level::INFO>("Certificate compareKeys",
674 entry("FILEPATH=%s", filePath.c_str()));
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800675 internal::X509Ptr cert(X509_new(), ::X509_free);
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600676 if (!cert)
677 {
Nan Zhoubf3cf752021-12-28 11:02:07 -0800678 log<level::ERR>("Error occurred during X509_new call",
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600679 entry("FILE=%s", filePath.c_str()),
680 entry("ERRCODE=%lu", ERR_get_error()));
681 elog<InternalFailure>();
682 }
683
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800684 BIOMemPtr bioCert(BIO_new_file(filePath.c_str(), "rb"), ::BIO_free);
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600685 if (!bioCert)
686 {
Nan Zhoubf3cf752021-12-28 11:02:07 -0800687 log<level::ERR>("Error occurred during BIO_new_file call",
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600688 entry("FILE=%s", filePath.c_str()));
689 elog<InternalFailure>();
690 }
691
692 X509* x509 = cert.get();
693 PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr);
694
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800695 EVPPkeyPtr pubKey(X509_get_pubkey(cert.get()), ::EVP_PKEY_free);
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600696 if (!pubKey)
697 {
698 log<level::ERR>("Error occurred during X509_get_pubkey",
699 entry("FILE=%s", filePath.c_str()),
700 entry("ERRCODE=%lu", ERR_get_error()));
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800701 elog<InvalidCertificateError>(
702 InvalidCertificate::REASON("Failed to get public key info"));
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600703 }
704
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800705 BIOMemPtr keyBio(BIO_new(BIO_s_file()), ::BIO_free);
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600706 if (!keyBio)
707 {
Nan Zhoubf3cf752021-12-28 11:02:07 -0800708 log<level::ERR>("Error occurred during BIO_s_file call",
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600709 entry("FILE=%s", filePath.c_str()));
710 elog<InternalFailure>();
711 }
712 BIO_read_filename(keyBio.get(), filePath.c_str());
713
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800714 EVPPkeyPtr priKey(
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600715 PEM_read_bio_PrivateKey(keyBio.get(), nullptr, nullptr, nullptr),
716 ::EVP_PKEY_free);
717 if (!priKey)
718 {
719 log<level::ERR>("Error occurred during PEM_read_bio_PrivateKey",
720 entry("FILE=%s", filePath.c_str()),
721 entry("ERRCODE=%lu", ERR_get_error()));
Nan Zhoucf06ccd2021-12-28 16:25:45 -0800722 elog<InvalidCertificateError>(
723 InvalidCertificate::REASON("Failed to get private key info"));
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600724 }
725
Patrick Williams55ceaa22021-12-14 06:52:26 -0600726#if (OPENSSL_VERSION_NUMBER < 0x30000000L)
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600727 int32_t rc = EVP_PKEY_cmp(priKey.get(), pubKey.get());
Patrick Williams55ceaa22021-12-14 06:52:26 -0600728#else
729 int32_t rc = EVP_PKEY_eq(priKey.get(), pubKey.get());
730#endif
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600731 if (rc != 1)
732 {
733 log<level::ERR>("Private key is not matching with Certificate",
734 entry("FILE=%s", filePath.c_str()),
735 entry("ERRCODE=%d", rc));
736 return false;
737 }
738 return true;
739}
740
Zbigniew Kurzynskia3bb38f2019-09-17 13:34:25 +0200741void Certificate::delete_()
742{
Zbigniew Lukwinski2f3563c2020-01-08 12:35:23 +0100743 manager.deleteCertificate(this);
Zbigniew Kurzynskia3bb38f2019-09-17 13:34:25 +0200744}
Nan Zhoue1289ad2021-12-28 11:02:56 -0800745} // namespace phosphor::certs