blob: be1b98f21c551df57b5748902d78954d4f71f54e [file] [log] [blame]
#include "config.h"
#include "certs_manager.hpp"
#include "x509_utils.hpp"
#include <openssl/asn1.h>
#include <openssl/bn.h>
#include <openssl/ec.h>
#include <openssl/evp.h>
#include <openssl/obj_mac.h>
#include <openssl/objects.h>
#include <openssl/opensslv.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <unistd.h>
#include <phosphor-logging/elog-errors.hpp>
#include <phosphor-logging/elog.hpp>
#include <phosphor-logging/lg2.hpp>
#include <sdbusplus/bus.hpp>
#include <sdbusplus/exception.hpp>
#include <sdbusplus/message.hpp>
#include <sdeventplus/source/base.hpp>
#include <sdeventplus/source/child.hpp>
#include <xyz/openbmc_project/Certs/error.hpp>
#include <xyz/openbmc_project/Common/error.hpp>
#include <algorithm>
#include <array>
#include <cerrno>
#include <chrono>
#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <exception>
#include <fstream>
#include <utility>
namespace phosphor::certs
{
namespace
{
namespace fs = std::filesystem;
using ::phosphor::logging::commit;
using ::phosphor::logging::elog;
using ::phosphor::logging::report;
using ::sdbusplus::xyz::openbmc_project::Certs::Error::InvalidCertificate;
using ::sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
using ::sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed;
using NotAllowedReason =
::phosphor::logging::xyz::openbmc_project::Common::NotAllowed::REASON;
using InvalidCertificateReason = ::phosphor::logging::xyz::openbmc_project::
Certs::InvalidCertificate::REASON;
using ::sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
using Argument =
::phosphor::logging::xyz::openbmc_project::Common::InvalidArgument;
// RAII support for openSSL functions.
using X509ReqPtr = std::unique_ptr<X509_REQ, decltype(&::X509_REQ_free)>;
using EVPPkeyPtr = std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>;
using BignumPtr = std::unique_ptr<BIGNUM, decltype(&::BN_free)>;
using X509StorePtr = std::unique_ptr<X509_STORE, decltype(&::X509_STORE_free)>;
constexpr int supportedKeyBitLength = 2048;
constexpr int defaultKeyBitLength = 2048;
// secp224r1 is equal to RSA 2048 KeyBitLength. Refer RFC 5349
constexpr auto defaultKeyCurveID = "secp224r1";
// PEM certificate block markers, defined in go/rfc/7468.
constexpr std::string_view beginCertificate = "-----BEGIN CERTIFICATE-----";
constexpr std::string_view endCertificate = "-----END CERTIFICATE-----";
/**
* @brief Splits the given authorities list file and returns an array of
* individual PEM encoded x509 certificate.
*
* @param[in] sourceFilePath - Path to the authorities list file.
*
* @return An array of individual PEM encoded x509 certificate
*/
std::vector<std::string> splitCertificates(const std::string& sourceFilePath)
{
std::ifstream inputCertFileStream;
inputCertFileStream.exceptions(
std::ifstream::failbit | std::ifstream::badbit | std::ifstream::eofbit);
std::stringstream pemStream;
std::vector<std::string> certificatesList;
try
{
inputCertFileStream.open(sourceFilePath);
pemStream << inputCertFileStream.rdbuf();
inputCertFileStream.close();
}
catch (const std::exception& e)
{
lg2::error("Failed to read certificates list, ERR:{ERR}, SRC:{SRC}",
"ERR", e, "SRC", sourceFilePath);
elog<InternalFailure>();
}
std::string pem = pemStream.str();
size_t begin = 0;
// |begin| points to the current start position for searching the next
// |beginCertificate| block. When we find the beginning of the certificate,
// we extract the content between the beginning and the end of the current
// certificate. And finally we move |begin| to the end of the current
// certificate to start searching the next potential certificate.
for (begin = pem.find(beginCertificate, begin); begin != std::string::npos;
begin = pem.find(beginCertificate, begin))
{
size_t end = pem.find(endCertificate, begin);
if (end == std::string::npos)
{
lg2::error(
"invalid PEM contains a BEGIN identifier without an END");
elog<InvalidCertificate>(InvalidCertificateReason(
"invalid PEM contains a BEGIN identifier without an END"));
}
end += endCertificate.size();
certificatesList.emplace_back(pem.substr(begin, end - begin));
begin = end;
}
return certificatesList;
}
} // namespace
Manager::Manager(sdbusplus::bus_t& bus, sdeventplus::Event& event,
const char* path, CertificateType type,
const std::string& unit, const std::string& installPath) :
internal::ManagerInterface(bus, path),
bus(bus), event(event), objectPath(path), certType(type),
unitToRestart(std::move(unit)), certInstallPath(std::move(installPath)),
certParentInstallPath(fs::path(certInstallPath).parent_path())
{
try
{
// Create certificate directory if not existing.
// Set correct certificate directory permissions.
fs::path certDirectory;
try
{
if (certType == CertificateType::authority)
{
certDirectory = certInstallPath;
}
else
{
certDirectory = certParentInstallPath;
}
if (!fs::exists(certDirectory))
{
fs::create_directories(certDirectory);
}
auto permission = fs::perms::owner_read | fs::perms::owner_write |
fs::perms::owner_exec;
fs::permissions(certDirectory, permission,
fs::perm_options::replace);
storageUpdate();
}
catch (const fs::filesystem_error& e)
{
lg2::error(
"Failed to create directory, ERR:{ERR}, DIRECTORY:{DIRECTORY}",
"ERR", e, "DIRECTORY", certParentInstallPath);
report<InternalFailure>();
}
// Generating RSA private key file if certificate type is server/client
if (certType != CertificateType::authority)
{
createRSAPrivateKeyFile();
}
// restore any existing certificates
createCertificates();
// watch is not required for authority certificates
if (certType != CertificateType::authority)
{
// watch for certificate file create/replace
certWatchPtr = std::make_unique<Watch>(event, certInstallPath,
[this]() {
try
{
// if certificate file existing update it
if (!installedCerts.empty())
{
lg2::info("Inotify callback to update "
"certificate properties");
installedCerts[0]->populateProperties();
}
else
{
lg2::info(
"Inotify callback to create certificate object");
createCertificates();
}
}
catch (const InternalFailure& e)
{
commit<InternalFailure>();
}
catch (const InvalidCertificate& e)
{
commit<InvalidCertificate>();
}
});
}
else
{
try
{
const std::string singleCertPath = "/etc/ssl/certs/Root-CA.pem";
if (fs::exists(singleCertPath) && !fs::is_empty(singleCertPath))
{
lg2::notice(
"Legacy certificate detected, will be installed from,"
"SINGLE_CERTPATH:{SINGLE_CERTPATH}",
"SINGLE_CERTPATH", singleCertPath);
install(singleCertPath);
if (!fs::remove(singleCertPath))
{
lg2::error("Unable to remove old certificate from,"
"SINGLE_CERTPATH:{SINGLE_CERTPATH}",
"SINGLE_CERTPATH", singleCertPath);
elog<InternalFailure>();
}
}
}
catch (const std::exception& ex)
{
lg2::error(
"Error in restoring legacy certificate, ERROR_STR:{ERROR_STR}",
"ERROR_STR", ex);
}
}
}
catch (const std::exception& ex)
{
lg2::error(
"Error in certificate manager constructor, ERROR_STR:{ERROR_STR}",
"ERROR_STR", ex);
}
}
std::string Manager::install(const std::string filePath)
{
if (certType != CertificateType::authority && !installedCerts.empty())
{
elog<NotAllowed>(NotAllowedReason("Certificate already exist"));
}
else if (certType == CertificateType::authority &&
installedCerts.size() >= maxNumAuthorityCertificates)
{
elog<NotAllowed>(NotAllowedReason("Certificates limit reached"));
}
std::string certObjectPath;
if (isCertificateUnique(filePath))
{
certObjectPath = objectPath + '/' + std::to_string(certIdCounter);
installedCerts.emplace_back(std::make_unique<Certificate>(
bus, certObjectPath, certType, certInstallPath, filePath,
certWatchPtr.get(), *this, /*restore=*/false));
reloadOrReset(unitToRestart);
certIdCounter++;
}
else
{
elog<NotAllowed>(NotAllowedReason("Certificate already exist"));
}
return certObjectPath;
}
std::vector<sdbusplus::message::object_path>
Manager::installAll(const std::string filePath)
{
if (certType != CertificateType::authority)
{
elog<NotAllowed>(
NotAllowedReason("The InstallAll interface is only allowed for "
"Authority certificates"));
}
if (!installedCerts.empty())
{
elog<NotAllowed>(NotAllowedReason(
"There are already root certificates; Call DeleteAll then "
"InstallAll, or use ReplaceAll"));
}
fs::path sourceFile(filePath);
if (!fs::exists(sourceFile))
{
lg2::error("File is Missing, FILE:{FILE}", "FILE", filePath);
elog<InternalFailure>();
}
std::vector<std::string> authorities = splitCertificates(sourceFile);
if (authorities.size() > maxNumAuthorityCertificates)
{
elog<NotAllowed>(NotAllowedReason("Certificates limit reached"));
}
lg2::info("Starts authority list install");
fs::path authorityStore(certInstallPath);
fs::path authoritiesListFile = authorityStore /
defaultAuthoritiesListFileName;
// Atomically install all the certificates
fs::path tempPath = Certificate::generateUniqueFilePath(authorityStore);
fs::create_directory(tempPath);
// Copies the authorities list
Certificate::copyCertificate(sourceFile,
tempPath / defaultAuthoritiesListFileName);
std::vector<std::unique_ptr<Certificate>> tempCertificates;
uint64_t tempCertIdCounter = certIdCounter;
X509StorePtr x509Store = getX509Store(sourceFile);
for (const auto& authority : authorities)
{
std::string certObjectPath = objectPath + '/' +
std::to_string(tempCertIdCounter);
tempCertificates.emplace_back(std::make_unique<Certificate>(
bus, certObjectPath, certType, tempPath, *x509Store, authority,
certWatchPtr.get(), *this, /*restore=*/false));
tempCertIdCounter++;
}
// We are good now, issue swap
installedCerts = std::move(tempCertificates);
certIdCounter = tempCertIdCounter;
// Rename all the certificates including the authorities list
for (const fs::path& f : fs::directory_iterator(tempPath))
{
if (fs::is_symlink(f))
{
continue;
}
fs::rename(/*from=*/f, /*to=*/certInstallPath / f.filename());
}
// Update file locations and create symbol links
for (const auto& cert : installedCerts)
{
cert->setCertInstallPath(certInstallPath);
cert->setCertFilePath(certInstallPath /
fs::path(cert->getCertFilePath()).filename());
cert->storageUpdate();
}
// Remove the temporary folder
fs::remove_all(tempPath);
std::vector<sdbusplus::message::object_path> objects;
for (const auto& certificate : installedCerts)
{
objects.emplace_back(certificate->getObjectPath());
}
lg2::info("Finishes authority list install; reload units starts");
reloadOrReset(unitToRestart);
return objects;
}
std::vector<sdbusplus::message::object_path>
Manager::replaceAll(std::string filePath)
{
installedCerts.clear();
certIdCounter = 1;
storageUpdate();
return installAll(std::move(filePath));
}
void Manager::deleteAll()
{
// TODO: #Issue 4 when a certificate is deleted system auto generates
// certificate file. At present we are not supporting creation of
// certificate object for the auto-generated certificate file as
// deletion if only applicable for REST server and Bmcweb does not allow
// deletion of certificates
installedCerts.clear();
// If the authorities list exists, delete it as well
if (certType == CertificateType::authority)
{
if (fs::path authoritiesList = fs::path(certInstallPath) /
defaultAuthoritiesListFileName;
fs::exists(authoritiesList))
{
fs::remove(authoritiesList);
}
}
certIdCounter = 1;
storageUpdate();
reloadOrReset(unitToRestart);
}
void Manager::deleteCertificate(const Certificate* const certificate)
{
const std::vector<std::unique_ptr<Certificate>>::iterator& certIt =
std::find_if(installedCerts.begin(), installedCerts.end(),
[certificate](const std::unique_ptr<Certificate>& cert) {
return (cert.get() == certificate);
});
if (certIt != installedCerts.end())
{
installedCerts.erase(certIt);
storageUpdate();
reloadOrReset(unitToRestart);
}
else
{
lg2::error("Certificate does not exist, ID:{ID}", "ID",
certificate->getCertId());
elog<InternalFailure>();
}
}
void Manager::replaceCertificate(Certificate* const certificate,
const std::string& filePath)
{
if (isCertificateUnique(filePath, certificate))
{
certificate->install(filePath, false);
storageUpdate();
reloadOrReset(unitToRestart);
}
else
{
elog<NotAllowed>(NotAllowedReason("Certificate already exist"));
}
}
std::string Manager::generateCSR(
std::vector<std::string> alternativeNames, std::string challengePassword,
std::string city, std::string commonName, std::string contactPerson,
std::string country, std::string email, std::string givenName,
std::string initials, int64_t keyBitLength, std::string keyCurveId,
std::string keyPairAlgorithm, std::vector<std::string> keyUsage,
std::string organization, std::string organizationalUnit, std::string state,
std::string surname, std::string unstructuredName)
{
// We support only one CSR.
csrPtr.reset(nullptr);
auto pid = fork();
if (pid == -1)
{
lg2::error("Error occurred during forking process");
report<InternalFailure>();
}
else if (pid == 0)
{
try
{
generateCSRHelper(alternativeNames, challengePassword, city,
commonName, contactPerson, country, email,
givenName, initials, keyBitLength, keyCurveId,
keyPairAlgorithm, keyUsage, organization,
organizationalUnit, state, surname,
unstructuredName);
exit(EXIT_SUCCESS);
}
catch (const InternalFailure& e)
{
// commit the error reported in child process and exit
// Callback method from SDEvent Loop looks for exit status
exit(EXIT_FAILURE);
commit<InternalFailure>();
}
catch (const InvalidArgument& e)
{
// commit the error reported in child process and exit
// Callback method from SDEvent Loop looks for exit status
exit(EXIT_FAILURE);
commit<InvalidArgument>();
}
}
else
{
using namespace sdeventplus::source;
Child::Callback callback = [this](Child& eventSource,
const siginfo_t* si) {
eventSource.set_enabled(Enabled::On);
if (si->si_status != 0)
{
this->createCSRObject(Status::failure);
}
else
{
this->createCSRObject(Status::success);
}
};
try
{
sigset_t ss;
if (sigemptyset(&ss) < 0)
{
lg2::error("Unable to initialize signal set");
elog<InternalFailure>();
}
if (sigaddset(&ss, SIGCHLD) < 0)
{
lg2::error("Unable to add signal to signal set");
elog<InternalFailure>();
}
// Block SIGCHLD first, so that the event loop can handle it
if (sigprocmask(SIG_BLOCK, &ss, nullptr) < 0)
{
lg2::error("Unable to block signal");
elog<InternalFailure>();
}
if (childPtr)
{
childPtr.reset();
}
childPtr = std::make_unique<Child>(event, pid, WEXITED | WSTOPPED,
std::move(callback));
}
catch (const InternalFailure& e)
{
commit<InternalFailure>();
}
}
auto csrObjectPath = objectPath + '/' + "csr";
return csrObjectPath;
}
std::vector<std::unique_ptr<Certificate>>& Manager::getCertificates()
{
return installedCerts;
}
void Manager::generateCSRHelper(
std::vector<std::string> alternativeNames, std::string challengePassword,
std::string city, std::string commonName, std::string contactPerson,
std::string country, std::string email, std::string givenName,
std::string initials, int64_t keyBitLength, std::string keyCurveId,
std::string keyPairAlgorithm, std::vector<std::string> keyUsage,
std::string organization, std::string organizationalUnit, std::string state,
std::string surname, std::string unstructuredName)
{
int ret = 0;
// set version of x509 req
int nVersion = 1;
X509ReqPtr x509Req(X509_REQ_new(), ::X509_REQ_free);
ret = X509_REQ_set_version(x509Req.get(), nVersion);
if (ret == 0)
{
lg2::error("Error occurred during X509_REQ_set_version call");
elog<InternalFailure>();
}
// set subject of x509 req
X509_NAME* x509Name = X509_REQ_get_subject_name(x509Req.get());
if (!alternativeNames.empty())
{
for (auto& name : alternativeNames)
{
addEntry(x509Name, "subjectAltName", name);
}
}
addEntry(x509Name, "challengePassword", challengePassword);
addEntry(x509Name, "L", city);
addEntry(x509Name, "CN", commonName);
addEntry(x509Name, "name", contactPerson);
addEntry(x509Name, "C", country);
addEntry(x509Name, "emailAddress", email);
addEntry(x509Name, "GN", givenName);
addEntry(x509Name, "initials", initials);
addEntry(x509Name, "algorithm", keyPairAlgorithm);
if (!keyUsage.empty())
{
for (auto& usage : keyUsage)
{
if (isExtendedKeyUsage(usage))
{
addEntry(x509Name, "extendedKeyUsage", usage);
}
else
{
addEntry(x509Name, "keyUsage", usage);
}
}
}
addEntry(x509Name, "O", organization);
addEntry(x509Name, "OU", organizationalUnit);
addEntry(x509Name, "ST", state);
addEntry(x509Name, "SN", surname);
addEntry(x509Name, "unstructuredName", unstructuredName);
EVPPkeyPtr pKey(nullptr, ::EVP_PKEY_free);
lg2::info("Given Key pair algorithm, KEYPAIRALGORITHM:{KEYPAIRALGORITHM}",
"KEYPAIRALGORITHM", keyPairAlgorithm);
// Used EC algorithm as default if user did not give algorithm type.
if (keyPairAlgorithm == "RSA")
pKey = getRSAKeyPair(keyBitLength);
else if ((keyPairAlgorithm == "EC") || (keyPairAlgorithm.empty()))
pKey = generateECKeyPair(keyCurveId);
else
{
lg2::error("Given Key pair algorithm is not supported. Supporting "
"RSA and EC only");
elog<InvalidArgument>(
Argument::ARGUMENT_NAME("KEYPAIRALGORITHM"),
Argument::ARGUMENT_VALUE(keyPairAlgorithm.c_str()));
}
ret = X509_REQ_set_pubkey(x509Req.get(), pKey.get());
if (ret == 0)
{
lg2::error("Error occurred while setting Public key");
elog<InternalFailure>();
}
// Write private key to file
writePrivateKey(pKey, defaultPrivateKeyFileName);
// set sign key of x509 req
ret = X509_REQ_sign(x509Req.get(), pKey.get(), EVP_sha256());
if (ret == 0)
{
lg2::error("Error occurred while signing key of x509");
elog<InternalFailure>();
}
lg2::info("Writing CSR to file");
fs::path csrFilePath = certParentInstallPath / defaultCSRFileName;
writeCSR(csrFilePath.string(), x509Req);
}
bool Manager::isExtendedKeyUsage(const std::string& usage)
{
const static std::array<const char*, 6> usageList = {
"ServerAuthentication", "ClientAuthentication", "OCSPSigning",
"Timestamping", "CodeSigning", "EmailProtection"};
auto it = std::find_if(
usageList.begin(), usageList.end(),
[&usage](const char* s) { return (strcmp(s, usage.c_str()) == 0); });
return it != usageList.end();
}
EVPPkeyPtr Manager::generateRSAKeyPair(const int64_t keyBitLength)
{
int64_t keyBitLen = keyBitLength;
// set keybit length to default value if not set
if (keyBitLen <= 0)
{
lg2::info("KeyBitLength is not given.Hence, using default KeyBitLength:"
"{DEFAULTKEYBITLENGTH}",
"DEFAULTKEYBITLENGTH", defaultKeyBitLength);
keyBitLen = defaultKeyBitLength;
}
#if (OPENSSL_VERSION_NUMBER < 0x30000000L)
// generate rsa key
BignumPtr bne(BN_new(), ::BN_free);
auto ret = BN_set_word(bne.get(), RSA_F4);
if (ret == 0)
{
lg2::error("Error occurred during BN_set_word call");
elog<InternalFailure>();
}
using RSAPtr = std::unique_ptr<RSA, decltype(&::RSA_free)>;
RSAPtr rsa(RSA_new(), ::RSA_free);
ret = RSA_generate_key_ex(rsa.get(), keyBitLen, bne.get(), nullptr);
if (ret != 1)
{
lg2::error(
"Error occurred during RSA_generate_key_ex call: {KEYBITLENGTH}",
"KEYBITLENGTH", keyBitLen);
elog<InternalFailure>();
}
// set public key of x509 req
EVPPkeyPtr pKey(EVP_PKEY_new(), ::EVP_PKEY_free);
ret = EVP_PKEY_assign_RSA(pKey.get(), rsa.get());
if (ret == 0)
{
lg2::error("Error occurred during assign rsa key into EVP");
elog<InternalFailure>();
}
// Now |rsa| is managed by |pKey|
rsa.release();
return pKey;
#else
auto ctx = std::unique_ptr<EVP_PKEY_CTX, decltype(&::EVP_PKEY_CTX_free)>(
EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr), &::EVP_PKEY_CTX_free);
if (!ctx)
{
lg2::error("Error occurred creating EVP_PKEY_CTX from algorithm");
elog<InternalFailure>();
}
if ((EVP_PKEY_keygen_init(ctx.get()) <= 0) ||
(EVP_PKEY_CTX_set_rsa_keygen_bits(ctx.get(), keyBitLen) <= 0))
{
lg2::error("Error occurred initializing keygen context");
elog<InternalFailure>();
}
EVP_PKEY* pKey = nullptr;
if (EVP_PKEY_keygen(ctx.get(), &pKey) <= 0)
{
lg2::error("Error occurred during generate EC key");
elog<InternalFailure>();
}
return {pKey, &::EVP_PKEY_free};
#endif
}
EVPPkeyPtr Manager::generateECKeyPair(const std::string& curveId)
{
std::string curId(curveId);
if (curId.empty())
{
lg2::info("KeyCurveId is not given. Hence using default curve id,"
"DEFAULTKEYCURVEID:{DEFAULTKEYCURVEID}",
"DEFAULTKEYCURVEID", defaultKeyCurveID);
curId = defaultKeyCurveID;
}
int ecGrp = OBJ_txt2nid(curId.c_str());
if (ecGrp == NID_undef)
{
lg2::error(
"Error occurred during convert the curve id string format into NID,"
"KEYCURVEID:{KEYCURVEID}",
"KEYCURVEID", curId);
elog<InternalFailure>();
}
#if (OPENSSL_VERSION_NUMBER < 0x30000000L)
EC_KEY* ecKey = EC_KEY_new_by_curve_name(ecGrp);
if (ecKey == nullptr)
{
lg2::error(
"Error occurred during create the EC_Key object from NID, ECGROUP:{ECGROUP}",
"ECGROUP", ecGrp);
elog<InternalFailure>();
}
// If you want to save a key and later load it with
// SSL_CTX_use_PrivateKey_file, then you must set the OPENSSL_EC_NAMED_CURVE
// flag on the key.
EC_KEY_set_asn1_flag(ecKey, OPENSSL_EC_NAMED_CURVE);
int ret = EC_KEY_generate_key(ecKey);
if (ret == 0)
{
EC_KEY_free(ecKey);
lg2::error("Error occurred during generate EC key");
elog<InternalFailure>();
}
EVPPkeyPtr pKey(EVP_PKEY_new(), ::EVP_PKEY_free);
ret = EVP_PKEY_assign_EC_KEY(pKey.get(), ecKey);
if (ret == 0)
{
EC_KEY_free(ecKey);
lg2::error("Error occurred during assign EC Key into EVP");
elog<InternalFailure>();
}
return pKey;
#else
auto holderOfKey = [](EVP_PKEY* key) {
return std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>{
key, &::EVP_PKEY_free};
};
// Create context to set up curve parameters.
auto ctx = std::unique_ptr<EVP_PKEY_CTX, decltype(&::EVP_PKEY_CTX_free)>(
EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr), &::EVP_PKEY_CTX_free);
if (!ctx)
{
lg2::error("Error occurred creating EVP_PKEY_CTX for params");
elog<InternalFailure>();
}
// Set up curve parameters.
EVP_PKEY* params = nullptr;
if ((EVP_PKEY_paramgen_init(ctx.get()) <= 0) ||
(EVP_PKEY_CTX_set_ec_param_enc(ctx.get(), OPENSSL_EC_NAMED_CURVE) <=
0) ||
(EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx.get(), ecGrp) <= 0) ||
(EVP_PKEY_paramgen(ctx.get(), &params) <= 0))
{
lg2::error("Error occurred setting curve parameters");
elog<InternalFailure>();
}
// Move parameters to RAII holder.
auto pparms = holderOfKey(params);
// Create new context for key.
ctx.reset(EVP_PKEY_CTX_new_from_pkey(nullptr, params, nullptr));
if (!ctx || (EVP_PKEY_keygen_init(ctx.get()) <= 0))
{
lg2::error("Error occurred initializing keygen context");
elog<InternalFailure>();
}
EVP_PKEY* pKey = nullptr;
if (EVP_PKEY_keygen(ctx.get(), &pKey) <= 0)
{
lg2::error("Error occurred during generate EC key");
elog<InternalFailure>();
}
return holderOfKey(pKey);
#endif
}
void Manager::writePrivateKey(const EVPPkeyPtr& pKey,
const std::string& privKeyFileName)
{
lg2::info("Writing private key to file");
// write private key to file
fs::path privKeyPath = certParentInstallPath / privKeyFileName;
FILE* fp = std::fopen(privKeyPath.c_str(), "w");
if (fp == nullptr)
{
lg2::error("Error occurred creating private key file");
elog<InternalFailure>();
}
int ret = PEM_write_PrivateKey(fp, pKey.get(), nullptr, nullptr, 0, 0,
nullptr);
std::fclose(fp);
if (ret == 0)
{
lg2::error("Error occurred while writing private key to file");
elog<InternalFailure>();
}
}
void Manager::addEntry(X509_NAME* x509Name, const char* field,
const std::string& bytes)
{
if (bytes.empty())
{
return;
}
int ret = X509_NAME_add_entry_by_txt(
x509Name, field, MBSTRING_ASC,
reinterpret_cast<const unsigned char*>(bytes.c_str()), -1, -1, 0);
if (ret != 1)
{
lg2::error("Unable to set entry, FIELD:{FIELD}, VALUE:{VALUE}", "FIELD",
field, "VALUE", bytes);
elog<InternalFailure>();
}
}
void Manager::createCSRObject(const Status& status)
{
if (csrPtr)
{
csrPtr.reset(nullptr);
}
auto csrObjectPath = objectPath + '/' + "csr";
csrPtr = std::make_unique<CSR>(bus, csrObjectPath.c_str(),
certInstallPath.c_str(), status);
}
void Manager::writeCSR(const std::string& filePath, const X509ReqPtr& x509Req)
{
if (fs::exists(filePath))
{
lg2::info("Removing the existing file, FILENAME:{FILENAME}", "FILENAME",
filePath);
if (!fs::remove(filePath.c_str()))
{
lg2::error("Unable to remove the file, FILENAME:{FILENAME}",
"FILENAME", filePath);
elog<InternalFailure>();
}
}
FILE* fp = nullptr;
if ((fp = std::fopen(filePath.c_str(), "w")) == nullptr)
{
lg2::error(
"Error opening the file to write the CSR, FILENAME:{FILENAME}",
"FILENAME", filePath);
elog<InternalFailure>();
}
int rc = PEM_write_X509_REQ(fp, x509Req.get());
if (!rc)
{
lg2::error("PEM write routine failed, FILENAME:{FILENAME}", "FILENAME",
filePath);
std::fclose(fp);
elog<InternalFailure>();
}
std::fclose(fp);
}
void Manager::createCertificates()
{
auto certObjectPath = objectPath + '/';
if (certType == CertificateType::authority)
{
// Check whether install path is a directory.
if (!fs::is_directory(certInstallPath))
{
lg2::error("Certificate installation path exists and it is "
"not a directory");
elog<InternalFailure>();
}
// If the authorities list exists, recover from it and return
if (fs::path authoritiesListFilePath = fs::path(certInstallPath) /
defaultAuthoritiesListFileName;
fs::exists(authoritiesListFilePath))
{
// remove all other files and directories
for (auto& path : fs::directory_iterator(certInstallPath))
{
if (path.path() != authoritiesListFilePath)
{
fs::remove_all(path);
}
}
installAll(authoritiesListFilePath);
return;
}
for (auto& path : fs::directory_iterator(certInstallPath))
{
try
{
// Assume here any regular file located in certificate directory
// contains certificates body. Do not want to use soft links
// would add value.
if (fs::is_regular_file(path))
{
installedCerts.emplace_back(std::make_unique<Certificate>(
bus, certObjectPath + std::to_string(certIdCounter++),
certType, certInstallPath, path.path(),
certWatchPtr.get(), *this, /*restore=*/true));
}
}
catch (const InternalFailure& e)
{
report<InternalFailure>();
}
catch (const InvalidCertificate& e)
{
report<InvalidCertificate>(InvalidCertificateReason(
"Existing certificate file is corrupted"));
}
}
}
else if (fs::exists(certInstallPath))
{
try
{
installedCerts.emplace_back(std::make_unique<Certificate>(
bus, certObjectPath + '1', certType, certInstallPath,
certInstallPath, certWatchPtr.get(), *this, /*restore=*/false));
}
catch (const InternalFailure& e)
{
report<InternalFailure>();
}
catch (const InvalidCertificate& e)
{
report<InvalidCertificate>(InvalidCertificateReason(
"Existing certificate file is corrupted"));
}
}
}
void Manager::createRSAPrivateKeyFile()
{
fs::path rsaPrivateKeyFileName = certParentInstallPath /
defaultRSAPrivateKeyFileName;
try
{
if (!fs::exists(rsaPrivateKeyFileName))
{
writePrivateKey(generateRSAKeyPair(supportedKeyBitLength),
defaultRSAPrivateKeyFileName);
}
}
catch (const InternalFailure& e)
{
report<InternalFailure>();
}
}
EVPPkeyPtr Manager::getRSAKeyPair(const int64_t keyBitLength)
{
if (keyBitLength != supportedKeyBitLength)
{
lg2::error(
"Given Key bit length is not supported, GIVENKEYBITLENGTH:"
"{GIVENKEYBITLENGTH}, SUPPORTEDKEYBITLENGTH:{SUPPORTEDKEYBITLENGTH}",
"GIVENKEYBITLENGTH", keyBitLength, "SUPPORTEDKEYBITLENGTH",
supportedKeyBitLength);
elog<InvalidArgument>(
Argument::ARGUMENT_NAME("KEYBITLENGTH"),
Argument::ARGUMENT_VALUE(std::to_string(keyBitLength).c_str()));
}
fs::path rsaPrivateKeyFileName = certParentInstallPath /
defaultRSAPrivateKeyFileName;
FILE* privateKeyFile = std::fopen(rsaPrivateKeyFileName.c_str(), "r");
if (!privateKeyFile)
{
lg2::error(
"Unable to open RSA private key file to read, RSAKEYFILE:{RSAKEYFILE},"
"ERRORREASON:{ERRORREASON}",
"RSAKEYFILE", rsaPrivateKeyFileName, "ERRORREASON",
strerror(errno));
elog<InternalFailure>();
}
EVPPkeyPtr privateKey(
PEM_read_PrivateKey(privateKeyFile, nullptr, nullptr, nullptr),
::EVP_PKEY_free);
std::fclose(privateKeyFile);
if (!privateKey)
{
lg2::error("Error occurred during PEM_read_PrivateKey call");
elog<InternalFailure>();
}
return privateKey;
}
void Manager::storageUpdate()
{
if (certType == CertificateType::authority)
{
// Remove symbolic links in the certificate directory
for (auto& certPath : fs::directory_iterator(certInstallPath))
{
try
{
if (fs::is_symlink(certPath))
{
fs::remove(certPath);
}
}
catch (const std::exception& e)
{
lg2::error(
"Failed to remove symlink for certificate, ERR:{ERR} SYMLINK:{SYMLINK}",
"ERR", e, "SYMLINK", certPath.path().string());
elog<InternalFailure>();
}
}
}
for (const auto& cert : installedCerts)
{
cert->storageUpdate();
}
}
void Manager::reloadOrReset(const std::string& unit)
{
if (!unit.empty())
{
try
{
constexpr auto defaultSystemdService = "org.freedesktop.systemd1";
constexpr auto defaultSystemdObjectPath =
"/org/freedesktop/systemd1";
constexpr auto defaultSystemdInterface =
"org.freedesktop.systemd1.Manager";
auto method = bus.new_method_call(
defaultSystemdService, defaultSystemdObjectPath,
defaultSystemdInterface, "ReloadOrRestartUnit");
method.append(unit, "replace");
bus.call_noreply(method);
}
catch (const sdbusplus::exception_t& e)
{
lg2::error(
"Failed to reload or restart service, ERR:{ERR}, UNIT:{UNIT}",
"ERR", e, "UNIT", unit);
elog<InternalFailure>();
}
}
}
bool Manager::isCertificateUnique(const std::string& filePath,
const Certificate* const certToDrop)
{
if (std::any_of(
installedCerts.begin(), installedCerts.end(),
[&filePath, certToDrop](const std::unique_ptr<Certificate>& cert) {
return cert.get() != certToDrop && cert->isSame(filePath);
}))
{
return false;
}
else
{
return true;
}
}
} // namespace phosphor::certs