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