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