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