blob: e3fe60908c3b92c2ee378cf1047b9ffd68ea876d [file] [log] [blame]
#include "config.h"
#include "certificate.hpp"
#include "certs_manager.hpp"
#include "x509_utils.hpp"
#include <openssl/asn1.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/obj_mac.h>
#include <openssl/objects.h>
#include <openssl/opensslv.h>
#include <openssl/pem.h>
#include <openssl/x509v3.h>
#include <phosphor-logging/elog-errors.hpp>
#include <phosphor-logging/elog.hpp>
#include <phosphor-logging/lg2.hpp>
#include <watch.hpp>
#include <xyz/openbmc_project/Certs/error.hpp>
#include <xyz/openbmc_project/Common/error.hpp>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <exception>
#include <filesystem>
#include <fstream>
#include <map>
#include <utility>
#include <vector>
namespace phosphor::certs
{
namespace
{
namespace fs = std::filesystem;
using ::phosphor::logging::elog;
using InvalidCertificateError =
::sdbusplus::xyz::openbmc_project::Certs::Error::InvalidCertificate;
using ::phosphor::logging::xyz::openbmc_project::Certs::InvalidCertificate;
using ::sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
// RAII support for openSSL functions.
using BIOMemPtr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
using X509StorePtr = std::unique_ptr<X509_STORE, decltype(&::X509_STORE_free)>;
using ASN1TimePtr = std::unique_ptr<ASN1_TIME, decltype(&ASN1_STRING_free)>;
using EVPPkeyPtr = std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>;
using BufMemPtr = std::unique_ptr<BUF_MEM, decltype(&::BUF_MEM_free)>;
// Refer to schema 2018.3
// http://redfish.dmtf.org/schemas/v1/Certificate.json#/definitions/KeyUsage for
// supported KeyUsage types in redfish
// Refer to
// https://github.com/openssl/openssl/blob/master/include/openssl/x509v3.h for
// key usage bit fields
std::map<uint8_t, std::string> keyUsageToRfStr = {
{KU_DIGITAL_SIGNATURE, "DigitalSignature"},
{KU_NON_REPUDIATION, "NonRepudiation"},
{KU_KEY_ENCIPHERMENT, "KeyEncipherment"},
{KU_DATA_ENCIPHERMENT, "DataEncipherment"},
{KU_KEY_AGREEMENT, "KeyAgreement"},
{KU_KEY_CERT_SIGN, "KeyCertSign"},
{KU_CRL_SIGN, "CRLSigning"},
{KU_ENCIPHER_ONLY, "EncipherOnly"},
{KU_DECIPHER_ONLY, "DecipherOnly"}};
// Refer to schema 2018.3
// http://redfish.dmtf.org/schemas/v1/Certificate.json#/definitions/KeyUsage for
// supported Extended KeyUsage types in redfish
std::map<uint8_t, std::string> extendedKeyUsageToRfStr = {
{NID_server_auth, "ServerAuthentication"},
{NID_client_auth, "ClientAuthentication"},
{NID_email_protect, "EmailProtection"},
{NID_OCSP_sign, "OCSPSigning"},
{NID_ad_timeStamping, "Timestamping"},
{NID_code_sign, "CodeSigning"}};
/**
* @brief Dumps the PEM encoded certificate to installFilePath
*
* @param[in] pem - PEM encoded X509 certificate buffer.
* @param[in] certFilePath - Path to the destination file.
*
* @return void
*/
void dumpCertificate(const std::string& pem, const std::string& certFilePath)
{
std::ofstream outputCertFileStream;
outputCertFileStream.exceptions(
std::ofstream::failbit | std::ofstream::badbit | std::ofstream::eofbit);
try
{
outputCertFileStream.open(certFilePath, std::ios::out);
outputCertFileStream << pem << "\n" << std::flush;
outputCertFileStream.close();
}
catch (const std::exception& e)
{
lg2::error(
"Failed to dump certificate, ERR:{ERR}, SRC_PEM:{SRC_PEM}, DST:{DST}",
"ERR", e, "SRC_PEM", pem, "DST", certFilePath);
elog<InternalFailure>();
}
}
} // namespace
void Certificate::copyCertificate(const std::string& certSrcFilePath,
const std::string& certFilePath)
{
// Copy the certificate to the installation path
// During bootup will be parsing existing file so no need to
// copy it.
if (certSrcFilePath != certFilePath)
{
std::ifstream inputCertFileStream;
std::ofstream outputCertFileStream;
inputCertFileStream.exceptions(std::ifstream::failbit |
std::ifstream::badbit |
std::ifstream::eofbit);
outputCertFileStream.exceptions(std::ofstream::failbit |
std::ofstream::badbit |
std::ofstream::eofbit);
try
{
inputCertFileStream.open(certSrcFilePath);
outputCertFileStream.open(certFilePath, std::ios::out);
outputCertFileStream << inputCertFileStream.rdbuf() << std::flush;
inputCertFileStream.close();
outputCertFileStream.close();
}
catch (const std::exception& e)
{
lg2::error(
"Failed to copy certificate, ERR:{ERR}, SRC:{SRC}, DST:{DST}",
"ERR", e, "SRC", certSrcFilePath, "DST", certFilePath);
elog<InternalFailure>();
}
}
}
std::string
Certificate::generateUniqueFilePath(const std::string& directoryPath)
{
char* filePath = tempnam(directoryPath.c_str(), nullptr);
if (filePath == nullptr)
{
lg2::error(
"Error occurred while creating random certificate file path, DIR:{DIR}",
"DIR", directoryPath);
elog<InternalFailure>();
}
std::string filePathStr(filePath);
free(filePath);
return filePathStr;
}
std::string Certificate::generateAuthCertFileX509Path(
const std::string& certSrcFilePath, const std::string& certDstDirPath)
{
const internal::X509Ptr cert = loadCert(certSrcFilePath);
unsigned long hash = X509_subject_name_hash(cert.get());
static constexpr auto certHashLength = 9;
char hashBuf[certHashLength];
snprintf(hashBuf, certHashLength, "%08lx", hash);
const std::string certHash(hashBuf);
for (size_t i = 0; i < maxNumAuthorityCertificates; ++i)
{
const std::string certDstFileX509Path =
certDstDirPath + "/" + certHash + "." + std::to_string(i);
if (!fs::exists(certDstFileX509Path))
{
return certDstFileX509Path;
}
}
lg2::error("Authority certificate x509 file path already used, DIR:{DIR}",
"DIR", certDstDirPath);
elog<InternalFailure>();
}
std::string
Certificate::generateAuthCertFilePath(const std::string& certSrcFilePath)
{
// If there is a certificate file path (which means certificate replacement
// is doing) use it (do not create new one)
if (!certFilePath.empty())
{
return certFilePath;
}
// If source certificate file is located in the certificates directory use
// it (do not create new one)
else if (fs::path(certSrcFilePath).parent_path().string() ==
certInstallPath)
{
return certSrcFilePath;
}
// Otherwise generate new file name/path
else
{
return generateUniqueFilePath(certInstallPath);
}
}
std::string
Certificate::generateCertFilePath(const std::string& certSrcFilePath)
{
if (certType == CertificateType::authority)
{
return generateAuthCertFilePath(certSrcFilePath);
}
else
{
return certInstallPath;
}
}
Certificate::Certificate(sdbusplus::bus_t& bus, const std::string& objPath,
CertificateType type, const std::string& installPath,
const std::string& uploadPath, Watch* watch,
Manager& parent, bool restore) :
internal::CertificateInterface(
bus, objPath.c_str(),
internal::CertificateInterface::action::defer_emit),
objectPath(objPath), certType(type), certInstallPath(installPath),
certWatch(watch), manager(parent)
{
auto installHelper = [this](const auto& filePath) {
if (!compareKeys(filePath))
{
elog<InvalidCertificateError>(InvalidCertificate::REASON(
"Private key does not match the Certificate"));
};
};
typeFuncMap[CertificateType::server] = installHelper;
typeFuncMap[CertificateType::client] = installHelper;
typeFuncMap[CertificateType::authority] = [](const std::string&) {};
auto appendPrivateKey = [this](const std::string& filePath) {
checkAndAppendPrivateKey(filePath);
};
appendKeyMap[CertificateType::server] = appendPrivateKey;
appendKeyMap[CertificateType::client] = appendPrivateKey;
appendKeyMap[CertificateType::authority] = [](const std::string&) {};
// Generate certificate file path
certFilePath = generateCertFilePath(uploadPath);
// install the certificate
install(uploadPath, restore);
this->emit_object_added();
}
Certificate::Certificate(sdbusplus::bus_t& bus, const std::string& objPath,
const CertificateType& type,
const std::string& installPath, X509_STORE& x509Store,
const std::string& pem, Watch* watchPtr,
Manager& parent, bool restore) :
internal::CertificateInterface(
bus, objPath.c_str(),
internal::CertificateInterface::action::defer_emit),
objectPath(objPath), certType(type), certInstallPath(installPath),
certWatch(watchPtr), manager(parent)
{
// Generate certificate file path
certFilePath = generateUniqueFilePath(installPath);
// install the certificate
install(x509Store, pem, restore);
this->emit_object_added();
}
Certificate::~Certificate()
{
if (!fs::remove(certFilePath))
{
lg2::info("Certificate file not found! PATH:{PATH}", "PATH",
certFilePath);
}
}
void Certificate::replace(const std::string filePath)
{
manager.replaceCertificate(this, filePath);
}
void Certificate::install(const std::string& certSrcFilePath, bool restore)
{
if (restore)
{
lg2::debug("Certificate install, FILEPATH:{FILEPATH}", "FILEPATH",
certSrcFilePath);
}
else
{
lg2::info("Certificate install, FILEPATH:{FILEPATH}", "FILEPATH",
certSrcFilePath);
}
// stop watch for user initiated certificate install
if (certWatch != nullptr)
{
certWatch->stopWatch();
}
// Verify the certificate file
fs::path file(certSrcFilePath);
if (!fs::exists(file))
{
lg2::error("File is Missing, FILE:{FILE}", "FILE", certSrcFilePath);
elog<InternalFailure>();
}
try
{
if (fs::file_size(certSrcFilePath) == 0)
{
// file is empty
lg2::error("File is empty, FILE:{FILE}", "FILE", certSrcFilePath);
elog<InvalidCertificateError>(
InvalidCertificate::REASON("File is empty"));
}
}
catch (const fs::filesystem_error& e)
{
// Log Error message
lg2::error("File is empty, FILE:{FILE}, ERR:{ERR}", "FILE",
certSrcFilePath, "ERR", e);
elog<InternalFailure>();
}
X509StorePtr x509Store = getX509Store(certSrcFilePath);
// Load Certificate file into the X509 structure.
internal::X509Ptr cert = loadCert(certSrcFilePath);
// Perform validation
validateCertificateAgainstStore(*x509Store, *cert);
validateCertificateStartDate(*cert);
validateCertificateInSSLContext(*cert);
// Invoke type specific append private key function.
if (auto it = appendKeyMap.find(certType); it == appendKeyMap.end())
{
lg2::error("Unsupported Type, TYPE:{TYPE}", "TYPE",
certificateTypeToString(certType));
elog<InternalFailure>();
}
else
{
it->second(certSrcFilePath);
}
// Invoke type specific compare keys function.
if (auto it = typeFuncMap.find(certType); it == typeFuncMap.end())
{
lg2::error("Unsupported Type, TYPE:{TYPE}", "TYPE",
certificateTypeToString(certType));
elog<InternalFailure>();
}
else
{
it->second(certSrcFilePath);
}
copyCertificate(certSrcFilePath, certFilePath);
storageUpdate();
// Keep certificate ID
certId = generateCertId(*cert);
// Parse the certificate file and populate properties
populateProperties(*cert);
// restart watch
if (certWatch != nullptr)
{
certWatch->startWatch();
}
}
void Certificate::install(X509_STORE& x509Store, const std::string& pem,
bool restore)
{
if (restore)
{
lg2::debug("Certificate install, PEM_STR:{PEM_STR}", "PEM_STR", pem);
}
else
{
lg2::info("Certificate install, PEM_STR:{PEM_STR} ", "PEM_STR", pem);
}
if (certType != CertificateType::authority)
{
lg2::error("Bulk install error: Unsupported Type; only authority "
"supports bulk install, TYPE:{TYPE}",
"TYPE", certificateTypeToString(certType));
elog<InternalFailure>();
}
// stop watch for user initiated certificate install
if (certWatch)
{
certWatch->stopWatch();
}
// Load Certificate file into the X509 structure.
internal::X509Ptr cert = parseCert(pem);
// Perform validation; no type specific compare keys function
validateCertificateAgainstStore(x509Store, *cert);
validateCertificateStartDate(*cert);
validateCertificateInSSLContext(*cert);
// Copy the PEM to the installation path
dumpCertificate(pem, certFilePath);
storageUpdate();
// Keep certificate ID
certId = generateCertId(*cert);
// Parse the certificate file and populate properties
populateProperties(*cert);
// restart watch
if (certWatch)
{
certWatch->startWatch();
}
}
void Certificate::populateProperties()
{
internal::X509Ptr cert = loadCert(certInstallPath);
populateProperties(*cert);
}
std::string Certificate::getCertId() const
{
return certId;
}
bool Certificate::isSame(const std::string& certPath)
{
internal::X509Ptr cert = loadCert(certPath);
return getCertId() == generateCertId(*cert);
}
void Certificate::storageUpdate()
{
if (certType == CertificateType::authority)
{
// Create symbolic link in the certificate directory
std::string certFileX509Path;
try
{
if (!certFilePath.empty() &&
fs::is_regular_file(fs::path(certFilePath)))
{
certFileX509Path =
generateAuthCertFileX509Path(certFilePath, certInstallPath);
fs::create_symlink(fs::path(certFilePath),
fs::path(certFileX509Path));
}
}
catch (const std::exception& e)
{
lg2::error("Failed to create symlink for certificate, ERR:{ERR},"
"FILE:{FILE}, SYMLINK:{SYMLINK}",
"ERR", e, "FILE", certFilePath, "SYMLINK",
certFileX509Path);
elog<InternalFailure>();
}
}
}
void Certificate::populateProperties(X509& cert)
{
// Update properties if no error thrown
BIOMemPtr certBio(BIO_new(BIO_s_mem()), BIO_free);
PEM_write_bio_X509(certBio.get(), &cert);
BufMemPtr certBuf(BUF_MEM_new(), BUF_MEM_free);
BUF_MEM* buf = certBuf.get();
BIO_get_mem_ptr(certBio.get(), &buf);
std::string certStr(buf->data, buf->length);
certificateString(certStr);
static const int maxKeySize = 4096;
char subBuffer[maxKeySize] = {0};
BIOMemPtr subBio(BIO_new(BIO_s_mem()), BIO_free);
// This pointer cannot be freed independantly.
X509_NAME* sub = X509_get_subject_name(&cert);
X509_NAME_print_ex(subBio.get(), sub, 0, XN_FLAG_SEP_COMMA_PLUS);
BIO_read(subBio.get(), subBuffer, maxKeySize);
subject(subBuffer);
char issuerBuffer[maxKeySize] = {0};
BIOMemPtr issuerBio(BIO_new(BIO_s_mem()), BIO_free);
// This pointer cannot be freed independantly.
X509_NAME* issuerName = X509_get_issuer_name(&cert);
X509_NAME_print_ex(issuerBio.get(), issuerName, 0, XN_FLAG_SEP_COMMA_PLUS);
BIO_read(issuerBio.get(), issuerBuffer, maxKeySize);
issuer(issuerBuffer);
std::vector<std::string> keyUsageList;
ASN1_BIT_STRING* usage;
// Go through each usage in the bit string and convert to
// corresponding string value
if ((usage = static_cast<ASN1_BIT_STRING*>(
X509_get_ext_d2i(&cert, NID_key_usage, nullptr, nullptr))))
{
for (auto i = 0; i < usage->length; ++i)
{
for (auto& x : keyUsageToRfStr)
{
if (x.first & usage->data[i])
{
keyUsageList.push_back(x.second);
break;
}
}
}
}
EXTENDED_KEY_USAGE* extUsage;
if ((extUsage = static_cast<EXTENDED_KEY_USAGE*>(
X509_get_ext_d2i(&cert, NID_ext_key_usage, nullptr, nullptr))))
{
for (int i = 0; i < sk_ASN1_OBJECT_num(extUsage); i++)
{
keyUsageList.push_back(extendedKeyUsageToRfStr[OBJ_obj2nid(
sk_ASN1_OBJECT_value(extUsage, i))]);
}
}
keyUsage(keyUsageList);
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");
static const uint64_t dayToSeconds = 24 * 60 * 60;
ASN1_TIME* notAfter = X509_get_notAfter(&cert);
ASN1_TIME_diff(&days, &secs, epoch.get(), notAfter);
validNotAfter((days * dayToSeconds) + secs);
ASN1_TIME* notBefore = X509_get_notBefore(&cert);
ASN1_TIME_diff(&days, &secs, epoch.get(), notBefore);
validNotBefore((days * dayToSeconds) + secs);
}
void Certificate::checkAndAppendPrivateKey(const std::string& filePath)
{
BIOMemPtr keyBio(BIO_new(BIO_s_file()), ::BIO_free);
if (!keyBio)
{
lg2::error("Error occurred during BIO_s_file call, FILE:{FILE}", "FILE",
filePath);
elog<InternalFailure>();
}
BIO_read_filename(keyBio.get(), filePath.c_str());
EVPPkeyPtr priKey(
PEM_read_bio_PrivateKey(keyBio.get(), nullptr, nullptr, nullptr),
::EVP_PKEY_free);
if (!priKey)
{
lg2::info("Private key not present in file, FILE:{FILE}", "FILE",
filePath);
fs::path privateKeyFile = fs::path(certInstallPath).parent_path();
privateKeyFile = privateKeyFile / defaultPrivateKeyFileName;
if (!fs::exists(privateKeyFile))
{
lg2::error("Private key file is not found, FILE:{FILE}", "FILE",
privateKeyFile);
elog<InternalFailure>();
}
std::ifstream privKeyFileStream;
std::ofstream certFileStream;
privKeyFileStream.exceptions(std::ifstream::failbit |
std::ifstream::badbit |
std::ifstream::eofbit);
certFileStream.exceptions(std::ofstream::failbit |
std::ofstream::badbit |
std::ofstream::eofbit);
try
{
privKeyFileStream.open(privateKeyFile);
certFileStream.open(filePath, std::ios::app);
certFileStream << std::endl; // insert line break
certFileStream << privKeyFileStream.rdbuf() << std::flush;
privKeyFileStream.close();
certFileStream.close();
}
catch (const std::exception& e)
{
lg2::error(
"Failed to append private key, ERR:{ERR}, SRC:{SRC}, DST:{DST}",
"ERR", e, "SRC", privateKeyFile, "DST", filePath);
elog<InternalFailure>();
}
}
}
bool Certificate::compareKeys(const std::string& filePath)
{
lg2::info("Certificate compareKeys, FILEPATH:{FILEPATH}", "FILEPATH",
filePath);
internal::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();
PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr);
EVPPkeyPtr pubKey(X509_get_pubkey(cert.get()), ::EVP_PKEY_free);
if (!pubKey)
{
lg2::error(
"Error occurred during X509_get_pubkey, FILE:{FILE}, ERRCODE:{ERRCODE}",
"FILE", filePath, "ERRCODE", ERR_get_error());
elog<InvalidCertificateError>(
InvalidCertificate::REASON("Failed to get public key info"));
}
BIOMemPtr keyBio(BIO_new(BIO_s_file()), ::BIO_free);
if (!keyBio)
{
lg2::error("Error occurred during BIO_s_file call, FILE:{FILE}", "FILE",
filePath);
elog<InternalFailure>();
}
BIO_read_filename(keyBio.get(), filePath.c_str());
EVPPkeyPtr priKey(
PEM_read_bio_PrivateKey(keyBio.get(), nullptr, nullptr, nullptr),
::EVP_PKEY_free);
if (!priKey)
{
lg2::error(
"Error occurred during PEM_read_bio_PrivateKey, FILE:{FILE}, ERRCODE:{ERRCODE}",
"FILE", filePath, "ERRCODE", ERR_get_error());
elog<InvalidCertificateError>(
InvalidCertificate::REASON("Failed to get private key info"));
}
#if (OPENSSL_VERSION_NUMBER < 0x30000000L)
int32_t rc = EVP_PKEY_cmp(priKey.get(), pubKey.get());
#else
int32_t rc = EVP_PKEY_eq(priKey.get(), pubKey.get());
#endif
if (rc != 1)
{
lg2::error(
"Private key is not matching with Certificate, FILE:{FILE}, ERRCODE:{ERRCODE}",
"FILE", filePath, "ERRCODE", rc);
return false;
}
return true;
}
void Certificate::delete_()
{
manager.deleteCertificate(this);
}
std::string Certificate::getObjectPath()
{
return objectPath;
}
std::string Certificate::getCertFilePath()
{
return certFilePath;
}
void Certificate::setCertFilePath(const std::string& path)
{
certFilePath = path;
}
void Certificate::setCertInstallPath(const std::string& path)
{
certInstallPath = path;
}
} // namespace phosphor::certs