1 #include "x509_utils.hpp"
2 
3 #include <openssl/asn1.h>
4 #include <openssl/bio.h>
5 #include <openssl/err.h>
6 #include <openssl/evp.h>
7 #include <openssl/pem.h>
8 #include <openssl/ssl3.h>
9 #include <openssl/x509_vfy.h>
10 
11 #include <cstdio>
12 #include <ctime>
13 #include <exception>
14 #include <memory>
15 #include <phosphor-logging/elog-errors.hpp>
16 #include <phosphor-logging/elog.hpp>
17 #include <phosphor-logging/log.hpp>
18 #include <xyz/openbmc_project/Certs/error.hpp>
19 #include <xyz/openbmc_project/Common/error.hpp>
20 
21 namespace phosphor::certs
22 {
23 
24 namespace
25 {
26 
27 using ::phosphor::logging::elog;
28 using ::phosphor::logging::entry;
29 using ::phosphor::logging::level;
30 using ::phosphor::logging::log;
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.`
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 
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         log<level::ERR>("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         log<level::ERR>("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         log<level::ERR>("Error occurred during X509_LOOKUP_load_file call",
84                         entry("FILE=%s", certSrcPath.c_str()));
85         elog<InvalidCertificate>(Reason("Invalid certificate file format"));
86     }
87     return x509Store;
88 }
89 
90 X509Ptr loadCert(const std::string& filePath)
91 {
92     // Read Certificate file
93     X509Ptr cert(X509_new(), ::X509_free);
94     if (!cert)
95     {
96         log<level::ERR>("Error occurred during X509_new call",
97                         entry("FILE=%s", filePath.c_str()),
98                         entry("ERRCODE=%lu", ERR_get_error()));
99         elog<InternalFailure>();
100     }
101 
102     BIOMemPtr bioCert(BIO_new_file(filePath.c_str(), "rb"), ::BIO_free);
103     if (!bioCert)
104     {
105         log<level::ERR>("Error occurred during BIO_new_file call",
106                         entry("FILE=%s", filePath.c_str()));
107         elog<InternalFailure>();
108     }
109 
110     X509* x509 = cert.get();
111     if (!PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr))
112     {
113         log<level::ERR>("Error occurred during PEM_read_bio_X509 call",
114                         entry("FILE=%s", filePath.c_str()));
115         elog<InternalFailure>();
116     }
117     return cert;
118 }
119 
120 // Checks that notBefore is not earlier than the unix epoch given that
121 // the corresponding DBus interface is uint64_t.
122 void validateCertificateStartDate(X509& cert)
123 {
124     int days = 0;
125     int secs = 0;
126 
127     ASN1TimePtr epoch(ASN1_TIME_new(), ASN1_STRING_free);
128     // Set time to 00:00am GMT, Jan 1 1970; format: YYYYMMDDHHMMSSZ
129     ASN1_TIME_set_string(epoch.get(), "19700101000000Z");
130 
131     ASN1_TIME* notBefore = X509_get_notBefore(&cert);
132     ASN1_TIME_diff(&days, &secs, epoch.get(), notBefore);
133 
134     if (days < 0 || secs < 0)
135     {
136         log<level::ERR>("Certificate valid date starts before the Unix Epoch");
137         elog<InvalidCertificate>(
138             Reason("NotBefore should after 19700101000000Z"));
139     }
140 }
141 
142 void validateCertificateAgainstStore(X509_STORE& x509Store, X509& cert)
143 {
144     int errCode = X509_V_OK;
145     X509StoreCtxPtr storeCtx(X509_STORE_CTX_new(), ::X509_STORE_CTX_free);
146     if (!storeCtx)
147     {
148         log<level::ERR>("Error occurred during X509_STORE_CTX_new call");
149         elog<InternalFailure>();
150     }
151 
152     errCode = X509_STORE_CTX_init(storeCtx.get(), &x509Store, &cert, nullptr);
153     if (errCode != 1)
154     {
155         log<level::ERR>("Error occurred during X509_STORE_CTX_init call");
156         elog<InternalFailure>();
157     }
158 
159     // Set time to current time.
160     auto locTime = time(nullptr);
161 
162     X509_STORE_CTX_set_time(storeCtx.get(), X509_V_FLAG_USE_CHECK_TIME,
163                             locTime);
164 
165     errCode = X509_verify_cert(storeCtx.get());
166     if (errCode == 1)
167     {
168         errCode = X509_V_OK;
169     }
170     else if (errCode == 0)
171     {
172         errCode = X509_STORE_CTX_get_error(storeCtx.get());
173         log<level::INFO>(
174             "Error occurred during X509_verify_cert call, checking for known "
175             "error",
176             entry("ERRCODE=%d", errCode),
177             entry("ERROR_STR=%s", X509_verify_cert_error_string(errCode)));
178     }
179     else
180     {
181         log<level::ERR>("Error occurred during X509_verify_cert call");
182         elog<InternalFailure>();
183     }
184 
185     // Allow certificate upload, for "certificate is not yet valid" and
186     // trust chain related errors.
187     if (!((errCode == X509_V_OK) ||
188           (errCode == X509_V_ERR_CERT_NOT_YET_VALID) ||
189           isTrustChainError(errCode)))
190     {
191         if (errCode == X509_V_ERR_CERT_HAS_EXPIRED)
192         {
193             log<level::ERR>("Expired certificate ");
194             elog<InvalidCertificate>(Reason("Expired Certificate"));
195         }
196         // Loging general error here.
197         log<level::ERR>(
198             "Certificate validation failed", entry("ERRCODE=%d", errCode),
199             entry("ERROR_STR=%s", X509_verify_cert_error_string(errCode)));
200         elog<InvalidCertificate>(Reason("Certificate validation failed"));
201     }
202 }
203 
204 void validateCertificateInSSLContext(X509& cert)
205 {
206     const SSL_METHOD* method = TLS_method();
207     SSLCtxPtr ctx(SSL_CTX_new(method), SSL_CTX_free);
208     if (SSL_CTX_use_certificate(ctx.get(), &cert) != 1)
209     {
210         log<level::ERR>("Certificate is not usable",
211                         entry("ERRCODE=%x", ERR_get_error()));
212         elog<InvalidCertificate>(Reason("Certificate is not usable"));
213     }
214 }
215 
216 std::string generateCertId(X509& cert)
217 {
218     unsigned long subjectNameHash = X509_subject_name_hash(&cert);
219     unsigned long issuerSerialHash = X509_issuer_and_serial_hash(&cert);
220     static constexpr auto certIdLength = 17;
221     char idBuff[certIdLength];
222 
223     snprintf(idBuff, certIdLength, "%08lx%08lx", subjectNameHash,
224              issuerSerialHash);
225 
226     return {idBuff};
227 }
228 
229 std::unique_ptr<X509, decltype(&::X509_free)> parseCert(const std::string& pem)
230 {
231     if (pem.size() > INT_MAX)
232     {
233         log<level::ERR>("Error occurred during parseCert: PEM is too long");
234         elog<InvalidCertificate>(Reason("Invalid PEM: too long"));
235     }
236     X509Ptr cert(X509_new(), ::X509_free);
237     if (!cert)
238     {
239         log<level::ERR>("Error occurred during X509_new call",
240                         entry("ERRCODE=%lu", ERR_get_error()));
241         elog<InternalFailure>();
242     }
243 
244     BIOMemPtr bioCert(BIO_new_mem_buf(pem.data(), static_cast<int>(pem.size())),
245                       ::BIO_free);
246     X509* x509 = cert.get();
247     if (!PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr))
248     {
249         log<level::ERR>("Error occurred during PEM_read_bio_X509 call",
250                         entry("PEM=%s", pem.c_str()));
251         elog<InternalFailure>();
252     }
253     return cert;
254 }
255 } // namespace phosphor::certs
256