xref: /openbmc/phosphor-certificate-manager/certs_manager.cpp (revision feddcf2017afcab22f2aee0de293ecb13edd1af9)
1 #include "certs_manager.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 <experimental/filesystem>
11 #include <sdbusplus/bus.hpp>
12 #include <xyz/openbmc_project/Common/error.hpp>
13 
14 namespace phosphor
15 {
16 namespace certs
17 {
18 // RAII support for openSSL functions.
19 using BIO_MEM_Ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
20 using X509_STORE_CTX_Ptr =
21     std::unique_ptr<X509_STORE_CTX, decltype(&::X509_STORE_CTX_free)>;
22 using X509_LOOKUP_Ptr =
23     std::unique_ptr<X509_LOOKUP, decltype(&::X509_LOOKUP_free)>;
24 using EVP_PKEY_Ptr = std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>;
25 
26 namespace fs = std::experimental::filesystem;
27 using InternalFailure =
28     sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
29 // 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))
36 
37 void Manager::install(const std::string path)
38 {
39     // Verify the certificate file
40     auto rc = verifyCert(path);
41     // 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)))
45     {
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     }
53 
54     // 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     }
61     iter->second(path);
62 
63     // Copy the certificate file
64     copy(path, certPath);
65 
66     if (!unit.empty())
67     {
68         reloadOrReset(unit);
69     }
70 }
71 
72 void Manager::reloadOrReset(const std::string& unit)
73 {
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     {
80         auto method =
81             bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
82                                 SYSTEMD_INTERFACE, "ReloadOrRestartUnit");
83 
84         method.append(unit, "replace");
85 
86         bus.call_noreply(method);
87     }
88     catch (const sdbusplus::exception::SdBusError& e)
89     {
90         log<level::ERR>("Failed to reload or restart service",
91                         entry("ERR=%s", e.what()),
92                         entry("UNIT=%s", unit.c_str()));
93         elog<InternalFailure>();
94     }
95 }
96 
97 void Manager::copy(const std::string& src, const std::string& dst)
98 {
99     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 
115 X509_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 
145 int32_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 
251 bool 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 }
314 
315 void 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         }
324         else if (!unit.empty())
325         {
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 
342 } // namespace certs
343 } // namespace phosphor
344