blob: a4f9116d64d485c9b057e8ba9b6010d16e8a0567 [file] [log] [blame]
Jayanth Othayothcfbc8dc2018-09-03 07:22:27 -05001#include "certs_manager.hpp"
2
Jayanth Othayothdd74bd22018-09-28 06:13:35 -05003#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
Jayanth Othayothcfbc8dc2018-09-03 07:22:27 -050010#include <experimental/filesystem>
11#include <phosphor-logging/elog-errors.hpp>
12#include <phosphor-logging/elog.hpp>
13#include <phosphor-logging/log.hpp>
14#include <sdbusplus/bus.hpp>
Jayanth Othayothdd74bd22018-09-28 06:13:35 -050015#include <xyz/openbmc_project/Certs/Install/error.hpp>
Jayanth Othayothcfbc8dc2018-09-03 07:22:27 -050016#include <xyz/openbmc_project/Common/error.hpp>
17
18namespace phosphor
19{
20namespace certs
21{
Jayanth Othayothdd74bd22018-09-28 06:13:35 -050022// RAII support for openSSL functions.
23using BIO_MEM_Ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
24using X509_STORE_CTX_Ptr =
25 std::unique_ptr<X509_STORE_CTX, decltype(&::X509_STORE_CTX_free)>;
26using X509_LOOKUP_Ptr =
27 std::unique_ptr<X509_LOOKUP, decltype(&::X509_LOOKUP_free)>;
Jayanth Othayoth589159f2018-09-28 08:32:39 -050028using EVP_PKEY_Ptr = std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>;
Jayanth Othayothcfbc8dc2018-09-03 07:22:27 -050029
Jayanth Othayothdd74bd22018-09-28 06:13:35 -050030namespace fs = std::experimental::filesystem;
Jayanth Othayothcfbc8dc2018-09-03 07:22:27 -050031using namespace phosphor::logging;
32using InternalFailure =
33 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
Jayanth Othayothdd74bd22018-09-28 06:13:35 -050034using InvalidCertificate =
35 sdbusplus::xyz::openbmc_project::Certs::Install::Error::InvalidCertificate;
36using Reason = xyz::openbmc_project::Certs::Install::InvalidCertificate::REASON;
Jayanth Othayothcfbc8dc2018-09-03 07:22:27 -050037
38void Manager::install(const std::string path)
39{
Jayanth Othayothdd74bd22018-09-28 06:13:35 -050040 // Verify the certificate file
41 auto rc = verifyCert(path);
Jayanth Othayoth62ccb1f2018-10-03 05:57:45 -050042 // Allow certificate upload, for "certificate is not yet valid" case.
43 if (!((rc == X509_V_OK) || (rc == X509_V_ERR_CERT_NOT_YET_VALID)))
Jayanth Othayothdd74bd22018-09-28 06:13:35 -050044 {
45 if (rc == X509_V_ERR_CERT_HAS_EXPIRED)
46 {
47 elog<InvalidCertificate>(Reason("Expired Certificate"));
48 }
49 // Loging general error here.
50 elog<InvalidCertificate>(Reason("Certificate validation failed"));
51 }
Jayanth Othayoth589159f2018-09-28 08:32:39 -050052
53 // Compare the Keys
54 if (!compareKeys(path))
55 {
56 elog<InvalidCertificate>(
57 Reason("Private key is not matching with Certificate"));
58 }
59
Jayanth Othayothcfbc8dc2018-09-03 07:22:27 -050060 // Copy the certificate file
61 copy(path, certPath);
62
63 // Invoke type specific install function.
64 auto iter = typeFuncMap.find(type);
65 if (iter == typeFuncMap.end())
66 {
67 log<level::ERR>("Unsupported Type", entry("TYPE=%s", type.c_str()));
68 elog<InternalFailure>();
69 }
70 iter->second();
71}
72
73void Manager::serverInstall()
74{
75 if (!unit.empty())
76 {
Jayanth Othayothe8199a82018-09-29 00:46:10 -050077 reloadOrReset(unit);
Jayanth Othayothcfbc8dc2018-09-03 07:22:27 -050078 }
79}
80
81void Manager::clientInstall()
82{
Jayanth Othayothe8199a82018-09-29 00:46:10 -050083 if (!unit.empty())
84 {
85 reloadOrReset(unit);
86 }
Jayanth Othayothcfbc8dc2018-09-03 07:22:27 -050087}
88
Jayanth Othayothe8199a82018-09-29 00:46:10 -050089void Manager::reloadOrReset(const std::string& unit)
Jayanth Othayothcfbc8dc2018-09-03 07:22:27 -050090{
91 constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1";
92 constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1";
93 constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager";
94
95 try
96 {
Jayanth Othayothe8199a82018-09-29 00:46:10 -050097 auto method =
98 bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
99 SYSTEMD_INTERFACE, "ReloadOrRestartUnit");
Jayanth Othayothcfbc8dc2018-09-03 07:22:27 -0500100
101 method.append(unit, "replace");
102
103 bus.call_noreply(method);
104 }
105 catch (const sdbusplus::exception::SdBusError& e)
106 {
Jayanth Othayothe8199a82018-09-29 00:46:10 -0500107 log<level::ERR>("Failed to reload or restart service",
108 entry("ERR=%s", e.what()),
Jayanth Othayothcfbc8dc2018-09-03 07:22:27 -0500109 entry("UNIT=%s", unit.c_str()));
110 elog<InternalFailure>();
111 }
112}
113
114void Manager::copy(const std::string& src, const std::string& dst)
115{
Jayanth Othayothcfbc8dc2018-09-03 07:22:27 -0500116 try
117 {
118 auto path = fs::path(dst).parent_path();
119 // create dst path folder by default
120 fs::create_directories(path);
121 fs::copy_file(src, dst, fs::copy_options::overwrite_existing);
122 }
123 catch (fs::filesystem_error& e)
124 {
125 log<level::ERR>("Failed to copy certificate", entry("ERR=%s", e.what()),
126 entry("SRC=%s", src.c_str()),
127 entry("DST=%s", dst.c_str()));
128 elog<InternalFailure>();
129 }
130}
131
Jayanth Othayothdd74bd22018-09-28 06:13:35 -0500132X509_Ptr Manager::loadCert(const std::string& filePath)
133{
134 // Read Certificate file
135 X509_Ptr cert(X509_new(), ::X509_free);
136 if (!cert)
137 {
138 log<level::ERR>("Error occured during X509_new call",
139 entry("FILE=%s", filePath.c_str()),
140 entry("ERRCODE=%lu", ERR_get_error()));
141 elog<InternalFailure>();
142 }
143
144 BIO_MEM_Ptr bioCert(BIO_new_file(filePath.c_str(), "rb"), ::BIO_free);
145 if (!bioCert)
146 {
147 log<level::ERR>("Error occured during BIO_new_file call",
148 entry("FILE=%s", filePath.c_str()));
149 elog<InternalFailure>();
150 }
151
152 X509* x509 = cert.get();
153 if (!PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr))
154 {
155 log<level::ERR>("Error occured during PEM_read_bio_X509 call",
156 entry("FILE=%s", filePath.c_str()));
157 elog<InternalFailure>();
158 }
159 return cert;
160}
161
162int32_t Manager::verifyCert(const std::string& filePath)
163{
164 auto errCode = X509_V_OK;
165
166 fs::path file(filePath);
167 if (!fs::exists(file))
168 {
169 log<level::ERR>("File is Missing", entry("FILE=%s", filePath.c_str()));
170 elog<InternalFailure>();
171 }
172
173 try
174 {
175 if (fs::file_size(filePath) == 0)
176 {
177 // file is empty
178 log<level::ERR>("File is empty",
179 entry("FILE=%s", filePath.c_str()));
180 elog<InvalidCertificate>(Reason("File is empty"));
181 }
182 }
183 catch (const fs::filesystem_error& e)
184 {
185 // Log Error message
186 log<level::ERR>(e.what(), entry("FILE=%s", filePath.c_str()));
187 elog<InternalFailure>();
188 }
189
190 // Defining store object as RAW to avoid double free.
191 // X509_LOOKUP_free free up store object.
192 // Create an empty X509_STORE structure for certificate validation.
193 auto x509Store = X509_STORE_new();
194 if (!x509Store)
195 {
196 log<level::ERR>("Error occured during X509_STORE_new call");
197 elog<InternalFailure>();
198 }
199
200 OpenSSL_add_all_algorithms();
201
202 // ADD Certificate Lookup method.
203 X509_LOOKUP_Ptr lookup(X509_STORE_add_lookup(x509Store, X509_LOOKUP_file()),
204 ::X509_LOOKUP_free);
205 if (!lookup)
206 {
207 // Normally lookup cleanup function interanlly does X509Store cleanup
208 // Free up the X509Store.
209 X509_STORE_free(x509Store);
210 log<level::ERR>("Error occured during X509_STORE_add_lookup call");
211 elog<InternalFailure>();
212 }
213 // Load Certificate file.
214 int32_t rc = X509_LOOKUP_load_file(lookup.get(), filePath.c_str(),
215 X509_FILETYPE_PEM);
216 if (rc != 1)
217 {
218 log<level::ERR>("Error occured during X509_LOOKUP_load_file call",
219 entry("FILE=%s", filePath.c_str()));
220 elog<InvalidCertificate>(Reason("Invalid certificate file format"));
221 }
222
223 // Load Certificate file into the X509 structre.
224 X509_Ptr cert = std::move(loadCert(filePath));
225 X509_STORE_CTX_Ptr storeCtx(X509_STORE_CTX_new(), ::X509_STORE_CTX_free);
226 if (!storeCtx)
227 {
228 log<level::ERR>("Error occured during X509_STORE_CTX_new call",
229 entry("FILE=%s", filePath.c_str()));
230 elog<InternalFailure>();
231 }
232
233 rc = X509_STORE_CTX_init(storeCtx.get(), x509Store, cert.get(), NULL);
234 if (rc != 1)
235 {
236 log<level::ERR>("Error occured during X509_STORE_CTX_init call",
237 entry("FILE=%s", filePath.c_str()));
238 elog<InternalFailure>();
239 }
240
241 // Set time to current time.
242 auto locTime = time(nullptr);
243
244 X509_STORE_CTX_set_time(storeCtx.get(), X509_V_FLAG_USE_CHECK_TIME,
245 locTime);
246
247 rc = X509_verify_cert(storeCtx.get());
248 if (rc == 1)
249 {
250 errCode = X509_V_OK;
251 }
252 else if (rc == 0)
253 {
254 errCode = X509_STORE_CTX_get_error(storeCtx.get());
255 log<level::ERR>("Certificate verification failed",
256 entry("FILE=%s", filePath.c_str()),
257 entry("ERRCODE=%d", errCode));
258 }
259 else
260 {
261 log<level::ERR>("Error occured during X509_verify_cert call",
262 entry("FILE=%s", filePath.c_str()));
263 elog<InternalFailure>();
264 }
265 return errCode;
266}
267
Jayanth Othayoth589159f2018-09-28 08:32:39 -0500268bool Manager::compareKeys(const std::string& filePath)
269{
270 X509_Ptr cert(X509_new(), ::X509_free);
271 if (!cert)
272 {
273 log<level::ERR>("Error occured during X509_new call",
274 entry("FILE=%s", filePath.c_str()),
275 entry("ERRCODE=%lu", ERR_get_error()));
276 elog<InternalFailure>();
277 }
278
279 BIO_MEM_Ptr bioCert(BIO_new_file(filePath.c_str(), "rb"), ::BIO_free);
280 if (!bioCert)
281 {
282 log<level::ERR>("Error occured during BIO_new_file call",
283 entry("FILE=%s", filePath.c_str()));
284 elog<InternalFailure>();
285 }
286
287 X509* x509 = cert.get();
288 PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr);
289
290 EVP_PKEY_Ptr pubKey(X509_get_pubkey(cert.get()), ::EVP_PKEY_free);
291 if (!pubKey)
292 {
293 log<level::ERR>("Error occurred during X509_get_pubkey",
294 entry("FILE=%s", filePath.c_str()),
295 entry("ERRCODE=%lu", ERR_get_error()));
296 elog<InvalidCertificate>(Reason("Failed to get public key info"));
297 }
298
299 BIO_MEM_Ptr keyBio(BIO_new(BIO_s_file()), ::BIO_free);
300 if (!keyBio)
301 {
302 log<level::ERR>("Error occured during BIO_s_file call",
303 entry("FILE=%s", filePath.c_str()));
304 elog<InternalFailure>();
305 }
306 BIO_read_filename(keyBio.get(), filePath.c_str());
307
308 EVP_PKEY_Ptr priKey(
309 PEM_read_bio_PrivateKey(keyBio.get(), nullptr, nullptr, nullptr),
310 ::EVP_PKEY_free);
311
312 if (!priKey)
313 {
314 log<level::ERR>("Error occurred during PEM_read_bio_PrivateKey",
315 entry("FILE=%s", filePath.c_str()),
316 entry("ERRCODE=%lu", ERR_get_error()));
317 elog<InvalidCertificate>(Reason("Failed to get private key info"));
318 }
319
320 int32_t rc = EVP_PKEY_cmp(priKey.get(), pubKey.get());
321 if (rc != 1)
322 {
323 log<level::ERR>("Private key is not matching with Certificate",
324 entry("FILE=%s", filePath.c_str()),
325 entry("ERRCODE=%d", rc));
326 return false;
327 }
328
329 return true;
330}
Deepak Kodihalliae70b3d2018-09-30 05:42:00 -0500331
332void Manager::delete_()
333{
334 try
335 {
336 if (!fs::remove(certPath))
337 {
338 log<level::INFO>("Certificate file not found!",
339 entry("PATH=%s", certPath.c_str()));
340 }
341 else
342 {
343 reloadOrReset(unit);
344 }
345 }
346 catch (const InternalFailure& e)
347 {
348 throw;
349 }
350 catch (const std::exception& e)
351 {
352 log<level::ERR>(
353 "Failed to delete certificate", entry("UNIT=%s", unit.c_str()),
354 entry("ERR=%s", e.what()), entry("PATH=%s", certPath.c_str()));
355 elog<InternalFailure>();
356 }
357}
358
Jayanth Othayothcfbc8dc2018-09-03 07:22:27 -0500359} // namespace certs
360} // namespace phosphor