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