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