blob: 43b97258de6d998bb38e91352bfa185cf740f046 [file] [log] [blame]
Marri Devender Rao6ceec402019-02-01 03:15:19 -06001#include "certificate.hpp"
2
3#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
10#include <fstream>
11#include <phosphor-logging/elog-errors.hpp>
12#include <xyz/openbmc_project/Certs/Install/error.hpp>
13#include <xyz/openbmc_project/Common/error.hpp>
14namespace phosphor
15{
16namespace certs
17{
18// 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)>;
24using EVP_PKEY_Ptr = std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>;
25using BUF_MEM_Ptr = std::unique_ptr<BUF_MEM, decltype(&::BUF_MEM_free)>;
26using InternalFailure =
27 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
28using InvalidCertificate =
29 sdbusplus::xyz::openbmc_project::Certs::Install::Error::InvalidCertificate;
30using Reason = xyz::openbmc_project::Certs::Install::InvalidCertificate::REASON;
31
32// Trust chain related errors.`
33#define TRUST_CHAIN_ERR(errnum) \
34 ((errnum == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) || \
35 (errnum == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) || \
36 (errnum == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) || \
37 (errnum == X509_V_ERR_CERT_UNTRUSTED) || \
38 (errnum == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE))
39
40Certificate::Certificate(sdbusplus::bus::bus& bus, const std::string& objPath,
41 const CertificateType& type,
42 const UnitsToRestart& unit,
43 const CertInstallPath& installPath,
44 const CertUploadPath& uploadPath) :
45 bus(bus),
46 objectPath(objPath), certType(type), unitToRestart(unit),
47 certInstallPath(installPath)
48{
49 auto installHelper = [this](const auto& filePath) {
50 if (!compareKeys(filePath))
51 {
52 elog<InvalidCertificate>(
53 Reason("Private key does not match the Certificate"));
54 };
55 };
56 typeFuncMap[SERVER] = installHelper;
57 typeFuncMap[CLIENT] = installHelper;
58 typeFuncMap[AUTHORITY] = [](auto filePath) {};
59 install(uploadPath);
60}
61
62Certificate::~Certificate()
63{
64 if (!fs::remove(certInstallPath))
65 {
66 log<level::INFO>("Certificate file not found!",
67 entry("PATH=%s", certInstallPath.c_str()));
68 }
69 else if (!unitToRestart.empty())
70 {
71 reloadOrReset(unitToRestart);
72 }
73}
74
75void Certificate::install(const std::string filePath)
76{
77 log<level::INFO>("Certificate install ",
78 entry("FILEPATH=%s", filePath.c_str()));
79 auto errCode = X509_V_OK;
80
81 // Verify the certificate file
82 fs::path file(filePath);
83 if (!fs::exists(file))
84 {
85 log<level::ERR>("File is Missing", entry("FILE=%s", filePath.c_str()));
86 elog<InternalFailure>();
87 }
88
89 try
90 {
91 if (fs::file_size(filePath) == 0)
92 {
93 // file is empty
94 log<level::ERR>("File is empty",
95 entry("FILE=%s", filePath.c_str()));
96 elog<InvalidCertificate>(Reason("File is empty"));
97 }
98 }
99 catch (const fs::filesystem_error& e)
100 {
101 // Log Error message
102 log<level::ERR>(e.what(), entry("FILE=%s", filePath.c_str()));
103 elog<InternalFailure>();
104 }
105
106 // Defining store object as RAW to avoid double free.
107 // X509_LOOKUP_free free up store object.
108 // Create an empty X509_STORE structure for certificate validation.
109 auto x509Store = X509_STORE_new();
110 if (!x509Store)
111 {
112 log<level::ERR>("Error occured during X509_STORE_new call");
113 elog<InternalFailure>();
114 }
115
116 OpenSSL_add_all_algorithms();
117
118 // ADD Certificate Lookup method.
119 X509_LOOKUP_Ptr lookup(X509_STORE_add_lookup(x509Store, X509_LOOKUP_file()),
120 ::X509_LOOKUP_free);
121 if (!lookup)
122 {
123 // Normally lookup cleanup function interanlly does X509Store cleanup
124 // Free up the X509Store.
125 X509_STORE_free(x509Store);
126 log<level::ERR>("Error occured during X509_STORE_add_lookup call");
127 elog<InternalFailure>();
128 }
129 // Load Certificate file.
130 errCode = X509_LOOKUP_load_file(lookup.get(), filePath.c_str(),
131 X509_FILETYPE_PEM);
132 if (errCode != 1)
133 {
134 log<level::ERR>("Error occured during X509_LOOKUP_load_file call",
135 entry("FILE=%s", filePath.c_str()));
136 elog<InvalidCertificate>(Reason("Invalid certificate file format"));
137 }
138
139 // Load Certificate file into the X509 structre.
140 X509_Ptr cert = std::move(loadCert(filePath));
141 X509_STORE_CTX_Ptr storeCtx(X509_STORE_CTX_new(), ::X509_STORE_CTX_free);
142 if (!storeCtx)
143 {
144 log<level::ERR>("Error occured during X509_STORE_CTX_new call",
145 entry("FILE=%s", filePath.c_str()));
146 elog<InternalFailure>();
147 }
148
149 errCode = X509_STORE_CTX_init(storeCtx.get(), x509Store, cert.get(), NULL);
150 if (errCode != 1)
151 {
152 log<level::ERR>("Error occured during X509_STORE_CTX_init call",
153 entry("FILE=%s", filePath.c_str()));
154 elog<InternalFailure>();
155 }
156
157 // Set time to current time.
158 auto locTime = time(nullptr);
159
160 X509_STORE_CTX_set_time(storeCtx.get(), X509_V_FLAG_USE_CHECK_TIME,
161 locTime);
162
163 errCode = X509_verify_cert(storeCtx.get());
164 if (errCode == 1)
165 {
166 errCode = X509_V_OK;
167 }
168 else if (errCode == 0)
169 {
170 errCode = X509_STORE_CTX_get_error(storeCtx.get());
171 log<level::ERR>("Certificate verification failed",
172 entry("FILE=%s", filePath.c_str()),
173 entry("ERRCODE=%d", errCode));
174 }
175 else
176 {
177 log<level::ERR>("Error occured during X509_verify_cert call",
178 entry("FILE=%s", filePath.c_str()));
179 elog<InternalFailure>();
180 }
181
182 // Allow certificate upload, for "certificate is not yet valid" and
183 // trust chain related errors.
184 if (!((errCode == X509_V_OK) ||
185 (errCode == X509_V_ERR_CERT_NOT_YET_VALID) ||
186 TRUST_CHAIN_ERR(errCode)))
187 {
188 if (errCode == X509_V_ERR_CERT_HAS_EXPIRED)
189 {
190 elog<InvalidCertificate>(Reason("Expired Certificate"));
191 }
192 // Loging general error here.
193 elog<InvalidCertificate>(Reason("Certificate validation failed"));
194 }
195
196 // Invoke type specific compare keys function.
197 auto iter = typeFuncMap.find(certType);
198 if (iter == typeFuncMap.end())
199 {
200 log<level::ERR>("Unsupported Type", entry("TYPE=%s", certType.c_str()));
201 elog<InternalFailure>();
202 }
203 iter->second(filePath);
204
205 // Copy thecertificate to the installation path
206 auto path = fs::path(certInstallPath).parent_path();
207 try
208 {
209 fs::create_directories(path);
210 // During bootup will be parsing existing file so no need to
211 // copy it.
212 if (filePath != certInstallPath)
213 {
214 fs::copy_file(filePath, certInstallPath,
215 fs::copy_options::overwrite_existing);
216 }
217 }
218 catch (fs::filesystem_error& e)
219 {
220 log<level::ERR>("Failed to copy certificate", entry("ERR=%s", e.what()),
221 entry("SRC=%s", filePath.c_str()),
222 entry("DST=%s", certInstallPath.c_str()));
223 elog<InternalFailure>();
224 }
225 // restart the units
226 if (!unitToRestart.empty())
227 {
228 reloadOrReset(unitToRestart);
229 }
230}
231
232X509_Ptr Certificate::loadCert(const std::string& filePath)
233{
234 log<level::INFO>("Certificate loadCert",
235 entry("FILEPATH=%s", filePath.c_str()));
236 // Read Certificate file
237 X509_Ptr cert(X509_new(), ::X509_free);
238 if (!cert)
239 {
240 log<level::ERR>("Error occured during X509_new call",
241 entry("FILE=%s", filePath.c_str()),
242 entry("ERRCODE=%lu", ERR_get_error()));
243 elog<InternalFailure>();
244 }
245
246 BIO_MEM_Ptr bioCert(BIO_new_file(filePath.c_str(), "rb"), ::BIO_free);
247 if (!bioCert)
248 {
249 log<level::ERR>("Error occured during BIO_new_file call",
250 entry("FILE=%s", filePath.c_str()));
251 elog<InternalFailure>();
252 }
253
254 X509* x509 = cert.get();
255 if (!PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr))
256 {
257 log<level::ERR>("Error occured during PEM_read_bio_X509 call",
258 entry("FILE=%s", filePath.c_str()));
259 elog<InternalFailure>();
260 }
261 return cert;
262}
263bool Certificate::compareKeys(const std::string& filePath)
264{
265 log<level::INFO>("Certificate compareKeys",
266 entry("FILEPATH=%s", filePath.c_str()));
267 X509_Ptr cert(X509_new(), ::X509_free);
268 if (!cert)
269 {
270 log<level::ERR>("Error occured during X509_new call",
271 entry("FILE=%s", filePath.c_str()),
272 entry("ERRCODE=%lu", ERR_get_error()));
273 elog<InternalFailure>();
274 }
275
276 BIO_MEM_Ptr bioCert(BIO_new_file(filePath.c_str(), "rb"), ::BIO_free);
277 if (!bioCert)
278 {
279 log<level::ERR>("Error occured during BIO_new_file call",
280 entry("FILE=%s", filePath.c_str()));
281 elog<InternalFailure>();
282 }
283
284 X509* x509 = cert.get();
285 PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr);
286
287 EVP_PKEY_Ptr pubKey(X509_get_pubkey(cert.get()), ::EVP_PKEY_free);
288 if (!pubKey)
289 {
290 log<level::ERR>("Error occurred during X509_get_pubkey",
291 entry("FILE=%s", filePath.c_str()),
292 entry("ERRCODE=%lu", ERR_get_error()));
293 elog<InvalidCertificate>(Reason("Failed to get public key info"));
294 }
295
296 BIO_MEM_Ptr keyBio(BIO_new(BIO_s_file()), ::BIO_free);
297 if (!keyBio)
298 {
299 log<level::ERR>("Error occured during BIO_s_file call",
300 entry("FILE=%s", filePath.c_str()));
301 elog<InternalFailure>();
302 }
303 BIO_read_filename(keyBio.get(), filePath.c_str());
304
305 EVP_PKEY_Ptr priKey(
306 PEM_read_bio_PrivateKey(keyBio.get(), nullptr, nullptr, nullptr),
307 ::EVP_PKEY_free);
308 if (!priKey)
309 {
310 log<level::ERR>("Error occurred during PEM_read_bio_PrivateKey",
311 entry("FILE=%s", filePath.c_str()),
312 entry("ERRCODE=%lu", ERR_get_error()));
313 elog<InvalidCertificate>(Reason("Failed to get private key info"));
314 }
315
316 int32_t rc = EVP_PKEY_cmp(priKey.get(), pubKey.get());
317 if (rc != 1)
318 {
319 log<level::ERR>("Private key is not matching with Certificate",
320 entry("FILE=%s", filePath.c_str()),
321 entry("ERRCODE=%d", rc));
322 return false;
323 }
324 return true;
325}
326
327void Certificate::reloadOrReset(const UnitsToRestart& unit)
328{
329 constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1";
330 constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1";
331 constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager";
332 try
333 {
334 auto method =
335 bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
336 SYSTEMD_INTERFACE, "ReloadOrRestartUnit");
337 method.append(unit, "replace");
338 bus.call_noreply(method);
339 }
340 catch (const sdbusplus::exception::SdBusError& e)
341 {
342 log<level::ERR>("Failed to reload or restart service",
343 entry("ERR=%s", e.what()),
344 entry("UNIT=%s", unit.c_str()));
345 elog<InternalFailure>();
346 }
347}
348} // namespace certs
349} // namespace phosphor