blob: af8635ed82b8653a15027858a5ca6736833a6c20 [file] [log] [blame]
Marri Devender Rao6ceec402019-02-01 03:15:19 -06001#include "certificate.hpp"
2
3#include <openssl/bio.h>
4#include <openssl/crypto.h>
5#include <openssl/err.h>
6#include <openssl/evp.h>
7#include <openssl/pem.h>
8#include <openssl/x509v3.h>
9
10#include <fstream>
11#include <phosphor-logging/elog-errors.hpp>
Marri Devender Rao13bf74e2019-03-26 01:52:17 -050012#include <xyz/openbmc_project/Certs/error.hpp>
Marri Devender Rao6ceec402019-02-01 03:15:19 -060013#include <xyz/openbmc_project/Common/error.hpp>
Marri Devender Rao13bf74e2019-03-26 01:52:17 -050014
Marri Devender Rao6ceec402019-02-01 03:15:19 -060015namespace phosphor
16{
17namespace certs
18{
19// RAII support for openSSL functions.
20using BIO_MEM_Ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
21using X509_STORE_CTX_Ptr =
22 std::unique_ptr<X509_STORE_CTX, decltype(&::X509_STORE_CTX_free)>;
23using X509_LOOKUP_Ptr =
24 std::unique_ptr<X509_LOOKUP, decltype(&::X509_LOOKUP_free)>;
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -060025using ASN1_TIME_ptr = std::unique_ptr<ASN1_TIME, decltype(&ASN1_STRING_free)>;
Marri Devender Rao6ceec402019-02-01 03:15:19 -060026using EVP_PKEY_Ptr = std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>;
27using BUF_MEM_Ptr = std::unique_ptr<BUF_MEM, decltype(&::BUF_MEM_free)>;
28using InternalFailure =
29 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
30using InvalidCertificate =
Marri Devender Rao13bf74e2019-03-26 01:52:17 -050031 sdbusplus::xyz::openbmc_project::Certs::Error::InvalidCertificate;
32using Reason = xyz::openbmc_project::Certs::InvalidCertificate::REASON;
Marri Devender Rao6ceec402019-02-01 03:15:19 -060033
34// Trust chain related errors.`
35#define TRUST_CHAIN_ERR(errnum) \
36 ((errnum == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) || \
37 (errnum == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) || \
38 (errnum == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) || \
39 (errnum == X509_V_ERR_CERT_UNTRUSTED) || \
40 (errnum == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE))
41
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -060042// Refer to schema 2018.3
43// http://redfish.dmtf.org/schemas/v1/Certificate.json#/definitions/KeyUsage for
44// supported KeyUsage types in redfish
45// Refer to
46// https://github.com/openssl/openssl/blob/master/include/openssl/x509v3.h for
47// key usage bit fields
48std::map<uint8_t, std::string> keyUsageToRfStr = {
49 {KU_DIGITAL_SIGNATURE, "DigitalSignature"},
50 {KU_NON_REPUDIATION, "NonRepudiation"},
51 {KU_KEY_ENCIPHERMENT, "KeyEncipherment"},
52 {KU_DATA_ENCIPHERMENT, "DataEncipherment"},
53 {KU_KEY_AGREEMENT, "KeyAgreement"},
54 {KU_KEY_CERT_SIGN, "KeyCertSign"},
55 {KU_CRL_SIGN, "CRLSigning"},
56 {KU_ENCIPHER_ONLY, "EncipherOnly"},
57 {KU_DECIPHER_ONLY, "DecipherOnly"}};
58
59// Refer to schema 2018.3
60// http://redfish.dmtf.org/schemas/v1/Certificate.json#/definitions/KeyUsage for
61// supported Extended KeyUsage types in redfish
62std::map<uint8_t, std::string> extendedKeyUsageToRfStr = {
63 {NID_server_auth, "ServerAuthentication"},
64 {NID_client_auth, "ClientAuthentication"},
65 {NID_email_protect, "EmailProtection"},
66 {NID_OCSP_sign, "OCSPSigning"},
67 {NID_ad_timeStamping, "Timestamping"},
68 {NID_code_sign, "CodeSigning"}};
69
Marri Devender Rao6ceec402019-02-01 03:15:19 -060070Certificate::Certificate(sdbusplus::bus::bus& bus, const std::string& objPath,
71 const CertificateType& type,
72 const UnitsToRestart& unit,
73 const CertInstallPath& installPath,
Marri Devender Rao8f80c352019-05-13 00:53:01 -050074 const CertUploadPath& uploadPath,
Marri Devender Raoffad1ef2019-06-03 04:54:12 -050075 bool isSkipUnitReload,
76 const CertWatchPtr& certWatchPtr) :
Marri Devender Raoedd11312019-02-27 08:45:10 -060077 CertIfaces(bus, objPath.c_str(), true),
78 bus(bus), objectPath(objPath), certType(type), unitToRestart(unit),
Marri Devender Raoffad1ef2019-06-03 04:54:12 -050079 certInstallPath(installPath), certWatchPtr(certWatchPtr)
Marri Devender Rao6ceec402019-02-01 03:15:19 -060080{
81 auto installHelper = [this](const auto& filePath) {
82 if (!compareKeys(filePath))
83 {
84 elog<InvalidCertificate>(
85 Reason("Private key does not match the Certificate"));
86 };
87 };
88 typeFuncMap[SERVER] = installHelper;
89 typeFuncMap[CLIENT] = installHelper;
90 typeFuncMap[AUTHORITY] = [](auto filePath) {};
Marri Devender Rao8f80c352019-05-13 00:53:01 -050091 install(uploadPath, isSkipUnitReload);
Marri Devender Raoedd11312019-02-27 08:45:10 -060092 this->emit_object_added();
Marri Devender Rao6ceec402019-02-01 03:15:19 -060093}
94
95Certificate::~Certificate()
96{
97 if (!fs::remove(certInstallPath))
98 {
99 log<level::INFO>("Certificate file not found!",
100 entry("PATH=%s", certInstallPath.c_str()));
101 }
102 else if (!unitToRestart.empty())
103 {
104 reloadOrReset(unitToRestart);
105 }
106}
107
Marri Devender Rao13bf74e2019-03-26 01:52:17 -0500108void Certificate::replace(const std::string filePath)
109{
Marri Devender Rao8f80c352019-05-13 00:53:01 -0500110 install(filePath, false);
Marri Devender Rao13bf74e2019-03-26 01:52:17 -0500111}
112
Marri Devender Rao8f80c352019-05-13 00:53:01 -0500113void Certificate::install(const std::string& filePath, bool isSkipUnitReload)
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600114{
115 log<level::INFO>("Certificate install ",
116 entry("FILEPATH=%s", filePath.c_str()));
117 auto errCode = X509_V_OK;
118
Marri Devender Raoffad1ef2019-06-03 04:54:12 -0500119 // stop watch for user initiated certificate install
120 if (certWatchPtr)
121 {
122 certWatchPtr->stopWatch();
123 }
124
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600125 // Verify the certificate file
126 fs::path file(filePath);
127 if (!fs::exists(file))
128 {
129 log<level::ERR>("File is Missing", entry("FILE=%s", filePath.c_str()));
130 elog<InternalFailure>();
131 }
132
133 try
134 {
135 if (fs::file_size(filePath) == 0)
136 {
137 // file is empty
138 log<level::ERR>("File is empty",
139 entry("FILE=%s", filePath.c_str()));
140 elog<InvalidCertificate>(Reason("File is empty"));
141 }
142 }
143 catch (const fs::filesystem_error& e)
144 {
145 // Log Error message
146 log<level::ERR>(e.what(), entry("FILE=%s", filePath.c_str()));
147 elog<InternalFailure>();
148 }
149
150 // Defining store object as RAW to avoid double free.
151 // X509_LOOKUP_free free up store object.
152 // Create an empty X509_STORE structure for certificate validation.
153 auto x509Store = X509_STORE_new();
154 if (!x509Store)
155 {
156 log<level::ERR>("Error occured during X509_STORE_new call");
157 elog<InternalFailure>();
158 }
159
160 OpenSSL_add_all_algorithms();
161
162 // ADD Certificate Lookup method.
163 X509_LOOKUP_Ptr lookup(X509_STORE_add_lookup(x509Store, X509_LOOKUP_file()),
164 ::X509_LOOKUP_free);
165 if (!lookup)
166 {
167 // Normally lookup cleanup function interanlly does X509Store cleanup
168 // Free up the X509Store.
169 X509_STORE_free(x509Store);
170 log<level::ERR>("Error occured during X509_STORE_add_lookup call");
171 elog<InternalFailure>();
172 }
173 // Load Certificate file.
174 errCode = X509_LOOKUP_load_file(lookup.get(), filePath.c_str(),
175 X509_FILETYPE_PEM);
176 if (errCode != 1)
177 {
178 log<level::ERR>("Error occured during X509_LOOKUP_load_file call",
179 entry("FILE=%s", filePath.c_str()));
180 elog<InvalidCertificate>(Reason("Invalid certificate file format"));
181 }
182
183 // Load Certificate file into the X509 structre.
184 X509_Ptr cert = std::move(loadCert(filePath));
185 X509_STORE_CTX_Ptr storeCtx(X509_STORE_CTX_new(), ::X509_STORE_CTX_free);
186 if (!storeCtx)
187 {
188 log<level::ERR>("Error occured during X509_STORE_CTX_new call",
189 entry("FILE=%s", filePath.c_str()));
190 elog<InternalFailure>();
191 }
192
193 errCode = X509_STORE_CTX_init(storeCtx.get(), x509Store, cert.get(), NULL);
194 if (errCode != 1)
195 {
196 log<level::ERR>("Error occured during X509_STORE_CTX_init call",
197 entry("FILE=%s", filePath.c_str()));
198 elog<InternalFailure>();
199 }
200
201 // Set time to current time.
202 auto locTime = time(nullptr);
203
204 X509_STORE_CTX_set_time(storeCtx.get(), X509_V_FLAG_USE_CHECK_TIME,
205 locTime);
206
207 errCode = X509_verify_cert(storeCtx.get());
208 if (errCode == 1)
209 {
210 errCode = X509_V_OK;
211 }
212 else if (errCode == 0)
213 {
214 errCode = X509_STORE_CTX_get_error(storeCtx.get());
215 log<level::ERR>("Certificate verification failed",
216 entry("FILE=%s", filePath.c_str()),
217 entry("ERRCODE=%d", errCode));
218 }
219 else
220 {
221 log<level::ERR>("Error occured during X509_verify_cert call",
222 entry("FILE=%s", filePath.c_str()));
223 elog<InternalFailure>();
224 }
225
226 // Allow certificate upload, for "certificate is not yet valid" and
227 // trust chain related errors.
228 if (!((errCode == X509_V_OK) ||
229 (errCode == X509_V_ERR_CERT_NOT_YET_VALID) ||
230 TRUST_CHAIN_ERR(errCode)))
231 {
232 if (errCode == X509_V_ERR_CERT_HAS_EXPIRED)
233 {
234 elog<InvalidCertificate>(Reason("Expired Certificate"));
235 }
236 // Loging general error here.
237 elog<InvalidCertificate>(Reason("Certificate validation failed"));
238 }
239
240 // Invoke type specific compare keys function.
241 auto iter = typeFuncMap.find(certType);
242 if (iter == typeFuncMap.end())
243 {
244 log<level::ERR>("Unsupported Type", entry("TYPE=%s", certType.c_str()));
245 elog<InternalFailure>();
246 }
247 iter->second(filePath);
248
Marri Devender Raoffad1ef2019-06-03 04:54:12 -0500249 // Copy the certificate to the installation path
250 // During bootup will be parsing existing file so no need to
251 // copy it.
252 if (filePath != certInstallPath)
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600253 {
Marri Devender Raoffad1ef2019-06-03 04:54:12 -0500254 std::ifstream inputCertFileStream;
255 std::ofstream outputCertFileStream;
256 inputCertFileStream.exceptions(std::ifstream::failbit |
257 std::ifstream::badbit |
258 std::ifstream::eofbit);
259 outputCertFileStream.exceptions(std::ofstream::failbit |
260 std::ofstream::badbit |
261 std::ofstream::eofbit);
262 try
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600263 {
Marri Devender Raoffad1ef2019-06-03 04:54:12 -0500264 inputCertFileStream.open(filePath);
265 outputCertFileStream.open(certInstallPath, std::ios::out);
266 outputCertFileStream << inputCertFileStream.rdbuf() << std::flush;
267 inputCertFileStream.close();
268 outputCertFileStream.close();
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600269 }
Marri Devender Raoffad1ef2019-06-03 04:54:12 -0500270 catch (const std::exception& e)
271 {
272 log<level::ERR>("Failed to copy certificate",
273 entry("ERR=%s", e.what()),
274 entry("SRC=%s", filePath.c_str()),
275 entry("DST=%s", certInstallPath.c_str()));
276 elog<InternalFailure>();
277 }
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600278 }
Marri Devender Rao8f80c352019-05-13 00:53:01 -0500279
280 if (!isSkipUnitReload)
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600281 {
Marri Devender Rao8f80c352019-05-13 00:53:01 -0500282 // restart the units
283 if (!unitToRestart.empty())
284 {
285 reloadOrReset(unitToRestart);
286 }
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600287 }
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600288
289 // Parse the certificate file and populate properties
290 populateProperties();
Marri Devender Raoffad1ef2019-06-03 04:54:12 -0500291
292 // restart watch
293 if (certWatchPtr)
294 {
295 certWatchPtr->startWatch();
296 }
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600297}
298
299void Certificate::populateProperties()
300{
301 X509_Ptr cert = std::move(loadCert(certInstallPath));
302 // Update properties if no error thrown
303 BIO_MEM_Ptr certBio(BIO_new(BIO_s_mem()), BIO_free);
304 PEM_write_bio_X509(certBio.get(), cert.get());
305 BUF_MEM_Ptr certBuf(BUF_MEM_new(), BUF_MEM_free);
306 BUF_MEM* buf = certBuf.get();
307 BIO_get_mem_ptr(certBio.get(), &buf);
308 std::string certStr(buf->data, buf->length);
309 CertificateIface::certificateString(certStr);
310
311 static const int maxKeySize = 4096;
312 char subBuffer[maxKeySize] = {0};
Marri Devender Raodec58772019-06-11 03:10:00 -0500313 BIO_MEM_Ptr subBio(BIO_new(BIO_s_mem()), BIO_free);
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600314 // This pointer cannot be freed independantly.
315 X509_NAME* sub = X509_get_subject_name(cert.get());
Marri Devender Raodec58772019-06-11 03:10:00 -0500316 X509_NAME_print_ex(subBio.get(), sub, 0, XN_FLAG_SEP_COMMA_PLUS);
317 BIO_read(subBio.get(), subBuffer, maxKeySize);
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600318 CertificateIface::subject(subBuffer);
319
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600320 char issuerBuffer[maxKeySize] = {0};
Marri Devender Raodec58772019-06-11 03:10:00 -0500321 BIO_MEM_Ptr issuerBio(BIO_new(BIO_s_mem()), BIO_free);
322 // This pointer cannot be freed independantly.
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600323 X509_NAME* issuer_name = X509_get_issuer_name(cert.get());
Marri Devender Raodec58772019-06-11 03:10:00 -0500324 X509_NAME_print_ex(issuerBio.get(), issuer_name, 0, XN_FLAG_SEP_COMMA_PLUS);
325 BIO_read(issuerBio.get(), issuerBuffer, maxKeySize);
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600326 CertificateIface::issuer(issuerBuffer);
327
328 std::vector<std::string> keyUsageList;
329 ASN1_BIT_STRING* usage;
330
331 // Go through each usage in the bit string and convert to
332 // corresponding string value
333 if ((usage = static_cast<ASN1_BIT_STRING*>(
334 X509_get_ext_d2i(cert.get(), NID_key_usage, NULL, NULL))))
335 {
336 for (auto i = 0; i < usage->length; ++i)
337 {
338 for (auto& x : keyUsageToRfStr)
339 {
340 if (x.first & usage->data[i])
341 {
342 keyUsageList.push_back(x.second);
343 break;
344 }
345 }
346 }
347 }
348
349 EXTENDED_KEY_USAGE* extUsage;
350 if ((extUsage = static_cast<EXTENDED_KEY_USAGE*>(
351 X509_get_ext_d2i(cert.get(), NID_ext_key_usage, NULL, NULL))))
352 {
353 for (int i = 0; i < sk_ASN1_OBJECT_num(extUsage); i++)
354 {
355 keyUsageList.push_back(extendedKeyUsageToRfStr[OBJ_obj2nid(
356 sk_ASN1_OBJECT_value(extUsage, i))]);
357 }
358 }
359 CertificateIface::keyUsage(keyUsageList);
360
361 int days = 0;
362 int secs = 0;
363
364 ASN1_TIME_ptr epoch(ASN1_TIME_new(), ASN1_STRING_free);
365 // Set time to 12:00am GMT, Jan 1 1970
366 ASN1_TIME_set_string(epoch.get(), "700101120000Z");
367
368 static const int dayToSeconds = 24 * 60 * 60;
369 ASN1_TIME* notAfter = X509_get_notAfter(cert.get());
370 ASN1_TIME_diff(&days, &secs, epoch.get(), notAfter);
371 CertificateIface::validNotAfter((days * dayToSeconds) + secs);
372
373 ASN1_TIME* notBefore = X509_get_notBefore(cert.get());
374 ASN1_TIME_diff(&days, &secs, epoch.get(), notBefore);
375 CertificateIface::validNotBefore((days * dayToSeconds) + secs);
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600376}
377
378X509_Ptr Certificate::loadCert(const std::string& filePath)
379{
380 log<level::INFO>("Certificate loadCert",
381 entry("FILEPATH=%s", filePath.c_str()));
382 // Read Certificate file
383 X509_Ptr cert(X509_new(), ::X509_free);
384 if (!cert)
385 {
386 log<level::ERR>("Error occured during X509_new call",
387 entry("FILE=%s", filePath.c_str()),
388 entry("ERRCODE=%lu", ERR_get_error()));
389 elog<InternalFailure>();
390 }
391
392 BIO_MEM_Ptr bioCert(BIO_new_file(filePath.c_str(), "rb"), ::BIO_free);
393 if (!bioCert)
394 {
395 log<level::ERR>("Error occured during BIO_new_file call",
396 entry("FILE=%s", filePath.c_str()));
397 elog<InternalFailure>();
398 }
399
400 X509* x509 = cert.get();
401 if (!PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr))
402 {
403 log<level::ERR>("Error occured during PEM_read_bio_X509 call",
404 entry("FILE=%s", filePath.c_str()));
405 elog<InternalFailure>();
406 }
407 return cert;
408}
409bool Certificate::compareKeys(const std::string& filePath)
410{
411 log<level::INFO>("Certificate compareKeys",
412 entry("FILEPATH=%s", filePath.c_str()));
413 X509_Ptr cert(X509_new(), ::X509_free);
414 if (!cert)
415 {
416 log<level::ERR>("Error occured during X509_new call",
417 entry("FILE=%s", filePath.c_str()),
418 entry("ERRCODE=%lu", ERR_get_error()));
419 elog<InternalFailure>();
420 }
421
422 BIO_MEM_Ptr bioCert(BIO_new_file(filePath.c_str(), "rb"), ::BIO_free);
423 if (!bioCert)
424 {
425 log<level::ERR>("Error occured during BIO_new_file call",
426 entry("FILE=%s", filePath.c_str()));
427 elog<InternalFailure>();
428 }
429
430 X509* x509 = cert.get();
431 PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr);
432
433 EVP_PKEY_Ptr pubKey(X509_get_pubkey(cert.get()), ::EVP_PKEY_free);
434 if (!pubKey)
435 {
436 log<level::ERR>("Error occurred during X509_get_pubkey",
437 entry("FILE=%s", filePath.c_str()),
438 entry("ERRCODE=%lu", ERR_get_error()));
439 elog<InvalidCertificate>(Reason("Failed to get public key info"));
440 }
441
442 BIO_MEM_Ptr keyBio(BIO_new(BIO_s_file()), ::BIO_free);
443 if (!keyBio)
444 {
445 log<level::ERR>("Error occured during BIO_s_file call",
446 entry("FILE=%s", filePath.c_str()));
447 elog<InternalFailure>();
448 }
449 BIO_read_filename(keyBio.get(), filePath.c_str());
450
451 EVP_PKEY_Ptr priKey(
452 PEM_read_bio_PrivateKey(keyBio.get(), nullptr, nullptr, nullptr),
453 ::EVP_PKEY_free);
454 if (!priKey)
455 {
456 log<level::ERR>("Error occurred during PEM_read_bio_PrivateKey",
457 entry("FILE=%s", filePath.c_str()),
458 entry("ERRCODE=%lu", ERR_get_error()));
459 elog<InvalidCertificate>(Reason("Failed to get private key info"));
460 }
461
462 int32_t rc = EVP_PKEY_cmp(priKey.get(), pubKey.get());
463 if (rc != 1)
464 {
465 log<level::ERR>("Private key is not matching with Certificate",
466 entry("FILE=%s", filePath.c_str()),
467 entry("ERRCODE=%d", rc));
468 return false;
469 }
470 return true;
471}
472
473void Certificate::reloadOrReset(const UnitsToRestart& unit)
474{
475 constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1";
476 constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1";
477 constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager";
478 try
479 {
480 auto method =
481 bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
482 SYSTEMD_INTERFACE, "ReloadOrRestartUnit");
483 method.append(unit, "replace");
484 bus.call_noreply(method);
485 }
486 catch (const sdbusplus::exception::SdBusError& e)
487 {
488 log<level::ERR>("Failed to reload or restart service",
489 entry("ERR=%s", e.what()),
490 entry("UNIT=%s", unit.c_str()));
491 elog<InternalFailure>();
492 }
493}
494} // namespace certs
495} // namespace phosphor