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