blob: 3ae85a17ad17d45cb7a9584ff48f4c66f079310b [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,
75 bool isSkipUnitReload) :
Marri Devender Raoedd11312019-02-27 08:45:10 -060076 CertIfaces(bus, objPath.c_str(), true),
77 bus(bus), objectPath(objPath), certType(type), unitToRestart(unit),
Marri Devender Rao6ceec402019-02-01 03:15:19 -060078 certInstallPath(installPath)
79{
80 auto installHelper = [this](const auto& filePath) {
81 if (!compareKeys(filePath))
82 {
83 elog<InvalidCertificate>(
84 Reason("Private key does not match the Certificate"));
85 };
86 };
87 typeFuncMap[SERVER] = installHelper;
88 typeFuncMap[CLIENT] = installHelper;
89 typeFuncMap[AUTHORITY] = [](auto filePath) {};
Marri Devender Rao8f80c352019-05-13 00:53:01 -050090 install(uploadPath, isSkipUnitReload);
Marri Devender Raoedd11312019-02-27 08:45:10 -060091 this->emit_object_added();
Marri Devender Rao6ceec402019-02-01 03:15:19 -060092}
93
94Certificate::~Certificate()
95{
96 if (!fs::remove(certInstallPath))
97 {
98 log<level::INFO>("Certificate file not found!",
99 entry("PATH=%s", certInstallPath.c_str()));
100 }
101 else if (!unitToRestart.empty())
102 {
103 reloadOrReset(unitToRestart);
104 }
105}
106
Marri Devender Rao13bf74e2019-03-26 01:52:17 -0500107void Certificate::replace(const std::string filePath)
108{
Marri Devender Rao8f80c352019-05-13 00:53:01 -0500109 install(filePath, false);
Marri Devender Rao13bf74e2019-03-26 01:52:17 -0500110}
111
Marri Devender Rao8f80c352019-05-13 00:53:01 -0500112void Certificate::install(const std::string& filePath, bool isSkipUnitReload)
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600113{
114 log<level::INFO>("Certificate install ",
115 entry("FILEPATH=%s", filePath.c_str()));
116 auto errCode = X509_V_OK;
117
118 // Verify the certificate file
119 fs::path file(filePath);
120 if (!fs::exists(file))
121 {
122 log<level::ERR>("File is Missing", entry("FILE=%s", filePath.c_str()));
123 elog<InternalFailure>();
124 }
125
126 try
127 {
128 if (fs::file_size(filePath) == 0)
129 {
130 // file is empty
131 log<level::ERR>("File is empty",
132 entry("FILE=%s", filePath.c_str()));
133 elog<InvalidCertificate>(Reason("File is empty"));
134 }
135 }
136 catch (const fs::filesystem_error& e)
137 {
138 // Log Error message
139 log<level::ERR>(e.what(), entry("FILE=%s", filePath.c_str()));
140 elog<InternalFailure>();
141 }
142
143 // Defining store object as RAW to avoid double free.
144 // X509_LOOKUP_free free up store object.
145 // Create an empty X509_STORE structure for certificate validation.
146 auto x509Store = X509_STORE_new();
147 if (!x509Store)
148 {
149 log<level::ERR>("Error occured during X509_STORE_new call");
150 elog<InternalFailure>();
151 }
152
153 OpenSSL_add_all_algorithms();
154
155 // ADD Certificate Lookup method.
156 X509_LOOKUP_Ptr lookup(X509_STORE_add_lookup(x509Store, X509_LOOKUP_file()),
157 ::X509_LOOKUP_free);
158 if (!lookup)
159 {
160 // Normally lookup cleanup function interanlly does X509Store cleanup
161 // Free up the X509Store.
162 X509_STORE_free(x509Store);
163 log<level::ERR>("Error occured during X509_STORE_add_lookup call");
164 elog<InternalFailure>();
165 }
166 // Load Certificate file.
167 errCode = X509_LOOKUP_load_file(lookup.get(), filePath.c_str(),
168 X509_FILETYPE_PEM);
169 if (errCode != 1)
170 {
171 log<level::ERR>("Error occured during X509_LOOKUP_load_file call",
172 entry("FILE=%s", filePath.c_str()));
173 elog<InvalidCertificate>(Reason("Invalid certificate file format"));
174 }
175
176 // Load Certificate file into the X509 structre.
177 X509_Ptr cert = std::move(loadCert(filePath));
178 X509_STORE_CTX_Ptr storeCtx(X509_STORE_CTX_new(), ::X509_STORE_CTX_free);
179 if (!storeCtx)
180 {
181 log<level::ERR>("Error occured during X509_STORE_CTX_new call",
182 entry("FILE=%s", filePath.c_str()));
183 elog<InternalFailure>();
184 }
185
186 errCode = X509_STORE_CTX_init(storeCtx.get(), x509Store, cert.get(), NULL);
187 if (errCode != 1)
188 {
189 log<level::ERR>("Error occured during X509_STORE_CTX_init call",
190 entry("FILE=%s", filePath.c_str()));
191 elog<InternalFailure>();
192 }
193
194 // Set time to current time.
195 auto locTime = time(nullptr);
196
197 X509_STORE_CTX_set_time(storeCtx.get(), X509_V_FLAG_USE_CHECK_TIME,
198 locTime);
199
200 errCode = X509_verify_cert(storeCtx.get());
201 if (errCode == 1)
202 {
203 errCode = X509_V_OK;
204 }
205 else if (errCode == 0)
206 {
207 errCode = X509_STORE_CTX_get_error(storeCtx.get());
208 log<level::ERR>("Certificate verification failed",
209 entry("FILE=%s", filePath.c_str()),
210 entry("ERRCODE=%d", errCode));
211 }
212 else
213 {
214 log<level::ERR>("Error occured during X509_verify_cert call",
215 entry("FILE=%s", filePath.c_str()));
216 elog<InternalFailure>();
217 }
218
219 // Allow certificate upload, for "certificate is not yet valid" and
220 // trust chain related errors.
221 if (!((errCode == X509_V_OK) ||
222 (errCode == X509_V_ERR_CERT_NOT_YET_VALID) ||
223 TRUST_CHAIN_ERR(errCode)))
224 {
225 if (errCode == X509_V_ERR_CERT_HAS_EXPIRED)
226 {
227 elog<InvalidCertificate>(Reason("Expired Certificate"));
228 }
229 // Loging general error here.
230 elog<InvalidCertificate>(Reason("Certificate validation failed"));
231 }
232
233 // Invoke type specific compare keys function.
234 auto iter = typeFuncMap.find(certType);
235 if (iter == typeFuncMap.end())
236 {
237 log<level::ERR>("Unsupported Type", entry("TYPE=%s", certType.c_str()));
238 elog<InternalFailure>();
239 }
240 iter->second(filePath);
241
242 // Copy thecertificate to the installation path
243 auto path = fs::path(certInstallPath).parent_path();
244 try
245 {
246 fs::create_directories(path);
247 // During bootup will be parsing existing file so no need to
248 // copy it.
249 if (filePath != certInstallPath)
250 {
251 fs::copy_file(filePath, certInstallPath,
252 fs::copy_options::overwrite_existing);
253 }
254 }
255 catch (fs::filesystem_error& e)
256 {
257 log<level::ERR>("Failed to copy certificate", entry("ERR=%s", e.what()),
258 entry("SRC=%s", filePath.c_str()),
259 entry("DST=%s", certInstallPath.c_str()));
260 elog<InternalFailure>();
261 }
Marri Devender Rao8f80c352019-05-13 00:53:01 -0500262
263 if (!isSkipUnitReload)
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600264 {
Marri Devender Rao8f80c352019-05-13 00:53:01 -0500265 // restart the units
266 if (!unitToRestart.empty())
267 {
268 reloadOrReset(unitToRestart);
269 }
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600270 }
Dhruvaraj Subhashchandran36f25142019-02-14 05:06:26 -0600271
272 // Parse the certificate file and populate properties
273 populateProperties();
274}
275
276void Certificate::populateProperties()
277{
278 X509_Ptr cert = std::move(loadCert(certInstallPath));
279 // Update properties if no error thrown
280 BIO_MEM_Ptr certBio(BIO_new(BIO_s_mem()), BIO_free);
281 PEM_write_bio_X509(certBio.get(), cert.get());
282 BUF_MEM_Ptr certBuf(BUF_MEM_new(), BUF_MEM_free);
283 BUF_MEM* buf = certBuf.get();
284 BIO_get_mem_ptr(certBio.get(), &buf);
285 std::string certStr(buf->data, buf->length);
286 CertificateIface::certificateString(certStr);
287
288 static const int maxKeySize = 4096;
289 char subBuffer[maxKeySize] = {0};
290 // This pointer cannot be freed independantly.
291 X509_NAME* sub = X509_get_subject_name(cert.get());
292 X509_NAME_print_ex(certBio.get(), sub, 0, 0);
293 BIO_read(certBio.get(), subBuffer, maxKeySize);
294 CertificateIface::subject(subBuffer);
295
296 // This pointer cannot be freed independantly.
297 char issuerBuffer[maxKeySize] = {0};
298 X509_NAME* issuer_name = X509_get_issuer_name(cert.get());
299 X509_NAME_print_ex(certBio.get(), issuer_name, 0, 0);
300 BIO_read(certBio.get(), issuerBuffer, maxKeySize);
301 CertificateIface::issuer(issuerBuffer);
302
303 std::vector<std::string> keyUsageList;
304 ASN1_BIT_STRING* usage;
305
306 // Go through each usage in the bit string and convert to
307 // corresponding string value
308 if ((usage = static_cast<ASN1_BIT_STRING*>(
309 X509_get_ext_d2i(cert.get(), NID_key_usage, NULL, NULL))))
310 {
311 for (auto i = 0; i < usage->length; ++i)
312 {
313 for (auto& x : keyUsageToRfStr)
314 {
315 if (x.first & usage->data[i])
316 {
317 keyUsageList.push_back(x.second);
318 break;
319 }
320 }
321 }
322 }
323
324 EXTENDED_KEY_USAGE* extUsage;
325 if ((extUsage = static_cast<EXTENDED_KEY_USAGE*>(
326 X509_get_ext_d2i(cert.get(), NID_ext_key_usage, NULL, NULL))))
327 {
328 for (int i = 0; i < sk_ASN1_OBJECT_num(extUsage); i++)
329 {
330 keyUsageList.push_back(extendedKeyUsageToRfStr[OBJ_obj2nid(
331 sk_ASN1_OBJECT_value(extUsage, i))]);
332 }
333 }
334 CertificateIface::keyUsage(keyUsageList);
335
336 int days = 0;
337 int secs = 0;
338
339 ASN1_TIME_ptr epoch(ASN1_TIME_new(), ASN1_STRING_free);
340 // Set time to 12:00am GMT, Jan 1 1970
341 ASN1_TIME_set_string(epoch.get(), "700101120000Z");
342
343 static const int dayToSeconds = 24 * 60 * 60;
344 ASN1_TIME* notAfter = X509_get_notAfter(cert.get());
345 ASN1_TIME_diff(&days, &secs, epoch.get(), notAfter);
346 CertificateIface::validNotAfter((days * dayToSeconds) + secs);
347
348 ASN1_TIME* notBefore = X509_get_notBefore(cert.get());
349 ASN1_TIME_diff(&days, &secs, epoch.get(), notBefore);
350 CertificateIface::validNotBefore((days * dayToSeconds) + secs);
Marri Devender Rao6ceec402019-02-01 03:15:19 -0600351}
352
353X509_Ptr Certificate::loadCert(const std::string& filePath)
354{
355 log<level::INFO>("Certificate loadCert",
356 entry("FILEPATH=%s", filePath.c_str()));
357 // Read Certificate file
358 X509_Ptr cert(X509_new(), ::X509_free);
359 if (!cert)
360 {
361 log<level::ERR>("Error occured during X509_new call",
362 entry("FILE=%s", filePath.c_str()),
363 entry("ERRCODE=%lu", ERR_get_error()));
364 elog<InternalFailure>();
365 }
366
367 BIO_MEM_Ptr bioCert(BIO_new_file(filePath.c_str(), "rb"), ::BIO_free);
368 if (!bioCert)
369 {
370 log<level::ERR>("Error occured during BIO_new_file call",
371 entry("FILE=%s", filePath.c_str()));
372 elog<InternalFailure>();
373 }
374
375 X509* x509 = cert.get();
376 if (!PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr))
377 {
378 log<level::ERR>("Error occured during PEM_read_bio_X509 call",
379 entry("FILE=%s", filePath.c_str()));
380 elog<InternalFailure>();
381 }
382 return cert;
383}
384bool Certificate::compareKeys(const std::string& filePath)
385{
386 log<level::INFO>("Certificate compareKeys",
387 entry("FILEPATH=%s", filePath.c_str()));
388 X509_Ptr cert(X509_new(), ::X509_free);
389 if (!cert)
390 {
391 log<level::ERR>("Error occured during X509_new call",
392 entry("FILE=%s", filePath.c_str()),
393 entry("ERRCODE=%lu", ERR_get_error()));
394 elog<InternalFailure>();
395 }
396
397 BIO_MEM_Ptr bioCert(BIO_new_file(filePath.c_str(), "rb"), ::BIO_free);
398 if (!bioCert)
399 {
400 log<level::ERR>("Error occured during BIO_new_file call",
401 entry("FILE=%s", filePath.c_str()));
402 elog<InternalFailure>();
403 }
404
405 X509* x509 = cert.get();
406 PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr);
407
408 EVP_PKEY_Ptr pubKey(X509_get_pubkey(cert.get()), ::EVP_PKEY_free);
409 if (!pubKey)
410 {
411 log<level::ERR>("Error occurred during X509_get_pubkey",
412 entry("FILE=%s", filePath.c_str()),
413 entry("ERRCODE=%lu", ERR_get_error()));
414 elog<InvalidCertificate>(Reason("Failed to get public key info"));
415 }
416
417 BIO_MEM_Ptr keyBio(BIO_new(BIO_s_file()), ::BIO_free);
418 if (!keyBio)
419 {
420 log<level::ERR>("Error occured during BIO_s_file call",
421 entry("FILE=%s", filePath.c_str()));
422 elog<InternalFailure>();
423 }
424 BIO_read_filename(keyBio.get(), filePath.c_str());
425
426 EVP_PKEY_Ptr priKey(
427 PEM_read_bio_PrivateKey(keyBio.get(), nullptr, nullptr, nullptr),
428 ::EVP_PKEY_free);
429 if (!priKey)
430 {
431 log<level::ERR>("Error occurred during PEM_read_bio_PrivateKey",
432 entry("FILE=%s", filePath.c_str()),
433 entry("ERRCODE=%lu", ERR_get_error()));
434 elog<InvalidCertificate>(Reason("Failed to get private key info"));
435 }
436
437 int32_t rc = EVP_PKEY_cmp(priKey.get(), pubKey.get());
438 if (rc != 1)
439 {
440 log<level::ERR>("Private key is not matching with Certificate",
441 entry("FILE=%s", filePath.c_str()),
442 entry("ERRCODE=%d", rc));
443 return false;
444 }
445 return true;
446}
447
448void Certificate::reloadOrReset(const UnitsToRestart& unit)
449{
450 constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1";
451 constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1";
452 constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager";
453 try
454 {
455 auto method =
456 bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
457 SYSTEMD_INTERFACE, "ReloadOrRestartUnit");
458 method.append(unit, "replace");
459 bus.call_noreply(method);
460 }
461 catch (const sdbusplus::exception::SdBusError& e)
462 {
463 log<level::ERR>("Failed to reload or restart service",
464 entry("ERR=%s", e.what()),
465 entry("UNIT=%s", unit.c_str()));
466 elog<InternalFailure>();
467 }
468}
469} // namespace certs
470} // namespace phosphor