blob: da0899695f2ac3aee585da3b575007561b047dd0 [file] [log] [blame]
#include "config.h"
#include "certificate.hpp"
#include "certs_manager.hpp"
#include "csr.hpp"
#include <openssl/bio.h>
#include <openssl/ossl_typ.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <systemd/sd-event.h>
#include <unistd.h>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <iterator>
#include <memory>
#include <new>
#include <sdbusplus/bus.hpp>
#include <sdeventplus/event.hpp>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
#include <xyz/openbmc_project/Certs/error.hpp>
#include <xyz/openbmc_project/Common/error.hpp>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
namespace phosphor::certs
{
namespace
{
namespace fs = std::filesystem;
using ::sdbusplus::xyz::openbmc_project::Certs::Error::InvalidCertificate;
using ::sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
using ::testing::Eq;
using ::testing::Return;
// Compares two files; returns true only if the two are the same
bool compareFiles(const std::string& file1, const std::string& file2)
{
std::ifstream f1(file1, std::ifstream::binary | std::ifstream::ate);
std::ifstream f2(file2, std::ifstream::binary | std::ifstream::ate);
if (f1.fail() || f2.fail())
{
return false; // file problem
}
if (f1.tellg() != f2.tellg())
{
return false; // size mismatch
}
// seek back to beginning and use std::equal to compare contents
f1.seekg(0, std::ifstream::beg);
f2.seekg(0, std::ifstream::beg);
return std::equal(std::istreambuf_iterator<char>(f1.rdbuf()),
std::istreambuf_iterator<char>(),
std::istreambuf_iterator<char>(f2.rdbuf()));
}
/**
* Class to generate certificate file and test verification of certificate file
*/
class TestCertificates : public ::testing::Test
{
public:
TestCertificates() : bus(sdbusplus::bus::new_default())
{
}
void SetUp() override
{
char dirTemplate[] = "/tmp/FakeCerts.XXXXXX";
auto dirPtr = mkdtemp(dirTemplate);
if (dirPtr == nullptr)
{
throw std::bad_alloc();
}
certDir = std::string(dirPtr) + "/certs";
fs::create_directories(certDir);
createNewCertificate();
}
void TearDown() override
{
fs::remove_all(certDir);
fs::remove(certificateFile);
fs::remove(CSRFile);
fs::remove(privateKeyFile);
fs::remove_all("demoCA");
}
void createNewCertificate(bool setNewCertId = false)
{
certificateFile = "cert.pem";
CSRFile = "domain.csr";
privateKeyFile = "privkey.pem";
rsaPrivateKeyFilePath = certDir + "/.rsaprivkey.pem";
std::string cmd = "openssl req -x509 -sha256 -newkey rsa:2048 ";
cmd += "-keyout cert.pem -out cert.pem -days 365000 -nodes";
cmd += " -subj /O=openbmc-project.xyz/CN=localhost";
if (setNewCertId)
{
cmd += std::to_string(certId++);
}
auto val = std::system(cmd.c_str());
if (val)
{
std::cout << "COMMAND Error: " << val << std::endl;
}
}
void createNeverExpiredRootCertificate()
{
// remove the old cert
fs::remove(certificateFile);
// The following routines create a cert that has NotBefore
// set to 1970/01/01 and NotAfter set to 9999/12/31 via the
// OpenSSL CA application.
certificateFile = "cert.pem";
ASSERT_EQ(std::system("mkdir -p demoCA"), 0);
ASSERT_EQ(std::system("mkdir -p demoCA/private/"), 0);
ASSERT_EQ(std::system("mkdir -p demoCA/newcerts/"), 0);
ASSERT_EQ(std::system("touch demoCA/index.txt"), 0);
ASSERT_EQ(std::system("echo 1000 > demoCA/serial"), 0);
ASSERT_EQ(
std::system(
"openssl req -x509 -sha256 -newkey rsa:2048 -keyout "
"demoCA/private/cakey.pem -out demoCA/cacert.pem -nodes "
"-subj /O=openbmc-project.xyz/C=US/ST=CA/CN=localhost-ca"),
0);
ASSERT_EQ(std::system(
"openssl req -new -newkey rsa:2048 -nodes -keyout "
"demoCA/server.key -out demoCA/server.csr -subj "
"/O=openbmc-project.xyz/C=US/ST=CA/CN=localhost-server"),
0);
ASSERT_EQ(
std::system(
"openssl ca -batch -startdate 19700101000000Z -enddate "
"99991231235959Z -out cert.pem -infiles demoCA/server.csr"),
0);
}
bool compareFiles(const std::string& file1, const std::string& file2)
{
std::ifstream f1(file1, std::ifstream::binary | std::ifstream::ate);
std::ifstream f2(file2, std::ifstream::binary | std::ifstream::ate);
if (f1.fail() || f2.fail())
{
return false; // file problem
}
if (f1.tellg() != f2.tellg())
{
return false; // size mismatch
}
// seek back to beginning and use std::equal to compare contents
f1.seekg(0, std::ifstream::beg);
f2.seekg(0, std::ifstream::beg);
return std::equal(std::istreambuf_iterator<char>(f1.rdbuf()),
std::istreambuf_iterator<char>(),
std::istreambuf_iterator<char>(f2.rdbuf()));
}
std::string getCertSubjectNameHash(const std::string& certFilePath)
{
std::unique_ptr<X509, decltype(&::X509_free)> cert(X509_new(),
::X509_free);
if (!cert)
{
std::string();
}
std::unique_ptr<BIO, decltype(&::BIO_free)> bioCert(
BIO_new_file(certFilePath.c_str(), "rb"), ::BIO_free);
if (!bioCert)
{
std::string();
}
X509* x509 = cert.get();
if (!PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr))
{
std::string();
}
unsigned long hash = X509_subject_name_hash(cert.get());
static constexpr auto AUTH_CERT_HASH_LENGTH = 9;
char hashBuf[AUTH_CERT_HASH_LENGTH];
sprintf(hashBuf, "%08lx", hash);
return std::string(hashBuf);
}
protected:
sdbusplus::bus_t bus;
std::string certificateFile, CSRFile, privateKeyFile, rsaPrivateKeyFilePath;
std::string certDir;
uint64_t certId;
};
class MainApp
{
public:
MainApp(phosphor::certs::Manager* manager,
phosphor::certs::CSR* csr = nullptr) :
manager(manager),
csr_(csr)
{
}
void install(std::string& path)
{
manager->install(path);
}
std::string 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)
{
return (manager->generateCSR(
alternativeNames, challengePassword, city, commonName,
contactPerson, country, email, givenName, initials, keyBitLength,
keyCurveId, keyPairAlgorithm, keyUsage, organization,
organizationalUnit, state, surname, unstructuredName));
}
std::string csr()
{
return (csr_->csr());
}
phosphor::certs::Manager* manager;
phosphor::certs::CSR* csr_;
};
class ManagerInTest : public phosphor::certs::Manager
{
public:
static constexpr std::string_view unitToRestartInTest =
"xyz.openbmc_project.awesome-service";
ManagerInTest(sdbusplus::bus_t& bus, sdeventplus::Event& event,
const char* path, CertificateType type,
const std::string& unit, const std::string& installPath) :
Manager(bus, event, path, type, unit, installPath)
{
}
MOCK_METHOD(void, reloadOrReset, (const std::string&), (override));
};
/** @brief Check if server install routine is invoked for server setup
*/
TEST_F(TestCertificates, InvokeServerInstall)
{
std::string endpoint("https");
CertificateType type = CertificateType::Server;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest manager(bus, event, objPath.c_str(), type, verifyUnit,
installPath);
EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
.WillOnce(Return());
MainApp mainApp(&manager);
mainApp.install(certificateFile);
EXPECT_TRUE(fs::exists(verifyPath));
}
/** @brief Check if client install routine is invoked for client setup
*/
TEST_F(TestCertificates, InvokeClientInstall)
{
std::string endpoint("ldap");
CertificateType type = CertificateType::Server;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest manager(bus, event, objPath.c_str(), type, verifyUnit,
installPath);
EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
.WillOnce(Return());
MainApp mainApp(&manager);
mainApp.install(certificateFile);
EXPECT_TRUE(fs::exists(verifyPath));
}
/** @brief Check if storage install routine is invoked for storage setup
*/
TEST_F(TestCertificates, InvokeAuthorityInstall)
{
std::string endpoint("ldap");
CertificateType type = CertificateType::Authority;
std::string verifyDir(certDir);
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest manager(bus, event, objPath.c_str(), type, verifyUnit,
verifyDir);
EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
.WillOnce(Return());
MainApp mainApp(&manager);
// install the default certificate that's valid from today to 100 years
// later
mainApp.install(certificateFile);
std::vector<std::unique_ptr<Certificate>>& certs =
manager.getCertificates();
ASSERT_EQ(certs.size(), 1);
// check some attributes as well
EXPECT_EQ(certs.front()->validNotAfter() - certs.front()->validNotBefore(),
365000ULL * 24 * 3600);
EXPECT_EQ(certs.front()->subject(), "O=openbmc-project.xyz,CN=localhost");
EXPECT_EQ(certs.front()->issuer(), "O=openbmc-project.xyz,CN=localhost");
std::string verifyPath =
verifyDir + "/" + getCertSubjectNameHash(certificateFile) + ".0";
// Check that certificate has been created at installation directory
EXPECT_FALSE(fs::is_empty(verifyDir));
EXPECT_TRUE(fs::exists(verifyPath));
// Check that installed cert is identical to input one
EXPECT_TRUE(compareFiles(certificateFile, verifyPath));
}
/** @brief Check if storage install routine is invoked for storage setup
*/
TEST_F(TestCertificates, InvokeAuthorityInstallNeverExpiredRootCert)
{
std::string endpoint("ldap");
CertificateType type = CertificateType::Authority;
std::string verifyDir(certDir);
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest manager(bus, event, objPath.c_str(), type, verifyUnit,
certDir);
EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
.WillOnce(Return());
MainApp mainApp(&manager);
// install the certificate that's valid from the Unix Epoch to Dec 31, 9999
createNeverExpiredRootCertificate();
mainApp.install(certificateFile);
std::vector<std::unique_ptr<Certificate>>& certs =
manager.getCertificates();
EXPECT_EQ(certs.front()->validNotBefore(), 0);
EXPECT_EQ(certs.front()->validNotAfter(), 253402300799ULL);
std::string verifyPath =
verifyDir + "/" + getCertSubjectNameHash(certificateFile) + ".0";
// Check that certificate has been created at installation directory
EXPECT_FALSE(fs::is_empty(verifyDir));
EXPECT_TRUE(fs::exists(verifyPath));
// Check that installed cert is identical to input one
EXPECT_TRUE(compareFiles(certificateFile, verifyPath));
}
/** @brief Check if in authority mode user can't install the same
* certificate twice.
*/
TEST_F(TestCertificates, InvokeInstallSameCertTwice)
{
std::string endpoint("ldap");
CertificateType type = CertificateType::Authority;
std::string verifyDir(certDir);
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest manager(bus, event, objPath.c_str(), type, verifyUnit,
std::move(certDir));
EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
.WillOnce(Return());
MainApp mainApp(&manager);
mainApp.install(certificateFile);
std::vector<std::unique_ptr<Certificate>>& certs =
manager.getCertificates();
EXPECT_FALSE(certs.empty());
// Check that certificate has been created at installation directory
std::string verifyPath =
verifyDir + "/" + getCertSubjectNameHash(certificateFile) + ".0";
EXPECT_FALSE(fs::is_empty(verifyDir));
EXPECT_TRUE(fs::exists(verifyPath));
// Check that installed cert is identical to input one
EXPECT_TRUE(compareFiles(certificateFile, verifyPath));
using NotAllowed =
sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed;
EXPECT_THROW(
{
try
{
// Try to install the same certificate second time
mainApp.install(certificateFile);
}
catch (const NotAllowed& e)
{
throw;
}
},
NotAllowed);
// Check that the original certificate has been not removed
EXPECT_FALSE(fs::is_empty(verifyDir));
EXPECT_TRUE(fs::exists(verifyPath));
}
/** @brief Check if in authority mode user can install a certificate with
* certain subject hash twice.
*/
TEST_F(TestCertificates, InvokeInstallSameSubjectTwice)
{
std::string endpoint("ldap");
CertificateType type = CertificateType::Authority;
std::string verifyDir(certDir);
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest manager(bus, event, objPath.c_str(), type, verifyUnit,
certDir);
EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
.WillOnce(Return())
.WillOnce(Return());
MainApp mainApp(&manager);
mainApp.install(certificateFile);
std::vector<std::unique_ptr<Certificate>>& certs =
manager.getCertificates();
EXPECT_FALSE(certs.empty());
// Check that certificate has been created at installation directory
std::string verifyPath0 =
verifyDir + "/" + getCertSubjectNameHash(certificateFile) + ".0";
EXPECT_FALSE(fs::is_empty(verifyDir));
EXPECT_TRUE(fs::exists(verifyPath0));
// Check that installed cert is identical to input one
EXPECT_TRUE(compareFiles(certificateFile, verifyPath0));
// Prepare second certificate with the same subject
createNewCertificate();
// Install second certificate
mainApp.install(certificateFile);
// Expect there are exactly two certificates in the collection
EXPECT_EQ(certs.size(), 2);
// Check that certificate has been created at installation directory
std::string verifyPath1 =
verifyDir + "/" + getCertSubjectNameHash(certificateFile) + ".1";
EXPECT_TRUE(fs::exists(verifyPath1));
// Check that installed cert is identical to input one
EXPECT_TRUE(compareFiles(certificateFile, verifyPath1));
// Check that the original/first certificate has been not removed
EXPECT_FALSE(fs::is_empty(verifyDir));
EXPECT_TRUE(fs::exists(verifyPath0));
}
/** @brief Check if in authority mode user can't install more than
* maxNumAuthorityCertificates certificates.
*/
TEST_F(TestCertificates, InvokeInstallAuthCertLimit)
{
std::string endpoint("ldap");
CertificateType type = CertificateType::Authority;
std::string verifyDir(certDir);
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest manager(bus, event, objPath.c_str(), type, verifyUnit,
certDir);
EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
.WillRepeatedly(Return());
MainApp mainApp(&manager);
std::vector<std::unique_ptr<Certificate>>& certs =
manager.getCertificates();
std::vector<std::string> verifyPaths;
// Prepare maximum number of ceritificates
for (std::size_t i = 0; i < maxNumAuthorityCertificates; ++i)
{
// Prepare new certificatate
createNewCertificate(true);
// Install ceritificate
mainApp.install(certificateFile);
// Check number of certificates in the collection
EXPECT_EQ(certs.size(), i + 1);
// Check that certificate has been created at installation directory
std::string verifyPath =
verifyDir + "/" + getCertSubjectNameHash(certificateFile) + ".0";
EXPECT_FALSE(fs::is_empty(verifyDir));
EXPECT_TRUE(fs::exists(verifyPath));
// Check that installed cert is identical to input one
EXPECT_TRUE(compareFiles(certificateFile, verifyPath));
// Save current certificate file for later check
verifyPaths.push_back(verifyPath);
}
// Prepare new certificatate
createNewCertificate(true);
using NotAllowed =
sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed;
EXPECT_THROW(
{
try
{
// Try to install one more certificate
mainApp.install(certificateFile);
}
catch (const NotAllowed& e)
{
throw;
}
},
NotAllowed);
// Check that the original certificate has been not removed
EXPECT_FALSE(fs::is_empty(verifyDir));
for (size_t i = 0; i < maxNumAuthorityCertificates; ++i)
{
EXPECT_TRUE(fs::exists(verifyPaths[i]));
}
}
/** @brief Compare the installed certificate with the copied certificate
*/
TEST_F(TestCertificates, CompareInstalledCertificate)
{
std::string endpoint("ldap");
CertificateType type = CertificateType::Client;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest manager(bus, event, objPath.c_str(), type, verifyUnit,
installPath);
EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
.WillOnce(Return());
MainApp mainApp(&manager);
mainApp.install(certificateFile);
EXPECT_TRUE(fs::exists(verifyPath));
EXPECT_TRUE(compareFiles(verifyPath, certificateFile));
}
/** @brief Check if install fails if certificate file is not found
*/
TEST_F(TestCertificates, TestNoCertificateFile)
{
std::string endpoint("ldap");
CertificateType type = CertificateType::Client;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
std::string uploadFile = "nofile.pem";
EXPECT_THROW(
{
try
{
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest manager(bus, event, objPath.c_str(), type,
verifyUnit, installPath);
MainApp mainApp(&manager);
mainApp.install(uploadFile);
}
catch (const InternalFailure& e)
{
throw;
}
},
InternalFailure);
EXPECT_FALSE(fs::exists(verifyPath));
}
/** @brief Test replacing existing certificate
*/
TEST_F(TestCertificates, TestReplaceCertificate)
{
std::string endpoint("ldap");
CertificateType type = CertificateType::Server;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest manager(bus, event, objPath.c_str(), type, verifyUnit,
std::move(installPath));
EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
.WillOnce(Return())
.WillOnce(Return());
MainApp mainApp(&manager);
mainApp.install(certificateFile);
EXPECT_TRUE(fs::exists(verifyPath));
std::vector<std::unique_ptr<Certificate>>& certs =
manager.getCertificates();
EXPECT_FALSE(certs.empty());
EXPECT_NE(certs[0], nullptr);
certs[0]->replace(certificateFile);
EXPECT_TRUE(fs::exists(verifyPath));
}
/** @brief Test replacing existing certificate
*/
TEST_F(TestCertificates, TestAuthorityReplaceCertificate)
{
std::string endpoint("ldap");
CertificateType type = CertificateType::Authority;
std::string verifyDir(certDir);
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest manager(bus, event, objPath.c_str(), type, verifyUnit,
certDir);
constexpr const unsigned int REPLACE_ITERATIONS = 10;
EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
.Times(REPLACE_ITERATIONS + 1)
.WillRepeatedly(Return());
MainApp mainApp(&manager);
mainApp.install(certificateFile);
std::vector<std::unique_ptr<Certificate>>& certs =
manager.getCertificates();
for (unsigned int i = 0; i < REPLACE_ITERATIONS; i++)
{
// Certificate successfully installed
EXPECT_FALSE(certs.empty());
std::string verifyPath =
verifyDir + "/" + getCertSubjectNameHash(certificateFile) + ".0";
// Check that certificate has been created at installation directory
EXPECT_FALSE(fs::is_empty(verifyDir));
EXPECT_TRUE(fs::exists(verifyPath));
// Check that installed cert is identical to input one
EXPECT_TRUE(compareFiles(certificateFile, verifyPath));
// Create new certificate
createNewCertificate(true);
certs[0]->replace(certificateFile);
// Verify that old certificate has been removed
EXPECT_FALSE(fs::exists(verifyPath));
}
}
/** @brief Test verifiing if delete function works.
*/
TEST_F(TestCertificates, TestStorageDeleteCertificate)
{
std::string endpoint("ldap");
CertificateType type = CertificateType::Authority;
std::string verifyDir(certDir);
std::string verifyUnit((ManagerInTest::unitToRestartInTest));
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest manager(bus, event, objPath.c_str(), type, verifyUnit,
certDir);
EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
.WillRepeatedly(Return());
MainApp mainApp(&manager);
// Check if certificate placeholder dir is empty
EXPECT_TRUE(fs::is_empty(verifyDir));
mainApp.install(certificateFile);
// Create new certificate
createNewCertificate(true);
mainApp.install(certificateFile);
createNewCertificate(true);
mainApp.install(certificateFile);
std::vector<std::unique_ptr<Certificate>>& certs =
manager.getCertificates();
// All 3 certificates successfully installed and added to manager
EXPECT_EQ(certs.size(), 3);
// Check if certificate placeholder is not empty, there should be 3
// certificates
EXPECT_FALSE(fs::is_empty(verifyDir));
certs[0]->delete_();
EXPECT_EQ(certs.size(), 2);
certs[0]->delete_();
EXPECT_EQ(certs.size(), 1);
certs[0]->delete_();
EXPECT_EQ(certs.size(), 0);
// Check if certificate placeholder is empty.
EXPECT_TRUE(fs::is_empty(verifyDir));
}
/** @brief Check if install fails if certificate file is empty
*/
TEST_F(TestCertificates, TestEmptyCertificateFile)
{
std::string endpoint("ldap");
CertificateType type = CertificateType::Client;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
std::string emptyFile("emptycert.pem");
std::ofstream ofs;
ofs.open(emptyFile, std::ofstream::out);
ofs.close();
EXPECT_THROW(
{
try
{
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest manager(bus, event, objPath.c_str(), type,
verifyUnit, installPath);
MainApp mainApp(&manager);
mainApp.install(emptyFile);
}
catch (const InvalidCertificate& e)
{
throw;
}
},
InvalidCertificate);
EXPECT_FALSE(fs::exists(verifyPath));
fs::remove(emptyFile);
}
/** @brief Check if install fails if certificate file is corrupted
*/
TEST_F(TestCertificates, TestInvalidCertificateFile)
{
std::string endpoint("ldap");
CertificateType type = CertificateType::Client;
std::ofstream ofs;
ofs.open(certificateFile, std::ofstream::out);
ofs << "-----BEGIN CERTIFICATE-----";
ofs << "ADD_SOME_INVALID_DATA_INTO_FILE";
ofs << "-----END CERTIFICATE-----";
ofs.close();
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
EXPECT_THROW(
{
try
{
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest manager(bus, event, objPath.c_str(), type,
verifyUnit, installPath);
MainApp mainApp(&manager);
mainApp.install(certificateFile);
}
catch (const InvalidCertificate& e)
{
throw;
}
},
InvalidCertificate);
EXPECT_FALSE(fs::exists(verifyPath));
}
/**
* Class to generate private and certificate only file and test verification
*/
class TestInvalidCertificate : public ::testing::Test
{
public:
TestInvalidCertificate() : bus(sdbusplus::bus::new_default())
{
}
void SetUp() override
{
char dirTemplate[] = "/tmp/FakeCerts.XXXXXX";
auto dirPtr = mkdtemp(dirTemplate);
if (dirPtr == nullptr)
{
throw std::bad_alloc();
}
certDir = std::string(dirPtr) + "/certs";
fs::create_directories(certDir);
certificateFile = "cert.pem";
keyFile = "key.pem";
std::string cmd = "openssl req -x509 -sha256 -newkey rsa:2048 ";
cmd += "-keyout key.pem -out cert.pem -days 3650 ";
cmd += "-subj "
"/O=openbmc-project.xyz/CN=localhost"
" -nodes";
auto val = std::system(cmd.c_str());
if (val)
{
std::cout << "command Error: " << val << std::endl;
}
}
void TearDown() override
{
fs::remove_all(certDir);
fs::remove(certificateFile);
fs::remove(keyFile);
}
protected:
sdbusplus::bus_t bus;
std::string certificateFile;
std::string keyFile;
std::string certDir;
};
/** @brief Check install fails if private key is missing in certificate file
*/
TEST_F(TestInvalidCertificate, TestMissingPrivateKey)
{
std::string endpoint("ldap");
CertificateType type = CertificateType::Client;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
EXPECT_THROW(
{
try
{
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest manager(bus, event, objPath.c_str(), type,
verifyUnit, installPath);
MainApp mainApp(&manager);
mainApp.install(certificateFile);
}
catch (const InternalFailure& e)
{
throw;
}
},
InternalFailure);
EXPECT_FALSE(fs::exists(verifyPath));
}
/** @brief Check install fails if ceritificate is missing in certificate file
*/
TEST_F(TestInvalidCertificate, TestMissingCeritificate)
{
std::string endpoint("ldap");
CertificateType type = CertificateType::Client;
std::string installPath(certDir + "/" + keyFile);
std::string verifyPath(installPath);
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
EXPECT_THROW(
{
try
{
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest manager(bus, event, objPath.c_str(), type,
verifyUnit, installPath);
MainApp mainApp(&manager);
mainApp.install(keyFile);
}
catch (const InternalFailure& e)
{
throw;
}
},
InvalidCertificate);
EXPECT_FALSE(fs::exists(verifyPath));
}
/** @brief Check if error is thrown when multiple certificates are installed
* At present only one certificate per service is allowed
*/
TEST_F(TestCertificates, TestCertInstallNotAllowed)
{
using NotAllowed =
sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed;
std::string endpoint("ldap");
CertificateType type = CertificateType::Client;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest manager(bus, event, objPath.c_str(), type, verifyUnit,
installPath);
MainApp mainApp(&manager);
mainApp.install(certificateFile);
EXPECT_TRUE(fs::exists(verifyPath));
EXPECT_THROW(
{
try
{
// install second certificate
mainApp.install(certificateFile);
}
catch (const NotAllowed& e)
{
throw;
}
},
NotAllowed);
}
TEST_F(TestCertificates, TestGenerateCSR)
{
std::string endpoint("https");
std::string unit;
CertificateType type = CertificateType::Server;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
std::string CSRPath(certDir + "/" + CSRFile);
std::string privateKeyPath(certDir + "/" + privateKeyFile);
std::vector<std::string> alternativeNames{"localhost1", "localhost2"};
std::string challengePassword("Password");
std::string city("HYB");
std::string commonName("abc.com");
std::string contactPerson("Admin");
std::string country("IN");
std::string email("admin@in.ibm.com");
std::string givenName("givenName");
std::string initials("G");
int64_t keyBitLength(2048);
std::string keyCurveId("0");
std::string keyPairAlgorithm("RSA");
std::vector<std::string> keyUsage{"serverAuth", "clientAuth"};
std::string organization("IBM");
std::string organizationalUnit("orgUnit");
std::string state("TS");
std::string surname("surname");
std::string unstructuredName("unstructuredName");
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
Manager manager(bus, event, objPath.c_str(), type, std::move(unit),
std::move(installPath));
Status status;
CSR csr(bus, objPath.c_str(), CSRPath.c_str(), status);
MainApp mainApp(&manager, &csr);
mainApp.generateCSR(alternativeNames, challengePassword, city, commonName,
contactPerson, country, email, givenName, initials,
keyBitLength, keyCurveId, keyPairAlgorithm, keyUsage,
organization, organizationalUnit, state, surname,
unstructuredName);
std::string csrData("");
// generateCSR takes considerable time to create CSR and privateKey Files
EXPECT_FALSE(fs::exists(CSRPath));
EXPECT_FALSE(fs::exists(privateKeyPath));
EXPECT_THROW(
{
try
{
csrData = csr.csr();
}
catch (const InternalFailure& e)
{
throw;
}
},
InternalFailure);
// wait for 10 sec to get CSR and privateKey Files generated
sleep(10);
EXPECT_TRUE(fs::exists(CSRPath));
EXPECT_TRUE(fs::exists(privateKeyPath));
csrData = csr.csr();
ASSERT_NE("", csrData.c_str());
}
/** @brief Check if ECC key pair is generated when user is not given algorithm
* type. At present RSA and EC key pair algorithm are supported
*/
TEST_F(TestCertificates, TestGenerateCSRwithEmptyKeyPairAlgorithm)
{
std::string endpoint("https");
std::string unit;
CertificateType type = CertificateType::Server;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
std::string CSRPath(certDir + "/" + CSRFile);
std::string privateKeyPath(certDir + "/" + privateKeyFile);
std::vector<std::string> alternativeNames{"localhost1", "localhost2"};
std::string challengePassword("Password");
std::string city("HYB");
std::string commonName("abc.com");
std::string contactPerson("Admin");
std::string country("IN");
std::string email("admin@in.ibm.com");
std::string givenName("givenName");
std::string initials("G");
int64_t keyBitLength(2048);
std::string keyCurveId("");
std::string keyPairAlgorithm("");
std::vector<std::string> keyUsage{"serverAuth", "clientAuth"};
std::string organization("IBM");
std::string organizationalUnit("orgUnit");
std::string state("TS");
std::string surname("surname");
std::string unstructuredName("unstructuredName");
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
Manager manager(bus, event, objPath.c_str(), type, std::move(unit),
std::move(installPath));
Status status;
CSR csr(bus, objPath.c_str(), CSRPath.c_str(), status);
MainApp mainApp(&manager, &csr);
mainApp.generateCSR(alternativeNames, challengePassword, city, commonName,
contactPerson, country, email, givenName, initials,
keyBitLength, keyCurveId, keyPairAlgorithm, keyUsage,
organization, organizationalUnit, state, surname,
unstructuredName);
sleep(10);
EXPECT_TRUE(fs::exists(CSRPath));
EXPECT_TRUE(fs::exists(privateKeyPath));
}
/** @brief Check if error is thrown when giving un supported key pair
* algorithm. At present RSA and EC key pair algorithm are supported
*/
TEST_F(TestCertificates, TestGenerateCSRwithUnsupportedKeyPairAlgorithm)
{
std::string endpoint("https");
std::string unit;
CertificateType type = CertificateType::Server;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
std::string CSRPath(certDir + "/" + CSRFile);
std::string privateKeyPath(certDir + "/" + privateKeyFile);
std::vector<std::string> alternativeNames{"localhost1", "localhost2"};
std::string challengePassword("Password");
std::string city("HYB");
std::string commonName("abc.com");
std::string contactPerson("Admin");
std::string country("IN");
std::string email("admin@in.ibm.com");
std::string givenName("givenName");
std::string initials("G");
int64_t keyBitLength(2048);
std::string keyCurveId("secp521r1");
std::string keyPairAlgorithm("UnSupportedAlgorithm");
std::vector<std::string> keyUsage{"serverAuth", "clientAuth"};
std::string organization("IBM");
std::string organizationalUnit("orgUnit");
std::string state("TS");
std::string surname("surname");
std::string unstructuredName("unstructuredName");
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
Manager manager(bus, event, objPath.c_str(), type, std::move(unit),
std::move(installPath));
Status status;
CSR csr(bus, objPath.c_str(), CSRPath.c_str(), status);
MainApp mainApp(&manager, &csr);
mainApp.generateCSR(alternativeNames, challengePassword, city, commonName,
contactPerson, country, email, givenName, initials,
keyBitLength, keyCurveId, keyPairAlgorithm, keyUsage,
organization, organizationalUnit, state, surname,
unstructuredName);
EXPECT_FALSE(fs::exists(CSRPath));
EXPECT_FALSE(fs::exists(privateKeyPath));
}
/** @brief Check if error is thrown when NID_undef is returned for given key
* curve id
*/
TEST_F(TestCertificates, TestECKeyGenerationwithNIDundefCase)
{
std::string endpoint("https");
std::string unit;
CertificateType type = CertificateType::Server;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
std::string CSRPath(certDir + "/" + CSRFile);
std::string privateKeyPath(certDir + "/" + privateKeyFile);
std::vector<std::string> alternativeNames{"localhost1", "localhost2"};
std::string challengePassword("Password");
std::string city("BLR");
std::string commonName("abc.com");
std::string contactPerson("Admin");
std::string country("IN");
std::string email("admin@in.ibm.com");
std::string givenName("givenName");
std::string initials("G");
int64_t keyBitLength(2048);
std::string keyCurveId("DummyCurveName");
std::string keyPairAlgorithm("EC");
std::vector<std::string> keyUsage{"serverAuth", "clientAuth"};
std::string organization("IBM");
std::string organizationalUnit("orgUnit");
std::string state("TS");
std::string surname("surname");
std::string unstructuredName("unstructuredName");
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
Manager manager(bus, event, objPath.c_str(), type, std::move(unit),
std::move(installPath));
Status status;
CSR csr(bus, objPath.c_str(), CSRPath.c_str(), status);
MainApp mainApp(&manager, &csr);
mainApp.generateCSR(alternativeNames, challengePassword, city, commonName,
contactPerson, country, email, givenName, initials,
keyBitLength, keyCurveId, keyPairAlgorithm, keyUsage,
organization, organizationalUnit, state, surname,
unstructuredName);
EXPECT_FALSE(fs::exists(CSRPath));
EXPECT_FALSE(fs::exists(privateKeyPath));
}
/** @brief Check default Key Curve Id is used if given curve id is empty
*/
TEST_F(TestCertificates, TestECKeyGenerationwithDefaultKeyCurveId)
{
std::string endpoint("https");
std::string unit;
CertificateType type = CertificateType::Server;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
std::string CSRPath(certDir + "/" + CSRFile);
std::string privateKeyPath(certDir + "/" + privateKeyFile);
std::vector<std::string> alternativeNames{"localhost1", "localhost2"};
std::string challengePassword("Password");
std::string city("BLR");
std::string commonName("abc.com");
std::string contactPerson("Admin");
std::string country("IN");
std::string email("admin@in.ibm.com");
std::string givenName("givenName");
std::string initials("G");
int64_t keyBitLength(2048);
std::string keyCurveId("");
std::string keyPairAlgorithm("EC");
std::vector<std::string> keyUsage{"serverAuth", "clientAuth"};
std::string organization("IBM");
std::string organizationalUnit("orgUnit");
std::string state("TS");
std::string surname("surname");
std::string unstructuredName("unstructuredName");
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
Manager manager(bus, event, objPath.c_str(), type, std::move(unit),
std::move(installPath));
Status status;
CSR csr(bus, objPath.c_str(), CSRPath.c_str(), status);
MainApp mainApp(&manager, &csr);
mainApp.generateCSR(alternativeNames, challengePassword, city, commonName,
contactPerson, country, email, givenName, initials,
keyBitLength, keyCurveId, keyPairAlgorithm, keyUsage,
organization, organizationalUnit, state, surname,
unstructuredName);
sleep(10);
EXPECT_TRUE(fs::exists(CSRPath));
EXPECT_TRUE(fs::exists(privateKeyPath));
}
/** @brief Check if error is not thrown to generate EC key pair
*/
TEST_F(TestCertificates, TestECKeyGeneration)
{
std::string endpoint("https");
std::string unit;
CertificateType type = CertificateType::Server;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
std::string CSRPath(certDir + "/" + CSRFile);
std::string privateKeyPath(certDir + "/" + privateKeyFile);
std::vector<std::string> alternativeNames{"localhost1", "localhost2"};
std::string challengePassword("Password");
std::string city("BLR");
std::string commonName("abc.com");
std::string contactPerson("Admin");
std::string country("IN");
std::string email("admin@in.ibm.com");
std::string givenName("givenName");
std::string initials("G");
int64_t keyBitLength(2048);
std::string keyCurveId("secp521r1");
std::string keyPairAlgorithm("EC");
std::vector<std::string> keyUsage{"serverAuth", "clientAuth"};
std::string organization("IBM");
std::string organizationalUnit("orgUnit");
std::string state("TS");
std::string surname("surname");
std::string unstructuredName("unstructuredName");
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
Manager manager(bus, event, objPath.c_str(), type, std::move(unit),
std::move(installPath));
Status status;
CSR csr(bus, objPath.c_str(), CSRPath.c_str(), status);
MainApp mainApp(&manager, &csr);
mainApp.generateCSR(alternativeNames, challengePassword, city, commonName,
contactPerson, country, email, givenName, initials,
keyBitLength, keyCurveId, keyPairAlgorithm, keyUsage,
organization, organizationalUnit, state, surname,
unstructuredName);
std::cout << "CSRPath: " << CSRPath << std::endl
<< "privateKeyPath: " << privateKeyPath << std::endl;
sleep(10);
EXPECT_TRUE(fs::exists(CSRPath));
EXPECT_TRUE(fs::exists(privateKeyPath));
}
/** @brief Check error is thrown if giving unsupported key bit length to
* generate rsa key
*/
TEST_F(TestCertificates, TestRSAKeyWithUnsupportedKeyBitLength)
{
std::string endpoint("https");
std::string unit;
CertificateType type = CertificateType::Server;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
std::string CSRPath(certDir + "/" + CSRFile);
std::string privateKeyPath(certDir + "/" + privateKeyFile);
std::vector<std::string> alternativeNames{"localhost1", "localhost2"};
std::string challengePassword("Password");
std::string city("BLR");
std::string commonName("abc.com");
std::string contactPerson("Admin");
std::string country("IN");
std::string email("admin@in.ibm.com");
std::string givenName("givenName");
std::string initials("G");
int64_t keyBitLength(4096);
std::string keyCurveId("secp521r1");
std::string keyPairAlgorithm("RSA");
std::vector<std::string> keyUsage{"serverAuth", "clientAuth"};
std::string organization("IBM");
std::string organizationalUnit("orgUnit");
std::string state("TS");
std::string surname("surname");
std::string unstructuredName("unstructuredName");
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
Manager manager(bus, event, objPath.c_str(), type, std::move(unit),
std::move(installPath));
Status status;
CSR csr(bus, objPath.c_str(), CSRPath.c_str(), status);
MainApp mainApp(&manager, &csr);
mainApp.generateCSR(alternativeNames, challengePassword, city, commonName,
contactPerson, country, email, givenName, initials,
keyBitLength, keyCurveId, keyPairAlgorithm, keyUsage,
organization, organizationalUnit, state, surname,
unstructuredName);
EXPECT_FALSE(fs::exists(CSRPath));
EXPECT_FALSE(fs::exists(privateKeyPath));
}
/** @brief Check error is thrown if generated rsa key file is not present
*/
TEST_F(TestCertificates, TestRSAKeyFileNotPresentCase)
{
std::string endpoint("https");
std::string unit;
CertificateType type = CertificateType::Server;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
std::string CSRPath(certDir + "/" + CSRFile);
std::string privateKeyPath(certDir + "/" + privateKeyFile);
std::vector<std::string> alternativeNames{"localhost1", "localhost2"};
std::string challengePassword("Password");
std::string city("BLR");
std::string commonName("abc.com");
std::string contactPerson("Admin");
std::string country("IN");
std::string email("admin@in.ibm.com");
std::string givenName("givenName");
std::string initials("G");
int64_t keyBitLength(2048);
std::string keyCurveId("secp521r1");
std::string keyPairAlgorithm("RSA");
std::vector<std::string> keyUsage{"serverAuth", "clientAuth"};
std::string organization("IBM");
std::string organizationalUnit("orgUnit");
std::string state("TS");
std::string surname("surname");
std::string unstructuredName("unstructuredName");
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
Manager manager(bus, event, objPath.c_str(), type, std::move(unit),
std::move(installPath));
// Removing generated RSA key file
fs::remove(rsaPrivateKeyFilePath);
Status status;
CSR csr(bus, objPath.c_str(), CSRPath.c_str(), status);
MainApp mainApp(&manager, &csr);
mainApp.generateCSR(alternativeNames, challengePassword, city, commonName,
contactPerson, country, email, givenName, initials,
keyBitLength, keyCurveId, keyPairAlgorithm, keyUsage,
organization, organizationalUnit, state, surname,
unstructuredName);
EXPECT_FALSE(fs::exists(CSRPath));
EXPECT_FALSE(fs::exists(privateKeyPath));
}
/** @brief Check private key file is created from generated rsa key file is
* `present
*/
TEST_F(TestCertificates, TestRSAKeyFromRSAKeyFileIsWrittenIntoPrivateKeyFile)
{
std::string endpoint("https");
std::string unit;
CertificateType type = CertificateType::Server;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
std::string CSRPath(certDir + "/" + CSRFile);
std::string privateKeyPath(certDir + "/" + privateKeyFile);
std::vector<std::string> alternativeNames{"localhost1", "localhost2"};
std::string challengePassword("Password");
std::string city("BLR");
std::string commonName("abc.com");
std::string contactPerson("Admin");
std::string country("IN");
std::string email("admin@in.ibm.com");
std::string givenName("givenName");
std::string initials("G");
int64_t keyBitLength(2048);
std::string keyCurveId("secp521r1");
std::string keyPairAlgorithm("RSA");
std::vector<std::string> keyUsage{"serverAuth", "clientAuth"};
std::string organization("IBM");
std::string organizationalUnit("orgUnit");
std::string state("TS");
std::string surname("surname");
std::string unstructuredName("unstructuredName");
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
Manager manager(bus, event, objPath.c_str(), type, std::move(unit),
std::move(installPath));
Status status;
CSR csr(bus, objPath.c_str(), CSRPath.c_str(), status);
MainApp mainApp(&manager, &csr);
mainApp.generateCSR(alternativeNames, challengePassword, city, commonName,
contactPerson, country, email, givenName, initials,
keyBitLength, keyCurveId, keyPairAlgorithm, keyUsage,
organization, organizationalUnit, state, surname,
unstructuredName);
sleep(10);
EXPECT_TRUE(fs::exists(CSRPath));
EXPECT_TRUE(fs::exists(privateKeyPath));
}
/** @brief Check RSA key is generated during application startup*/
TEST_F(TestCertificates, TestGenerateRSAPrivateKeyFile)
{
std::string endpoint("https");
CertificateType type = CertificateType::Server;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
EXPECT_FALSE(fs::exists(rsaPrivateKeyFilePath));
Manager manager(bus, event, objPath.c_str(), type, verifyUnit, installPath);
EXPECT_TRUE(fs::exists(rsaPrivateKeyFilePath));
}
/**
* Class to test Authorities List installation and replacement
*/
class AuthoritiesListTest : public testing::Test
{
public:
AuthoritiesListTest() :
bus(sdbusplus::bus::new_default()),
authoritiesListFolder(
Certificate::generateUniqueFilePath(fs::temp_directory_path()))
{
fs::create_directory(authoritiesListFolder);
createAuthoritiesList(maxNumAuthorityCertificates);
}
~AuthoritiesListTest() override
{
fs::remove_all(authoritiesListFolder);
}
protected:
// Creates a testing authorities list which consists of |count| root
// certificates
void createAuthoritiesList(int count)
{
fs::path srcFolder = fs::temp_directory_path();
srcFolder = Certificate::generateUniqueFilePath(srcFolder);
fs::create_directory(srcFolder);
createSingleAuthority(srcFolder, "root_0");
sourceAuthoritiesListFile = srcFolder / "root_0_cert";
for (int i = 1; i < count; ++i)
{
std::string name = "root_" + std::to_string(i);
createSingleAuthority(srcFolder, name);
appendContentFromFile(sourceAuthoritiesListFile,
srcFolder / (name + "_cert"));
}
}
// Creates a single self-signed root certificate in given |path|; the key
// will be |path|/|cn|_key, the cert will be |path|/|cn|_cert, and the cn
// will be "/O=openbmc-project.xyz/C=US/ST=CA/CN=|cn|"
static void createSingleAuthority(const std::string& path,
const std::string& cn)
{
std::string key = fs::path(path) / (cn + "_key");
std::string cert = fs::path(path) / (cn + "_cert");
std::string cmd = "openssl req -x509 -sha256 -newkey rsa:2048 -keyout ";
cmd += key + " -out " + cert + " -nodes --days 365000 ";
cmd += "-subj /O=openbmc-project.xyz/CN=" + cn;
ASSERT_EQ(std::system(cmd.c_str()), 0);
}
// Appends the content of the |from| file to the |to| file.
static void appendContentFromFile(const std::string& to,
const std::string& from)
{
ASSERT_NO_THROW({
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);
inputCertFileStream.open(from);
outputCertFileStream.open(to, std::ios::app);
outputCertFileStream << inputCertFileStream.rdbuf() << std::flush;
inputCertFileStream.close();
outputCertFileStream.close();
});
}
// Appends the content of the |from| buffer to the |to| file.
static void setContentFromString(const std::string& to,
const std::string& from)
{
ASSERT_NO_THROW({
std::ofstream outputCertFileStream;
outputCertFileStream.exceptions(std::ofstream::failbit |
std::ofstream::badbit |
std::ofstream::eofbit);
outputCertFileStream.open(to, std::ios::out);
outputCertFileStream << from << std::flush;
outputCertFileStream.close();
});
}
// Verifies the effect of InstallAll or ReplaceAll
void verifyCertificates(std::vector<std::unique_ptr<Certificate>>& certs)
{
// The trust bundle file has been copied over
EXPECT_FALSE(fs::is_empty(authoritiesListFolder));
EXPECT_TRUE(
compareFiles(authoritiesListFolder / defaultAuthoritiesListFileName,
sourceAuthoritiesListFile));
ASSERT_EQ(certs.size(), maxNumAuthorityCertificates);
// Check attributes and alias
for (size_t i = 0; i < certs.size(); ++i)
{
std::string name = "root_" + std::to_string(i);
EXPECT_EQ(certs[i]->subject(), "O=openbmc-project.xyz,CN=" + name);
EXPECT_EQ(certs[i]->issuer(), "O=openbmc-project.xyz,CN=" + name);
std::string symbolLink =
authoritiesListFolder /
(certs[i]->getCertId().substr(0, 8) + ".0");
ASSERT_TRUE(fs::exists(symbolLink));
compareFileAgainstString(symbolLink, certs[i]->certificateString());
}
}
// Expects that the content of |path| file is |buffer|.
static void compareFileAgainstString(const std::string& path,
const std::string& buffer)
{
ASSERT_NO_THROW({
std::ifstream inputCertFileStream;
inputCertFileStream.exceptions(std::ifstream::failbit |
std::ifstream::badbit |
std::ifstream::eofbit);
inputCertFileStream.open(path);
std::stringstream read;
read << inputCertFileStream.rdbuf();
inputCertFileStream.close();
EXPECT_EQ(read.str(), buffer);
});
};
sdbusplus::bus_t bus;
fs::path authoritiesListFolder;
fs::path sourceAuthoritiesListFile;
};
// Tests that the Authority Manager installs all the certificates in an
// authorities list
TEST_F(AuthoritiesListTest, InstallAll)
{
std::string endpoint("ldap");
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
CertificateType type = CertificateType::Authority;
std::string object = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest manager(bus, event, object.c_str(), type, verifyUnit,
authoritiesListFolder);
EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
.WillOnce(Return());
ASSERT_TRUE(manager.getCertificates().empty());
std::vector<sdbusplus::message::object_path> objects =
manager.installAll(sourceAuthoritiesListFile);
for (size_t i = 0; i < manager.getCertificates().size(); ++i)
{
EXPECT_EQ(manager.getCertificates()[i]->getObjectPath(), objects[i]);
}
verifyCertificates(manager.getCertificates());
}
// Tests that the Authority Manager recovers from the authorities list persisted
// in the installation path at boot up
TEST_F(AuthoritiesListTest, RecoverAtBootUp)
{
std::string endpoint("ldap");
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
CertificateType type = CertificateType::Authority;
std::string object = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
// Copy the trust bundle into the installation path before creating an
// Authority Manager
fs::copy_file(/*from=*/sourceAuthoritiesListFile,
authoritiesListFolder / defaultAuthoritiesListFileName);
// Create some noise as well
fs::copy_file(/*from=*/sourceAuthoritiesListFile,
authoritiesListFolder / "should_be_deleted");
ManagerInTest manager(bus, event, object.c_str(), type, verifyUnit,
authoritiesListFolder);
ASSERT_EQ(manager.getCertificates().size(), maxNumAuthorityCertificates);
// Check attributes and alias
std::unordered_set<std::string> expectedFiles = {authoritiesListFolder /
"trust_bundle"};
std::vector<std::unique_ptr<Certificate>>& certs =
manager.getCertificates();
for (size_t i = 0; i < certs.size(); ++i)
{
std::string name = "root_" + std::to_string(i);
EXPECT_EQ(certs[i]->subject(), "O=openbmc-project.xyz,CN=" + name);
EXPECT_EQ(certs[i]->issuer(), "O=openbmc-project.xyz,CN=" + name);
std::string symbolLink =
authoritiesListFolder / (certs[i]->getCertId().substr(0, 8) + ".0");
expectedFiles.insert(symbolLink);
expectedFiles.insert(certs[i]->getCertFilePath());
ASSERT_TRUE(fs::exists(symbolLink));
compareFileAgainstString(symbolLink, certs[i]->certificateString());
}
// Check folder content
for (auto& path : fs::directory_iterator(authoritiesListFolder))
{
EXPECT_NE(path, authoritiesListFolder / "should_be_deleted");
expectedFiles.erase(path.path());
}
EXPECT_TRUE(expectedFiles.empty());
}
TEST_F(AuthoritiesListTest, InstallAndDelete)
{
std::string endpoint("ldap");
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
CertificateType type = CertificateType::Authority;
std::string object = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest manager(bus, event, object.c_str(), type, verifyUnit,
authoritiesListFolder);
EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
.WillOnce(Return())
.WillOnce(Return());
ASSERT_TRUE(manager.getCertificates().empty());
ASSERT_EQ(manager.installAll(sourceAuthoritiesListFile).size(),
maxNumAuthorityCertificates);
manager.deleteAll();
EXPECT_TRUE(manager.getCertificates().empty());
// Check folder content
for (const fs::path& f : fs::directory_iterator(authoritiesListFolder))
{
EXPECT_THAT(f.filename(), testing::AnyOf(".", ".."));
}
}
TEST_F(AuthoritiesListTest, InstallAllWrongManagerType)
{
std::string endpoint("ldap");
CertificateType type = CertificateType::Server;
std::string object = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest serverManager(bus, event, object.c_str(), type, "",
authoritiesListFolder);
EXPECT_THROW(serverManager.installAll(sourceAuthoritiesListFile),
sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed);
type = CertificateType::Client;
object = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
ManagerInTest clientManager(bus, event, object.c_str(), type, "",
authoritiesListFolder);
EXPECT_THROW(clientManager.installAll(sourceAuthoritiesListFile),
sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed);
}
TEST_F(AuthoritiesListTest, InstallAllTwice)
{
std::string endpoint("ldap");
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
CertificateType type = CertificateType::Authority;
std::string object = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest manager(bus, event, object.c_str(), type, verifyUnit,
authoritiesListFolder);
EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
.WillOnce(Return());
ASSERT_TRUE(manager.getCertificates().empty());
ASSERT_EQ(manager.installAll(sourceAuthoritiesListFile).size(),
maxNumAuthorityCertificates);
EXPECT_THROW(manager.installAll(sourceAuthoritiesListFile),
sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed);
}
TEST_F(AuthoritiesListTest, InstallAllMissSourceFile)
{
std::string endpoint("ldap");
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
CertificateType type = CertificateType::Authority;
std::string object = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest manager(bus, event, object.c_str(), type, verifyUnit,
authoritiesListFolder);
EXPECT_THROW(manager.installAll(authoritiesListFolder / "trust_bundle"),
InternalFailure);
}
TEST_F(AuthoritiesListTest, TooManyRootCertificates)
{
std::string endpoint("ldap");
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
CertificateType type = CertificateType::Authority;
std::string object = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest manager(bus, event, object.c_str(), type, verifyUnit,
authoritiesListFolder);
createAuthoritiesList(maxNumAuthorityCertificates + 1);
EXPECT_THROW(manager.installAll(sourceAuthoritiesListFile),
sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed);
}
TEST_F(AuthoritiesListTest, CertInWrongFormat)
{
std::string endpoint("ldap");
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
CertificateType type = CertificateType::Authority;
std::string object = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest manager(bus, event, object.c_str(), type, verifyUnit,
authoritiesListFolder);
// Replace the authorities list with non-valid PEM encoded x509 certificate
setContentFromString(sourceAuthoritiesListFile, "blah-blah");
EXPECT_THROW(manager.installAll(sourceAuthoritiesListFile),
InvalidCertificate);
setContentFromString(sourceAuthoritiesListFile,
"-----BEGIN CERTIFICATE-----");
EXPECT_THROW(manager.installAll(sourceAuthoritiesListFile),
InvalidCertificate);
}
TEST_F(AuthoritiesListTest, ReplaceAll)
{
std::string endpoint("ldap");
std::string verifyUnit(ManagerInTest::unitToRestartInTest);
CertificateType type = CertificateType::Authority;
std::string object = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
// Attach the bus to sd_event to service user requests
bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
ManagerInTest manager(bus, event, object.c_str(), type, verifyUnit,
authoritiesListFolder);
EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
.WillOnce(Return())
.WillOnce(Return());
manager.installAll(sourceAuthoritiesListFile);
// Replace the current list with a different list
fs::remove_all(sourceAuthoritiesListFile.parent_path());
createAuthoritiesList(maxNumAuthorityCertificates);
std::vector<sdbusplus::message::object_path> objects =
manager.replaceAll(sourceAuthoritiesListFile);
for (size_t i = 0; i < manager.getCertificates().size(); ++i)
{
EXPECT_EQ(manager.getCertificates()[i]->getObjectPath(), objects[i]);
}
verifyCertificates(manager.getCertificates());
}
} // namespace
} // namespace phosphor::certs