#include "certs_manager.hpp" #include #include #include #include #include #include namespace phosphor { namespace certs { using InternalFailure = sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; using InvalidCertificate = sdbusplus::xyz::openbmc_project::Certs::Error::InvalidCertificate; using Reason = xyz::openbmc_project::Certs::InvalidCertificate::REASON; using X509_REQ_Ptr = std::unique_ptr; using BIGNUM_Ptr = std::unique_ptr; using InvalidArgument = sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument; using Argument = xyz::openbmc_project::Common::InvalidArgument; constexpr auto SUPPORTED_KEYBITLENGTH = 2048; Manager::Manager(sdbusplus::bus::bus& bus, sdeventplus::Event& event, const char* path, const CertificateType& type, UnitsToRestart&& unit, CertInstallPath&& installPath) : Ifaces(bus, path), bus(bus), event(event), objectPath(path), certType(type), unitToRestart(std::move(unit)), certInstallPath(std::move(installPath)), certParentInstallPath(fs::path(certInstallPath).parent_path()) { // Create certificate directory if not existing. // Set correct certificate directory permitions. fs::path certDirectory; try { if (certType == 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 (fs::filesystem_error& e) { log("Failed to create directory", entry("ERR=%s", e.what()), entry("DIRECTORY=%s", certParentInstallPath.c_str())); report(); } // Generating RSA private key file if certificate type is server/client if (certType != AUTHORITY) { createRSAPrivateKeyFile(); } // restore any existing certificates createCertificates(); // watch is not required for authority certificates if (certType != 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()) { log( "Inotify callback to update certificate properties"); installedCerts[0]->populateProperties(); } else { log( "Inotify callback to create certificate object"); createCertificates(); } } catch (const InternalFailure& e) { commit(); } catch (const InvalidCertificate& e) { commit(); } }); } else { const std::string signleCertPath = "/etc/ssl/certs/Root-CA.pem"; if (fs::exists(signleCertPath) && !fs::is_empty(signleCertPath)) { log( "Legacy certificate detected, will be installed from: ", entry("SINGLE_CERTPATH=%s", signleCertPath.c_str())); install(signleCertPath); if (!fs::remove(signleCertPath)) { log( "Unable to remove old certificate from: ", entry("SINGLE_CERTPATH=%s", signleCertPath.c_str())); elog(); } } } } std::string Manager::install(const std::string filePath) { using NotAllowed = sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed; using Reason = xyz::openbmc_project::Common::NotAllowed::REASON; if (certType != phosphor::certs::AUTHORITY && !installedCerts.empty()) { elog(Reason("Certificate already exist")); } else if (certType == phosphor::certs::AUTHORITY && installedCerts.size() >= AUTHORITY_CERTIFICATES_LIMIT) { elog(Reason("Certificates limit reached")); } std::string certObjectPath; if (isCertificateUnique(filePath)) { certObjectPath = objectPath + '/' + std::to_string(certIdCounter); installedCerts.emplace_back(std::make_unique( bus, certObjectPath, certType, certInstallPath, filePath, certWatchPtr, *this)); reloadOrReset(unitToRestart); certIdCounter++; } else { elog(Reason("Certificate already exist")); } return certObjectPath; } 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(); storageUpdate(); reloadOrReset(unitToRestart); } void Manager::deleteCertificate(const Certificate* const certificate) { std::vector>::iterator const& certIt = std::find_if(installedCerts.begin(), installedCerts.end(), [certificate](std::unique_ptr const& cert) { return (cert.get() == certificate); }); if (certIt != installedCerts.end()) { installedCerts.erase(certIt); storageUpdate(); reloadOrReset(unitToRestart); } else { log("Certificate does not exist", entry("ID=%s", certificate->getCertId().c_str())); elog(); } } void Manager::replaceCertificate(Certificate* const certificate, const std::string& filePath) { if (isCertificateUnique(filePath, certificate)) { certificate->install(filePath); storageUpdate(); reloadOrReset(unitToRestart); } else { log("Certificate already exist"); elog(); } } std::string Manager::generateCSR( std::vector 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 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) { log("Error occurred during forking process"); report(); } 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(); } } 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) { log("Unable to initialize signal set"); elog(); } if (sigaddset(&ss, SIGCHLD) < 0) { log("Unable to add signal to signal set"); elog(); } // Block SIGCHLD first, so that the event loop can handle it if (sigprocmask(SIG_BLOCK, &ss, NULL) < 0) { log("Unable to block signal"); elog(); } if (childPtr) { childPtr.reset(); } childPtr = std::make_unique(event, pid, WEXITED | WSTOPPED, std::move(callback)); } catch (const InternalFailure& e) { commit(); } } auto csrObjectPath = objectPath + '/' + "csr"; return csrObjectPath; } std::vector>& Manager::getCertificates() { return installedCerts; } void Manager::generateCSRHelper( std::vector 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 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; // TODO: Issue#6 need to make version number configurable X509_REQ_Ptr x509Req(X509_REQ_new(), ::X509_REQ_free); ret = X509_REQ_set_version(x509Req.get(), nVersion); if (ret == 0) { log("Error occured during X509_REQ_set_version call"); elog(); } // 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, "ST", state); addEntry(x509Name, "SN", surname); addEntry(x509Name, "unstructuredName", unstructuredName); EVP_PKEY_Ptr pKey(nullptr, ::EVP_PKEY_free); log("Given Key pair algorithm", entry("KEYPAIRALGORITHM=%s", keyPairAlgorithm.c_str())); // 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 { log("Given Key pair algorithm is not supported. Supporting " "RSA and EC only"); elog( Argument::ARGUMENT_NAME("KEYPAIRALGORITHM"), Argument::ARGUMENT_VALUE(keyPairAlgorithm.c_str())); } ret = X509_REQ_set_pubkey(x509Req.get(), pKey.get()); if (ret == 0) { log("Error occured while setting Public key"); elog(); } // Write private key to file writePrivateKey(pKey, PRIV_KEY_FILE_NAME); // set sign key of x509 req ret = X509_REQ_sign(x509Req.get(), pKey.get(), EVP_sha256()); if (ret == 0) { log("Error occured while signing key of x509"); elog(); } log("Writing CSR to file"); fs::path csrFilePath = certParentInstallPath / CSR_FILE_NAME; writeCSR(csrFilePath.string(), x509Req); } bool Manager::isExtendedKeyUsage(const std::string& usage) { const static std::array 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(); } EVP_PKEY_Ptr Manager::generateRSAKeyPair(const int64_t keyBitLength) { int ret = 0; // generate rsa key BIGNUM_Ptr bne(BN_new(), ::BN_free); ret = BN_set_word(bne.get(), RSA_F4); if (ret == 0) { log("Error occured during BN_set_word call"); elog(); } int64_t keyBitLen = keyBitLength; // set keybit length to default value if not set if (keyBitLen <= 0) { constexpr auto DEFAULT_KEYBITLENGTH = 2048; log( "KeyBitLength is not given.Hence, using default KeyBitLength", entry("DEFAULTKEYBITLENGTH=%d", DEFAULT_KEYBITLENGTH)); keyBitLen = DEFAULT_KEYBITLENGTH; } RSA* rsa = RSA_new(); ret = RSA_generate_key_ex(rsa, keyBitLen, bne.get(), NULL); if (ret != 1) { free(rsa); log("Error occured during RSA_generate_key_ex call", entry("KEYBITLENGTH=%PRIu64", keyBitLen)); elog(); } // set public key of x509 req EVP_PKEY_Ptr pKey(EVP_PKEY_new(), ::EVP_PKEY_free); ret = EVP_PKEY_assign_RSA(pKey.get(), rsa); if (ret == 0) { free(rsa); log("Error occured during assign rsa key into EVP"); elog(); } return pKey; } EVP_PKEY_Ptr Manager::generateECKeyPair(const std::string& curveId) { std::string curId(curveId); if (curId.empty()) { // secp224r1 is equal to RSA 2048 KeyBitLength. Refer RFC 5349 constexpr auto DEFAULT_KEYCURVEID = "secp224r1"; log( "KeyCurveId is not given. Hence using default curve id", entry("DEFAULTKEYCURVEID=%s", DEFAULT_KEYCURVEID)); curId = DEFAULT_KEYCURVEID; } int ecGrp = OBJ_txt2nid(curId.c_str()); if (ecGrp == NID_undef) { log( "Error occured during convert the curve id string format into NID", entry("KEYCURVEID=%s", curId.c_str())); elog(); } EC_KEY* ecKey = EC_KEY_new_by_curve_name(ecGrp); if (ecKey == NULL) { log( "Error occured during create the EC_Key object from NID", entry("ECGROUP=%d", ecGrp)); elog(); } // 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); log("Error occured during generate EC key"); elog(); } EVP_PKEY_Ptr pKey(EVP_PKEY_new(), ::EVP_PKEY_free); ret = EVP_PKEY_assign_EC_KEY(pKey.get(), ecKey); if (ret == 0) { EC_KEY_free(ecKey); log("Error occured during assign EC Key into EVP"); elog(); } return pKey; } void Manager::writePrivateKey(const EVP_PKEY_Ptr& pKey, const std::string& privKeyFileName) { log("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 == NULL) { log("Error occured creating private key file"); elog(); } int ret = PEM_write_PrivateKey(fp, pKey.get(), NULL, NULL, 0, 0, NULL); std::fclose(fp); if (ret == 0) { log("Error occured while writing private key to file"); elog(); } } 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(bytes.c_str()), -1, -1, 0); if (ret != 1) { log("Unable to set entry", entry("FIELD=%s", field), entry("VALUE=%s", bytes.c_str())); elog(); } } void Manager::createCSRObject(const Status& status) { if (csrPtr) { csrPtr.reset(nullptr); } auto csrObjectPath = objectPath + '/' + "csr"; csrPtr = std::make_unique(bus, csrObjectPath.c_str(), certInstallPath.c_str(), status); } void Manager::writeCSR(const std::string& filePath, const X509_REQ_Ptr& x509Req) { if (fs::exists(filePath)) { log("Removing the existing file", entry("FILENAME=%s", filePath.c_str())); if (!fs::remove(filePath.c_str())) { log("Unable to remove the file", entry("FILENAME=%s", filePath.c_str())); elog(); } } FILE* fp = NULL; if ((fp = std::fopen(filePath.c_str(), "w")) == NULL) { log("Error opening the file to write the CSR", entry("FILENAME=%s", filePath.c_str())); elog(); } int rc = PEM_write_X509_REQ(fp, x509Req.get()); if (!rc) { log("PEM write routine failed", entry("FILENAME=%s", filePath.c_str())); std::fclose(fp); elog(); } std::fclose(fp); } void Manager::createCertificates() { auto certObjectPath = objectPath + '/'; if (certType == phosphor::certs::AUTHORITY) { // Check whether install path is a directory. if (!fs::is_directory(certInstallPath)) { log("Certificate installation path exists and it is " "not a directory"); elog(); 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( bus, certObjectPath + std::to_string(certIdCounter++), certType, certInstallPath, path.path(), certWatchPtr, *this)); } } catch (const InternalFailure& e) { report(); } catch (const InvalidCertificate& e) { report( Reason("Existing certificate file is corrupted")); } } } else if (fs::exists(certInstallPath)) { try { installedCerts.emplace_back(std::make_unique( bus, certObjectPath + '1', certType, certInstallPath, certInstallPath, certWatchPtr, *this)); } catch (const InternalFailure& e) { report(); } catch (const InvalidCertificate& e) { report( Reason("Existing certificate file is corrupted")); } } } void Manager::createRSAPrivateKeyFile() { fs::path rsaPrivateKeyFileName = certParentInstallPath / RSA_PRIV_KEY_FILE_NAME; try { if (!fs::exists(rsaPrivateKeyFileName)) { writePrivateKey(generateRSAKeyPair(SUPPORTED_KEYBITLENGTH), RSA_PRIV_KEY_FILE_NAME); } } catch (const InternalFailure& e) { report(); } } EVP_PKEY_Ptr Manager::getRSAKeyPair(const int64_t keyBitLength) { if (keyBitLength != SUPPORTED_KEYBITLENGTH) { log( "Given Key bit length is not supported", entry("GIVENKEYBITLENGTH=%d", keyBitLength), entry("SUPPORTEDKEYBITLENGTH=%d", SUPPORTED_KEYBITLENGTH)); elog( Argument::ARGUMENT_NAME("KEYBITLENGTH"), Argument::ARGUMENT_VALUE(std::to_string(keyBitLength).c_str())); } fs::path rsaPrivateKeyFileName = certParentInstallPath / RSA_PRIV_KEY_FILE_NAME; FILE* privateKeyFile = std::fopen(rsaPrivateKeyFileName.c_str(), "r"); if (!privateKeyFile) { log("Unable to open RSA private key file to read", entry("RSAKEYFILE=%s", rsaPrivateKeyFileName.c_str()), entry("ERRORREASON=%s", strerror(errno))); elog(); } EVP_PKEY_Ptr privateKey( PEM_read_PrivateKey(privateKeyFile, nullptr, nullptr, nullptr), ::EVP_PKEY_free); std::fclose(privateKeyFile); if (!privateKey) { log("Error occured during PEM_read_PrivateKey call"); elog(); } return privateKey; } void Manager::storageUpdate() { if (certType == phosphor::certs::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) { log( "Failed to remove symlink for certificate", entry("ERR=%s", e.what()), entry("SYMLINK=%s", certPath.path().string().c_str())); elog(); } } } for (const auto& cert : installedCerts) { cert->storageUpdate(); } } void Manager::reloadOrReset(const UnitsToRestart& unit) { if (!unit.empty()) { try { constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1"; constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1"; constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "ReloadOrRestartUnit"); method.append(unit, "replace"); bus.call_noreply(method); } catch (const sdbusplus::exception::SdBusError& e) { log("Failed to reload or restart service", entry("ERR=%s", e.what()), entry("UNIT=%s", unit.c_str())); elog(); } } } bool Manager::isCertificateUnique(const std::string& filePath, const Certificate* const certToDrop) { if (std::any_of( installedCerts.begin(), installedCerts.end(), [&filePath, certToDrop](std::unique_ptr const& cert) { return cert.get() != certToDrop && cert->isSame(filePath); })) { return false; } else { return true; } } } // namespace certs } // namespace phosphor