#include "config.h" #include "x509_utils.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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; using X509StoreCtxPtr = std::unique_ptr; using X509Ptr = std::unique_ptr; using BIOMemPtr = std::unique_ptr; using ASN1TimePtr = std::unique_ptr; using SSLCtxPtr = std::unique_ptr; // 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(); } 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(); } // 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(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(); } 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(); } 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(); } 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( 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(); } errCode = X509_STORE_CTX_init(storeCtx.get(), &x509Store, &cert, nullptr); if (errCode != 1) { lg2::error("Error occurred during X509_STORE_CTX_init call"); elog(); } // 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(); } // 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(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(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(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 parseCert(const std::string& pem) { if (pem.size() > INT_MAX) { lg2::error("Error occurred during parseCert: PEM is too long"); elog(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(); } BIOMemPtr bioCert(BIO_new_mem_buf(pem.data(), static_cast(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(); } return cert; } } // namespace phosphor::certs