blob: bc3ccbb2be81d4bd04d48dfd2950d267b3d8c68b [file] [log] [blame]
#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"));
}
// Loging 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