| #include "config.h" | 
 |  | 
 | #include "x509_utils.hpp" | 
 |  | 
 | #include <openssl/asn1.h> | 
 | #include <openssl/bio.h> | 
 | #include <openssl/err.h> | 
 | #include <openssl/evp.h> | 
 | #include <openssl/pem.h> | 
 | #include <openssl/ssl3.h> | 
 | #include <openssl/x509_vfy.h> | 
 |  | 
 | #include <phosphor-logging/elog-errors.hpp> | 
 | #include <phosphor-logging/elog.hpp> | 
 | #include <phosphor-logging/lg2.hpp> | 
 | #include <xyz/openbmc_project/Certs/error.hpp> | 
 | #include <xyz/openbmc_project/Common/error.hpp> | 
 |  | 
 | #include <cstdio> | 
 | #include <ctime> | 
 | #include <exception> | 
 | #include <memory> | 
 |  | 
 | namespace phosphor::certs | 
 | { | 
 |  | 
 | namespace | 
 | { | 
 |  | 
 | using ::phosphor::logging::elog; | 
 | using ::sdbusplus::xyz::openbmc_project::Certs::Error::InvalidCertificate; | 
 | using ::sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; | 
 | using Reason = ::phosphor::logging::xyz::openbmc_project::Certs:: | 
 |     InvalidCertificate::REASON; | 
 |  | 
 | // RAII support for openSSL functions. | 
 | using X509StorePtr = std::unique_ptr<X509_STORE, decltype(&::X509_STORE_free)>; | 
 | using X509StoreCtxPtr = | 
 |     std::unique_ptr<X509_STORE_CTX, decltype(&::X509_STORE_CTX_free)>; | 
 | using X509Ptr = std::unique_ptr<X509, decltype(&::X509_free)>; | 
 | using BIOMemPtr = std::unique_ptr<BIO, decltype(&::BIO_free)>; | 
 | using ASN1TimePtr = std::unique_ptr<ASN1_TIME, decltype(&ASN1_STRING_free)>; | 
 | using SSLCtxPtr = std::unique_ptr<SSL_CTX, decltype(&::SSL_CTX_free)>; | 
 |  | 
 | // Trust chain related errors.` | 
 | constexpr bool isTrustChainError(int error) | 
 | { | 
 |     return error == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT || | 
 |            error == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN || | 
 |            error == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY || | 
 |            error == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT || | 
 |            error == X509_V_ERR_CERT_UNTRUSTED || | 
 |            error == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE; | 
 | } | 
 | } // namespace | 
 |  | 
 | X509StorePtr getX509Store(const std::string& certSrcPath) | 
 | { | 
 |     // Create an empty X509_STORE structure for certificate validation. | 
 |     X509StorePtr x509Store(X509_STORE_new(), &X509_STORE_free); | 
 |     if (!x509Store) | 
 |     { | 
 |         lg2::error("Error occurred during X509_STORE_new call"); | 
 |         elog<InternalFailure>(); | 
 |     } | 
 |  | 
 |     OpenSSL_add_all_algorithms(); | 
 |  | 
 |     // ADD Certificate Lookup method. | 
 |     // lookup will be cleaned up automatically when the holding Store goes away. | 
 |     auto lookup = X509_STORE_add_lookup(x509Store.get(), X509_LOOKUP_file()); | 
 |  | 
 |     if (!lookup) | 
 |     { | 
 |         lg2::error("Error occurred during X509_STORE_add_lookup call"); | 
 |         elog<InternalFailure>(); | 
 |     } | 
 |     // Load the Certificate file into X509 Store. | 
 |     if (int errCode = X509_LOOKUP_load_file(lookup, certSrcPath.c_str(), | 
 |                                             X509_FILETYPE_PEM); | 
 |         errCode != 1) | 
 |     { | 
 |         lg2::error( | 
 |             "Error occurred during X509_LOOKUP_load_file call, FILE:{FILE}", | 
 |             "FILE", certSrcPath); | 
 |         elog<InvalidCertificate>(Reason("Invalid certificate file format")); | 
 |     } | 
 |     return x509Store; | 
 | } | 
 |  | 
 | X509Ptr loadCert(const std::string& filePath) | 
 | { | 
 |     // Read Certificate file | 
 |     X509Ptr cert(X509_new(), ::X509_free); | 
 |     if (!cert) | 
 |     { | 
 |         lg2::error( | 
 |             "Error occurred during X509_new call, FILE:{FILE}, ERRCODE:{ERRCODE}", | 
 |             "FILE", filePath, "ERRCODE", ERR_get_error()); | 
 |         elog<InternalFailure>(); | 
 |     } | 
 |  | 
 |     BIOMemPtr bioCert(BIO_new_file(filePath.c_str(), "rb"), ::BIO_free); | 
 |     if (!bioCert) | 
 |     { | 
 |         lg2::error("Error occurred during BIO_new_file call, FILE:{FILE}", | 
 |                    "FILE", filePath); | 
 |         elog<InternalFailure>(); | 
 |     } | 
 |  | 
 |     X509* x509 = cert.get(); | 
 |     if (!PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr)) | 
 |     { | 
 |         lg2::error("Error occurred during PEM_read_bio_X509 call, FILE:{FILE}", | 
 |                    "FILE", filePath); | 
 |         elog<InternalFailure>(); | 
 |     } | 
 |     return cert; | 
 | } | 
 |  | 
 | // Checks that notBefore is not earlier than the unix epoch given that | 
 | // the corresponding DBus interface is uint64_t. | 
 | void validateCertificateStartDate(X509& cert) | 
 | { | 
 |     int days = 0; | 
 |     int secs = 0; | 
 |  | 
 |     ASN1TimePtr epoch(ASN1_TIME_new(), ASN1_STRING_free); | 
 |     // Set time to 00:00am GMT, Jan 1 1970; format: YYYYMMDDHHMMSSZ | 
 |     ASN1_TIME_set_string(epoch.get(), "19700101000000Z"); | 
 |  | 
 |     ASN1_TIME* notBefore = X509_get_notBefore(&cert); | 
 |     ASN1_TIME_diff(&days, &secs, epoch.get(), notBefore); | 
 |  | 
 |     if (days < 0 || secs < 0) | 
 |     { | 
 |         lg2::error("Certificate valid date starts before the Unix Epoch"); | 
 |         elog<InvalidCertificate>( | 
 |             Reason("NotBefore should after 19700101000000Z")); | 
 |     } | 
 | } | 
 |  | 
 | void validateCertificateAgainstStore(X509_STORE& x509Store, X509& cert) | 
 | { | 
 |     int errCode = X509_V_OK; | 
 |     X509StoreCtxPtr storeCtx(X509_STORE_CTX_new(), ::X509_STORE_CTX_free); | 
 |     if (!storeCtx) | 
 |     { | 
 |         lg2::error("Error occurred during X509_STORE_CTX_new call"); | 
 |         elog<InternalFailure>(); | 
 |     } | 
 |  | 
 |     errCode = X509_STORE_CTX_init(storeCtx.get(), &x509Store, &cert, nullptr); | 
 |     if (errCode != 1) | 
 |     { | 
 |         lg2::error("Error occurred during X509_STORE_CTX_init call"); | 
 |         elog<InternalFailure>(); | 
 |     } | 
 |  | 
 |     // Set time to current time. | 
 |     auto locTime = time(nullptr); | 
 |  | 
 |     X509_STORE_CTX_set_time(storeCtx.get(), X509_V_FLAG_USE_CHECK_TIME, | 
 |                             locTime); | 
 |  | 
 |     errCode = X509_verify_cert(storeCtx.get()); | 
 |     if (errCode == 1) | 
 |     { | 
 |         errCode = X509_V_OK; | 
 |     } | 
 |     else if (errCode == 0) | 
 |     { | 
 |         errCode = X509_STORE_CTX_get_error(storeCtx.get()); | 
 |         lg2::info( | 
 |             "Error occurred during X509_verify_cert call, checking for known " | 
 |             "error, ERRCODE:{ERRCODE}, ERROR_STR:{ERROR_STR}", | 
 |             "ERRCODE", errCode, "ERROR_STR", | 
 |             X509_verify_cert_error_string(errCode)); | 
 |     } | 
 |     else | 
 |     { | 
 |         lg2::error("Error occurred during X509_verify_cert call"); | 
 |         elog<InternalFailure>(); | 
 |     } | 
 |  | 
 |     // Allow certificate upload, for "certificate is not yet valid" and | 
 |     // trust chain related errors. | 
 |     // If ALLOW_EXPIRED is defined, allow expired certificate so that it | 
 |     // could be replaced | 
 |     bool isOK = (errCode == X509_V_OK) || | 
 |                 (errCode == X509_V_ERR_CERT_NOT_YET_VALID) || | 
 |                 isTrustChainError(errCode) || | 
 |                 (allowExpired && errCode == X509_V_ERR_CERT_HAS_EXPIRED); | 
 |  | 
 |     if (!isOK) | 
 |     { | 
 |         if (errCode == X509_V_ERR_CERT_HAS_EXPIRED) | 
 |         { | 
 |             lg2::error("Expired certificate "); | 
 |             elog<InvalidCertificate>(Reason("Expired Certificate")); | 
 |         } | 
 |         // Logging general error here. | 
 |         lg2::error( | 
 |             "Certificate validation failed, ERRCODE:{ERRCODE}, ERROR_STR:{ERROR_STR}", | 
 |             "ERRCODE", errCode, "ERROR_STR", | 
 |             X509_verify_cert_error_string(errCode)); | 
 |         elog<InvalidCertificate>(Reason("Certificate validation failed")); | 
 |     } | 
 | } | 
 |  | 
 | void validateCertificateInSSLContext(X509& cert) | 
 | { | 
 |     const SSL_METHOD* method = TLS_method(); | 
 |     SSLCtxPtr ctx(SSL_CTX_new(method), SSL_CTX_free); | 
 |     if (SSL_CTX_use_certificate(ctx.get(), &cert) != 1) | 
 |     { | 
 |         lg2::error("Certificate is not usable, ERRCODE:{ERRCODE}", "ERRCODE", | 
 |                    ERR_get_error()); | 
 |         elog<InvalidCertificate>(Reason("Certificate is not usable")); | 
 |     } | 
 | } | 
 |  | 
 | std::string generateCertId(X509& cert) | 
 | { | 
 |     unsigned long subjectNameHash = X509_subject_name_hash(&cert); | 
 |     unsigned long issuerSerialHash = X509_issuer_and_serial_hash(&cert); | 
 |     static constexpr auto certIdLength = 17; | 
 |     char idBuff[certIdLength]; | 
 |  | 
 |     snprintf(idBuff, certIdLength, "%08lx%08lx", subjectNameHash, | 
 |              issuerSerialHash); | 
 |  | 
 |     return {idBuff}; | 
 | } | 
 |  | 
 | std::unique_ptr<X509, decltype(&::X509_free)> parseCert(const std::string& pem) | 
 | { | 
 |     if (pem.size() > INT_MAX) | 
 |     { | 
 |         lg2::error("Error occurred during parseCert: PEM is too long"); | 
 |         elog<InvalidCertificate>(Reason("Invalid PEM: too long")); | 
 |     } | 
 |     X509Ptr cert(X509_new(), ::X509_free); | 
 |     if (!cert) | 
 |     { | 
 |         lg2::error("Error occurred during X509_new call, ERRCODE:{ERRCODE}", | 
 |                    "ERRCODE", ERR_get_error()); | 
 |         elog<InternalFailure>(); | 
 |     } | 
 |  | 
 |     BIOMemPtr bioCert(BIO_new_mem_buf(pem.data(), static_cast<int>(pem.size())), | 
 |                       ::BIO_free); | 
 |     X509* x509 = cert.get(); | 
 |     if (!PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr)) | 
 |     { | 
 |         lg2::error("Error occurred during PEM_read_bio_X509 call, PEM:{PEM}", | 
 |                    "PEM", pem); | 
 |         elog<InternalFailure>(); | 
 |     } | 
 |     return cert; | 
 | } | 
 | } // namespace phosphor::certs |