1 #include "config.h"
2 
3 #include "x509_utils.hpp"
4 
5 #include <openssl/asn1.h>
6 #include <openssl/bio.h>
7 #include <openssl/err.h>
8 #include <openssl/evp.h>
9 #include <openssl/pem.h>
10 #include <openssl/ssl3.h>
11 #include <openssl/x509_vfy.h>
12 
13 #include <phosphor-logging/elog-errors.hpp>
14 #include <phosphor-logging/elog.hpp>
15 #include <phosphor-logging/lg2.hpp>
16 #include <xyz/openbmc_project/Certs/error.hpp>
17 #include <xyz/openbmc_project/Common/error.hpp>
18 
19 #include <cstdio>
20 #include <ctime>
21 #include <exception>
22 #include <memory>
23 
24 namespace phosphor::certs
25 {
26 
27 namespace
28 {
29 
30 using ::phosphor::logging::elog;
31 using ::sdbusplus::xyz::openbmc_project::Certs::Error::InvalidCertificate;
32 using ::sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
33 using Reason = ::phosphor::logging::xyz::openbmc_project::Certs::
34     InvalidCertificate::REASON;
35 
36 // RAII support for openSSL functions.
37 using X509StorePtr = std::unique_ptr<X509_STORE, decltype(&::X509_STORE_free)>;
38 using X509StoreCtxPtr =
39     std::unique_ptr<X509_STORE_CTX, decltype(&::X509_STORE_CTX_free)>;
40 using X509Ptr = std::unique_ptr<X509, decltype(&::X509_free)>;
41 using BIOMemPtr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
42 using ASN1TimePtr = std::unique_ptr<ASN1_TIME, decltype(&ASN1_STRING_free)>;
43 using SSLCtxPtr = std::unique_ptr<SSL_CTX, decltype(&::SSL_CTX_free)>;
44 
45 // Trust chain related errors.`
isTrustChainError(int error)46 constexpr bool isTrustChainError(int error)
47 {
48     return error == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
49            error == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN ||
50            error == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY ||
51            error == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT ||
52            error == X509_V_ERR_CERT_UNTRUSTED ||
53            error == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE;
54 }
55 } // namespace
56 
getX509Store(const std::string & certSrcPath)57 X509StorePtr getX509Store(const std::string& certSrcPath)
58 {
59     // Create an empty X509_STORE structure for certificate validation.
60     X509StorePtr x509Store(X509_STORE_new(), &X509_STORE_free);
61     if (!x509Store)
62     {
63         lg2::error("Error occurred during X509_STORE_new call");
64         elog<InternalFailure>();
65     }
66 
67     OpenSSL_add_all_algorithms();
68 
69     // ADD Certificate Lookup method.
70     // lookup will be cleaned up automatically when the holding Store goes away.
71     auto lookup = X509_STORE_add_lookup(x509Store.get(), X509_LOOKUP_file());
72 
73     if (!lookup)
74     {
75         lg2::error("Error occurred during X509_STORE_add_lookup call");
76         elog<InternalFailure>();
77     }
78     // Load the Certificate file into X509 Store.
79     if (int errCode = X509_LOOKUP_load_file(lookup, certSrcPath.c_str(),
80                                             X509_FILETYPE_PEM);
81         errCode != 1)
82     {
83         lg2::error(
84             "Error occurred during X509_LOOKUP_load_file call, FILE:{FILE}",
85             "FILE", certSrcPath);
86         elog<InvalidCertificate>(Reason("Invalid certificate file format"));
87     }
88     return x509Store;
89 }
90 
loadCert(const std::string & filePath)91 X509Ptr loadCert(const std::string& filePath)
92 {
93     // Read Certificate file
94     X509Ptr cert(X509_new(), ::X509_free);
95     if (!cert)
96     {
97         lg2::error(
98             "Error occurred during X509_new call, FILE:{FILE}, ERRCODE:{ERRCODE}",
99             "FILE", filePath, "ERRCODE", ERR_get_error());
100         elog<InternalFailure>();
101     }
102 
103     BIOMemPtr bioCert(BIO_new_file(filePath.c_str(), "rb"), ::BIO_free);
104     if (!bioCert)
105     {
106         lg2::error("Error occurred during BIO_new_file call, FILE:{FILE}",
107                    "FILE", filePath);
108         elog<InternalFailure>();
109     }
110 
111     X509* x509 = cert.get();
112     if (!PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr))
113     {
114         lg2::error("Error occurred during PEM_read_bio_X509 call, FILE:{FILE}",
115                    "FILE", filePath);
116         elog<InternalFailure>();
117     }
118     return cert;
119 }
120 
121 // Checks that notBefore is not earlier than the unix epoch given that
122 // the corresponding DBus interface is uint64_t.
validateCertificateStartDate(X509 & cert)123 void validateCertificateStartDate(X509& cert)
124 {
125     int days = 0;
126     int secs = 0;
127 
128     ASN1TimePtr epoch(ASN1_TIME_new(), ASN1_STRING_free);
129     // Set time to 00:00am GMT, Jan 1 1970; format: YYYYMMDDHHMMSSZ
130     ASN1_TIME_set_string(epoch.get(), "19700101000000Z");
131 
132     ASN1_TIME* notBefore = X509_get_notBefore(&cert);
133     ASN1_TIME_diff(&days, &secs, epoch.get(), notBefore);
134 
135     if (days < 0 || secs < 0)
136     {
137         lg2::error("Certificate valid date starts before the Unix Epoch");
138         elog<InvalidCertificate>(
139             Reason("NotBefore should after 19700101000000Z"));
140     }
141 }
142 
validateCertificateAgainstStore(X509_STORE & x509Store,X509 & cert)143 void validateCertificateAgainstStore(X509_STORE& x509Store, X509& cert)
144 {
145     int errCode = X509_V_OK;
146     X509StoreCtxPtr storeCtx(X509_STORE_CTX_new(), ::X509_STORE_CTX_free);
147     if (!storeCtx)
148     {
149         lg2::error("Error occurred during X509_STORE_CTX_new call");
150         elog<InternalFailure>();
151     }
152 
153     errCode = X509_STORE_CTX_init(storeCtx.get(), &x509Store, &cert, nullptr);
154     if (errCode != 1)
155     {
156         lg2::error("Error occurred during X509_STORE_CTX_init call");
157         elog<InternalFailure>();
158     }
159 
160     // Set time to current time.
161     auto locTime = time(nullptr);
162 
163     X509_STORE_CTX_set_time(storeCtx.get(), X509_V_FLAG_USE_CHECK_TIME,
164                             locTime);
165 
166     errCode = X509_verify_cert(storeCtx.get());
167     if (errCode == 1)
168     {
169         errCode = X509_V_OK;
170     }
171     else if (errCode == 0)
172     {
173         errCode = X509_STORE_CTX_get_error(storeCtx.get());
174         lg2::info(
175             "Error occurred during X509_verify_cert call, checking for known "
176             "error, ERRCODE:{ERRCODE}, ERROR_STR:{ERROR_STR}",
177             "ERRCODE", errCode, "ERROR_STR",
178             X509_verify_cert_error_string(errCode));
179     }
180     else
181     {
182         lg2::error("Error occurred during X509_verify_cert call");
183         elog<InternalFailure>();
184     }
185 
186     // Allow certificate upload, for "certificate is not yet valid" and
187     // trust chain related errors.
188     // If ALLOW_EXPIRED is defined, allow expired certificate so that it
189     // could be replaced
190     bool isOK = (errCode == X509_V_OK) ||
191                 (errCode == X509_V_ERR_CERT_NOT_YET_VALID) ||
192                 isTrustChainError(errCode) ||
193                 (allowExpired && errCode == X509_V_ERR_CERT_HAS_EXPIRED);
194 
195     if (!isOK)
196     {
197         if (errCode == X509_V_ERR_CERT_HAS_EXPIRED)
198         {
199             lg2::error("Expired certificate ");
200             elog<InvalidCertificate>(Reason("Expired Certificate"));
201         }
202         // Logging general error here.
203         lg2::error(
204             "Certificate validation failed, ERRCODE:{ERRCODE}, ERROR_STR:{ERROR_STR}",
205             "ERRCODE", errCode, "ERROR_STR",
206             X509_verify_cert_error_string(errCode));
207         elog<InvalidCertificate>(Reason("Certificate validation failed"));
208     }
209 }
210 
validateCertificateInSSLContext(X509 & cert)211 void validateCertificateInSSLContext(X509& cert)
212 {
213     const SSL_METHOD* method = TLS_method();
214     SSLCtxPtr ctx(SSL_CTX_new(method), SSL_CTX_free);
215     if (SSL_CTX_use_certificate(ctx.get(), &cert) != 1)
216     {
217         lg2::error("Certificate is not usable, ERRCODE:{ERRCODE}", "ERRCODE",
218                    ERR_get_error());
219         elog<InvalidCertificate>(Reason("Certificate is not usable"));
220     }
221 }
222 
generateCertId(X509 & cert)223 std::string generateCertId(X509& cert)
224 {
225     unsigned long subjectNameHash = X509_subject_name_hash(&cert);
226     unsigned long issuerSerialHash = X509_issuer_and_serial_hash(&cert);
227     static constexpr auto certIdLength = 17;
228     char idBuff[certIdLength];
229 
230     snprintf(idBuff, certIdLength, "%08lx%08lx", subjectNameHash,
231              issuerSerialHash);
232 
233     return {idBuff};
234 }
235 
parseCert(const std::string & pem)236 std::unique_ptr<X509, decltype(&::X509_free)> parseCert(const std::string& pem)
237 {
238     if (pem.size() > INT_MAX)
239     {
240         lg2::error("Error occurred during parseCert: PEM is too long");
241         elog<InvalidCertificate>(Reason("Invalid PEM: too long"));
242     }
243     X509Ptr cert(X509_new(), ::X509_free);
244     if (!cert)
245     {
246         lg2::error("Error occurred during X509_new call, ERRCODE:{ERRCODE}",
247                    "ERRCODE", ERR_get_error());
248         elog<InternalFailure>();
249     }
250 
251     BIOMemPtr bioCert(BIO_new_mem_buf(pem.data(), static_cast<int>(pem.size())),
252                       ::BIO_free);
253     X509* x509 = cert.get();
254     if (!PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr))
255     {
256         lg2::error("Error occurred during PEM_read_bio_X509 call, PEM:{PEM}",
257                    "PEM", pem);
258         elog<InternalFailure>();
259     }
260     return cert;
261 }
262 } // namespace phosphor::certs
263