blob: 38f77c22a819cdd3dbc342eb43c9b445fa52dad0 [file] [log] [blame]
Jayanth Othayothcfbc8dc2018-09-03 07:22:27 -05001#include "certs_manager.hpp"
2
Marri Devender Raof4682712019-03-19 05:00:28 -05003#include <openssl/pem.h>
4#include <unistd.h>
5
Marri Devender Rao6ceec402019-02-01 03:15:19 -06006#include <phosphor-logging/elog-errors.hpp>
Marri Devender Rao13bf74e2019-03-26 01:52:17 -05007#include <xyz/openbmc_project/Certs/error.hpp>
Jayanth Othayothcfbc8dc2018-09-03 07:22:27 -05008#include <xyz/openbmc_project/Common/error.hpp>
Jayanth Othayothcfbc8dc2018-09-03 07:22:27 -05009namespace phosphor
10{
11namespace certs
12{
Marri Devender Rao13965112019-02-27 08:47:12 -060013using InternalFailure =
14 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
Jayanth Othayothcfbc8dc2018-09-03 07:22:27 -050015
Marri Devender Raof4682712019-03-19 05:00:28 -050016using X509_REQ_Ptr = std::unique_ptr<X509_REQ, decltype(&::X509_REQ_free)>;
17using BIGNUM_Ptr = std::unique_ptr<BIGNUM, decltype(&::BN_free)>;
18
19Manager::Manager(sdbusplus::bus::bus& bus, sdeventplus::Event& event,
20 const char* path, const CertificateType& type,
21 UnitsToRestart&& unit, CertInstallPath&& installPath) :
Marri Devender Rao6ceec402019-02-01 03:15:19 -060022 Ifaces(bus, path),
Marri Devender Raof4682712019-03-19 05:00:28 -050023 bus(bus), event(event), objectPath(path), certType(type),
24 unitToRestart(std::move(unit)), certInstallPath(std::move(installPath)),
25 childPtr(nullptr)
Jayanth Othayothcfbc8dc2018-09-03 07:22:27 -050026{
Marri Devender Rao13bf74e2019-03-26 01:52:17 -050027 using InvalidCertificate =
28 sdbusplus::xyz::openbmc_project::Certs::Error::InvalidCertificate;
29 using Reason = xyz::openbmc_project::Certs::InvalidCertificate::REASON;
Marri Devender Raobf7c5882019-02-27 08:41:07 -060030 if (fs::exists(certInstallPath))
31 {
32 try
33 {
34 // TODO: Issue#3 At present supporting only one certificate to be
35 // uploaded this need to be revisited to support multiple
36 // certificates
37 auto certObjectPath = objectPath + '/' + '1';
38 certificatePtr = std::make_unique<Certificate>(
39 bus, certObjectPath, certType, unitToRestart, certInstallPath,
Marri Devender Rao8f80c352019-05-13 00:53:01 -050040 certInstallPath, true);
Marri Devender Raobf7c5882019-02-27 08:41:07 -060041 }
42 catch (const InternalFailure& e)
43 {
Marri Devender Raobf7c5882019-02-27 08:41:07 -060044 report<InternalFailure>();
45 }
46 catch (const InvalidCertificate& e)
47 {
Marri Devender Raobf7c5882019-02-27 08:41:07 -060048 report<InvalidCertificate>(
49 Reason("Existing certificate file is corrupted"));
50 }
51 }
Jayanth Othayothcfbc8dc2018-09-03 07:22:27 -050052}
53
Marri Devender Rao6ceec402019-02-01 03:15:19 -060054void Manager::install(const std::string filePath)
Jayanth Othayothcfbc8dc2018-09-03 07:22:27 -050055{
Marri Devender Rao13965112019-02-27 08:47:12 -060056 using NotAllowed =
57 sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed;
58 using Reason = xyz::openbmc_project::Common::NotAllowed::REASON;
59 // TODO: Issue#3 At present supporting only one certificate to be
60 // uploaded this need to be revisited to support multiple
61 // certificates
62 if (certificatePtr != nullptr)
63 {
64 elog<NotAllowed>(Reason("Certificate already exist"));
65 }
66 auto certObjectPath = objectPath + '/' + '1';
Marri Devender Rao8f80c352019-05-13 00:53:01 -050067 certificatePtr = std::make_unique<Certificate>(
68 bus, certObjectPath, certType, unitToRestart, certInstallPath, filePath,
69 false);
Jayanth Othayoth589159f2018-09-28 08:32:39 -050070}
Deepak Kodihalliae70b3d2018-09-30 05:42:00 -050071
72void Manager::delete_()
73{
Marri Devender Rao6ceec402019-02-01 03:15:19 -060074 // TODO: #Issue 4 when a certificate is deleted system auto generates
75 // certificate file. At present we are not supporting creation of
76 // certificate object for the auto-generated certificate file as
77 // deletion if only applicable for REST server and Bmcweb does not allow
78 // deletion of certificates
79 if (certificatePtr != nullptr)
Deepak Kodihalliae70b3d2018-09-30 05:42:00 -050080 {
Marri Devender Rao6ceec402019-02-01 03:15:19 -060081 certificatePtr.reset(nullptr);
Deepak Kodihalliae70b3d2018-09-30 05:42:00 -050082 }
83}
Marri Devender Raof4682712019-03-19 05:00:28 -050084
85std::string Manager::generateCSR(
86 std::vector<std::string> alternativeNames, std::string challengePassword,
87 std::string city, std::string commonName, std::string contactPerson,
88 std::string country, std::string email, std::string givenName,
89 std::string initials, int64_t keyBitLength, std::string keyCurveId,
90 std::string keyPairAlgorithm, std::vector<std::string> keyUsage,
91 std::string organization, std::string organizationalUnit, std::string state,
92 std::string surname, std::string unstructuredName)
93{
94 // We support only one CSR.
95 csrPtr.reset(nullptr);
96 auto pid = fork();
97 if (pid == -1)
98 {
99 log<level::ERR>("Error occurred during forking process");
100 report<InternalFailure>();
101 }
102 else if (pid == 0)
103 {
104 try
105 {
106 generateCSRHelper(alternativeNames, challengePassword, city,
107 commonName, contactPerson, country, email,
108 givenName, initials, keyBitLength, keyCurveId,
109 keyPairAlgorithm, keyUsage, organization,
110 organizationalUnit, state, surname,
111 unstructuredName);
112 exit(EXIT_SUCCESS);
113 }
114 catch (const InternalFailure& e)
115 {
116 // commit the error reported in child process and exit
117 // Callback method from SDEvent Loop looks for exit status
118 exit(EXIT_FAILURE);
119 commit<InternalFailure>();
120 }
121 }
122 else
123 {
124 using namespace sdeventplus::source;
125 Child::Callback callback = [this](Child& eventSource,
126 const siginfo_t* si) {
127 eventSource.set_enabled(Enabled::On);
128 if (si->si_status != 0)
129 {
130 this->createCSRObject(Status::FAILURE);
131 }
132 else
133 {
134 this->createCSRObject(Status::SUCCESS);
135 }
136 };
137 try
138 {
139 sigset_t ss;
140 if (sigemptyset(&ss) < 0)
141 {
142 log<level::ERR>("Unable to initialize signal set");
143 elog<InternalFailure>();
144 }
145 if (sigaddset(&ss, SIGCHLD) < 0)
146 {
147 log<level::ERR>("Unable to add signal to signal set");
148 elog<InternalFailure>();
149 }
150
151 // Block SIGCHLD first, so that the event loop can handle it
152 if (sigprocmask(SIG_BLOCK, &ss, NULL) < 0)
153 {
154 log<level::ERR>("Unable to block signal");
155 elog<InternalFailure>();
156 }
157 if (childPtr)
158 {
159 childPtr.reset();
160 }
161 childPtr = std::make_unique<Child>(event, pid, WEXITED | WSTOPPED,
162 std::move(callback));
163 }
164 catch (const InternalFailure& e)
165 {
166 commit<InternalFailure>();
167 }
168 }
169 auto csrObjectPath = objectPath + '/' + "csr";
170 return csrObjectPath;
171}
172
173void Manager::generateCSRHelper(
174 std::vector<std::string> alternativeNames, std::string challengePassword,
175 std::string city, std::string commonName, std::string contactPerson,
176 std::string country, std::string email, std::string givenName,
177 std::string initials, int64_t keyBitLength, std::string keyCurveId,
178 std::string keyPairAlgorithm, std::vector<std::string> keyUsage,
179 std::string organization, std::string organizationalUnit, std::string state,
180 std::string surname, std::string unstructuredName)
181{
182 int ret = 0;
183
184 // set version of x509 req
185 int nVersion = 1;
186 // TODO: Issue#6 need to make version number configurable
187 X509_REQ_Ptr x509Req(X509_REQ_new(), ::X509_REQ_free);
188 ret = X509_REQ_set_version(x509Req.get(), nVersion);
189 if (ret == 0)
190 {
191 log<level::ERR>("Error occured during X509_REQ_set_version call");
192 elog<InternalFailure>();
193 }
194
195 // set subject of x509 req
196 X509_NAME* x509Name = X509_REQ_get_subject_name(x509Req.get());
197
198 if (!alternativeNames.empty())
199 {
200 for (auto& name : alternativeNames)
201 {
202 addEntry(x509Name, "subjectAltName", name);
203 }
204 }
205 addEntry(x509Name, "challengePassword", challengePassword);
206 addEntry(x509Name, "L", city);
207 addEntry(x509Name, "CN", commonName);
208 addEntry(x509Name, "name", contactPerson);
209 addEntry(x509Name, "C", country);
210 addEntry(x509Name, "emailAddress", email);
211 addEntry(x509Name, "GN", givenName);
212 addEntry(x509Name, "initials", initials);
213 addEntry(x509Name, "algorithm", keyPairAlgorithm);
214 if (!keyUsage.empty())
215 {
216 for (auto& usage : keyUsage)
217 {
218 addEntry(x509Name, "keyUsage", usage);
219 }
220 }
221 addEntry(x509Name, "O", organization);
222 addEntry(x509Name, "ST", state);
223 addEntry(x509Name, "SN", surname);
224 addEntry(x509Name, "unstructuredName", unstructuredName);
225
Ramesh Iyyar8a09b522019-06-07 05:23:29 -0500226 EVP_PKEY_Ptr pKey(nullptr, ::EVP_PKEY_free);
227
228 log<level::INFO>("Given Key pair algorithm",
229 entry("KEYPAIRALGORITHM=%s", keyPairAlgorithm.c_str()));
230
231 // Used EC algorithm as default if user did not give algorithm type.
232 if (keyPairAlgorithm == "RSA")
233 pKey = std::move(generateRSAKeyPair(keyBitLength));
234 else if ((keyPairAlgorithm == "EC") || (keyPairAlgorithm.empty()))
235 pKey = std::move(generateECKeyPair(keyCurveId));
236 else
237 {
238 using InvalidArgument =
239 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
240 using Argument = xyz::openbmc_project::Common::InvalidArgument;
241
242 log<level::ERR>("Given Key pair algorithm is not supported. Supporting "
243 "RSA and EC only");
244 elog<InvalidArgument>(
245 Argument::ARGUMENT_NAME("KEYPAIRALGORITHM"),
246 Argument::ARGUMENT_VALUE(keyPairAlgorithm.c_str()));
247 }
248
249 ret = X509_REQ_set_pubkey(x509Req.get(), pKey.get());
250 if (ret == 0)
251 {
252 log<level::ERR>("Error occured while setting Public key");
253 elog<InternalFailure>();
254 }
255
256 // Write private key to file
257 writePrivateKey(pKey);
Marri Devender Raof4682712019-03-19 05:00:28 -0500258
259 // set sign key of x509 req
260 ret = X509_REQ_sign(x509Req.get(), pKey.get(), EVP_sha256());
Ramesh Iyyar8a09b522019-06-07 05:23:29 -0500261 if (ret == 0)
Marri Devender Raof4682712019-03-19 05:00:28 -0500262 {
263 log<level::ERR>("Error occured while signing key of x509");
264 elog<InternalFailure>();
265 }
Ramesh Iyyar8a09b522019-06-07 05:23:29 -0500266
Marri Devender Raof4682712019-03-19 05:00:28 -0500267 log<level::INFO>("Writing CSR to file");
268 std::string path = fs::path(certInstallPath).parent_path();
269 std::string csrFilePath = path + '/' + CSR_FILE_NAME;
270 writeCSR(csrFilePath, x509Req);
271}
272
Ramesh Iyyar8a09b522019-06-07 05:23:29 -0500273EVP_PKEY_Ptr Manager::generateRSAKeyPair(const int64_t keyBitLength)
Marri Devender Raof4682712019-03-19 05:00:28 -0500274{
275 int ret = 0;
276 // generate rsa key
277 BIGNUM_Ptr bne(BN_new(), ::BN_free);
278 ret = BN_set_word(bne.get(), RSA_F4);
279 if (ret == 0)
280 {
281 log<level::ERR>("Error occured during BN_set_word call");
282 elog<InternalFailure>();
283 }
284
Ramesh Iyyar8a09b522019-06-07 05:23:29 -0500285 int64_t keyBitLen = keyBitLength;
Marri Devender Raof4682712019-03-19 05:00:28 -0500286 // set keybit length to default value if not set
Ramesh Iyyar8a09b522019-06-07 05:23:29 -0500287 if (keyBitLen <= 0)
Marri Devender Raof4682712019-03-19 05:00:28 -0500288 {
Ramesh Iyyar8a09b522019-06-07 05:23:29 -0500289 constexpr auto DEFAULT_KEYBITLENGTH = 2048;
290 log<level::INFO>(
291 "KeyBitLength is not given.Hence, using default KeyBitLength",
292 entry("DEFAULTKEYBITLENGTH=%d", DEFAULT_KEYBITLENGTH));
293 keyBitLen = DEFAULT_KEYBITLENGTH;
Marri Devender Raof4682712019-03-19 05:00:28 -0500294 }
295 RSA* rsa = RSA_new();
Ramesh Iyyar8a09b522019-06-07 05:23:29 -0500296 ret = RSA_generate_key_ex(rsa, keyBitLen, bne.get(), NULL);
Marri Devender Raof4682712019-03-19 05:00:28 -0500297 if (ret != 1)
298 {
299 free(rsa);
300 log<level::ERR>("Error occured during RSA_generate_key_ex call",
Ramesh Iyyar8a09b522019-06-07 05:23:29 -0500301 entry("KEYBITLENGTH=%PRIu64", keyBitLen));
Marri Devender Raof4682712019-03-19 05:00:28 -0500302 elog<InternalFailure>();
303 }
304
305 // set public key of x509 req
306 EVP_PKEY_Ptr pKey(EVP_PKEY_new(), ::EVP_PKEY_free);
Ramesh Iyyar8a09b522019-06-07 05:23:29 -0500307 ret = EVP_PKEY_assign_RSA(pKey.get(), rsa);
Marri Devender Raof4682712019-03-19 05:00:28 -0500308 if (ret == 0)
309 {
Ramesh Iyyar8a09b522019-06-07 05:23:29 -0500310 free(rsa);
311 log<level::ERR>("Error occured during assign rsa key into EVP");
Marri Devender Raof4682712019-03-19 05:00:28 -0500312 elog<InternalFailure>();
313 }
314
Ramesh Iyyar8a09b522019-06-07 05:23:29 -0500315 return pKey;
316}
317
318EVP_PKEY_Ptr Manager::generateECKeyPair(const std::string& curveId)
319{
320 std::string curId(curveId);
321
322 if (curId.empty())
323 {
324 // secp224r1 is equal to RSA 2048 KeyBitLength. Refer RFC 5349
325 constexpr auto DEFAULT_KEYCURVEID = "secp224r1";
326 log<level::INFO>(
327 "KeyCurveId is not given. Hence using default curve id",
328 entry("DEFAULTKEYCURVEID=%s", DEFAULT_KEYCURVEID));
329 curId = DEFAULT_KEYCURVEID;
330 }
331
332 int ecGrp = OBJ_txt2nid(curId.c_str());
333
334 if (ecGrp == NID_undef)
335 {
336 log<level::ERR>(
337 "Error occured during convert the curve id string format into NID",
338 entry("KEYCURVEID=%s", curId.c_str()));
339 elog<InternalFailure>();
340 }
341
342 EC_KEY* ecKey = EC_KEY_new_by_curve_name(ecGrp);
343
344 if (ecKey == NULL)
345 {
346 log<level::ERR>(
347 "Error occured during create the EC_Key object from NID",
348 entry("ECGROUP=%d", ecGrp));
349 elog<InternalFailure>();
350 }
351
352 // If you want to save a key and later load it with
353 // SSL_CTX_use_PrivateKey_file, then you must set the OPENSSL_EC_NAMED_CURVE
354 // flag on the key.
355 EC_KEY_set_asn1_flag(ecKey, OPENSSL_EC_NAMED_CURVE);
356
357 int ret = EC_KEY_generate_key(ecKey);
358
359 if (ret == 0)
360 {
361 EC_KEY_free(ecKey);
362 log<level::ERR>("Error occured during generate EC key");
363 elog<InternalFailure>();
364 }
365
366 EVP_PKEY_Ptr pKey(EVP_PKEY_new(), ::EVP_PKEY_free);
367 ret = EVP_PKEY_assign_EC_KEY(pKey.get(), ecKey);
368 if (ret == 0)
369 {
370 EC_KEY_free(ecKey);
371 log<level::ERR>("Error occured during assign EC Key into EVP");
372 elog<InternalFailure>();
373 }
374
375 return pKey;
376}
377
378void Manager::writePrivateKey(const EVP_PKEY_Ptr& pKey)
379{
380 log<level::INFO>("Writing private key to file");
Marri Devender Raof4682712019-03-19 05:00:28 -0500381 // write private key to file
382 std::string path = fs::path(certInstallPath).parent_path();
383 std::string privKeyPath = path + '/' + PRIV_KEY_FILE_NAME;
384
385 FILE* fp = std::fopen(privKeyPath.c_str(), "w");
386 if (fp == NULL)
387 {
Marri Devender Raof4682712019-03-19 05:00:28 -0500388 log<level::ERR>("Error occured creating private key file");
389 elog<InternalFailure>();
390 }
Ramesh Iyyar8a09b522019-06-07 05:23:29 -0500391 int ret = PEM_write_PrivateKey(fp, pKey.get(), NULL, NULL, 0, 0, NULL);
Marri Devender Raof4682712019-03-19 05:00:28 -0500392 std::fclose(fp);
393 if (ret == 0)
394 {
395 log<level::ERR>("Error occured while writing private key to file");
396 elog<InternalFailure>();
397 }
Marri Devender Raof4682712019-03-19 05:00:28 -0500398}
399
400void Manager::addEntry(X509_NAME* x509Name, const char* field,
401 const std::string& bytes)
402{
403 if (bytes.empty())
404 {
405 return;
406 }
407 int ret = X509_NAME_add_entry_by_txt(
408 x509Name, field, MBSTRING_ASC,
409 reinterpret_cast<const unsigned char*>(bytes.c_str()), -1, -1, 0);
410 if (ret != 1)
411 {
412 log<level::ERR>("Unable to set entry", entry("FIELD=%s", field),
413 entry("VALUE=%s", bytes.c_str()));
414 elog<InternalFailure>();
415 }
416}
417
418void Manager::createCSRObject(const Status& status)
419{
420 if (csrPtr)
421 {
422 csrPtr.reset(nullptr);
423 }
424 auto csrObjectPath = objectPath + '/' + "csr";
425 csrPtr = std::make_unique<CSR>(bus, csrObjectPath.c_str(),
426 certInstallPath.c_str(), status);
427}
428
429void Manager::writeCSR(const std::string& filePath, const X509_REQ_Ptr& x509Req)
430{
431 if (fs::exists(filePath))
432 {
433 log<level::INFO>("Removing the existing file",
434 entry("FILENAME=%s", filePath.c_str()));
435 if (!fs::remove(filePath.c_str()))
436 {
437 log<level::ERR>("Unable to remove the file",
438 entry("FILENAME=%s", filePath.c_str()));
439 elog<InternalFailure>();
440 }
441 }
442
443 FILE* fp = NULL;
444
445 if ((fp = std::fopen(filePath.c_str(), "w")) == NULL)
446 {
447 log<level::ERR>("Error opening the file to write the CSR",
448 entry("FILENAME=%s", filePath.c_str()));
449 elog<InternalFailure>();
450 }
451
452 int rc = PEM_write_X509_REQ(fp, x509Req.get());
453 if (!rc)
454 {
455 log<level::ERR>("PEM write routine failed",
456 entry("FILENAME=%s", filePath.c_str()));
457 std::fclose(fp);
458 elog<InternalFailure>();
459 }
460 std::fclose(fp);
461}
462
Jayanth Othayothcfbc8dc2018-09-03 07:22:27 -0500463} // namespace certs
464} // namespace phosphor