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