Skip to content

Commit 8c6ca21

Browse files
committed
Test internal cert generation & verification process
1 parent f308bb1 commit 8c6ca21

File tree

2 files changed

+137
-81
lines changed

2 files changed

+137
-81
lines changed

test/CMakeLists.txt

+2-5
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,8 @@ add_boost_test(base
170170
base_timer/invoke
171171
base_timer/scope
172172
base_tlsutility/sha1
173-
base_tlsutility/iscauptodate_ok
174-
base_tlsutility/iscauptodate_expiring
175-
base_tlsutility/iscertuptodate_ok
176-
base_tlsutility/iscertuptodate_expiring
177-
base_tlsutility/iscertuptodate_old
173+
base_tlsutility/create_verify_ca
174+
base_tlsutility/create_verify_leaf_certs
178175
base_utility/parse_version
179176
base_utility/compare_version
180177
base_utility/comparepasswords_works

test/base-tlsutility.cpp

+135-76
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,40 @@
11
/* Icinga 2 | (c) 2021 Icinga GmbH | GPLv2+ */
22

3+
#include "base/configuration.hpp"
34
#include "base/tlsutility.hpp"
5+
#include "base/utility.hpp"
6+
#include "remote/apilistener.hpp"
7+
#include "remote/pkiutility.hpp"
48
#include <BoostTestTargetConfig.h>
9+
#include <boost/filesystem/operations.hpp>
10+
#include <boost/filesystem/path.hpp>
511
#include <functional>
612
#include <memory>
7-
#include <openssl/asn1.h>
8-
#include <openssl/bn.h>
913
#include <openssl/evp.h>
10-
#include <openssl/obj_mac.h>
11-
#include <openssl/rsa.h>
1214
#include <openssl/x509.h>
1315
#include <utility>
1416
#include <vector>
1517

1618
using namespace icinga;
1719

18-
static EVP_PKEY* GenKeypair()
20+
struct ConfigurationConstantsFixture
1921
{
20-
InitializeOpenSSL();
21-
22-
auto e (BN_new());
23-
BOOST_REQUIRE(e);
24-
25-
auto rsa (RSA_new());
26-
BOOST_REQUIRE(rsa);
27-
28-
auto key (EVP_PKEY_new());
29-
BOOST_REQUIRE(key);
30-
31-
BOOST_REQUIRE(BN_set_word(e, RSA_F4));
32-
BOOST_REQUIRE(RSA_generate_key_ex(rsa, 4096, e, nullptr));
33-
BOOST_REQUIRE(EVP_PKEY_assign_RSA(key, rsa));
34-
35-
return key;
36-
}
37-
38-
static std::shared_ptr<X509> MakeCert(const char* issuer, EVP_PKEY* signer, const char* subject, EVP_PKEY* pubkey, std::function<void(ASN1_TIME*, ASN1_TIME*)> setTimes)
39-
{
40-
auto cert (X509_new());
41-
BOOST_REQUIRE(cert);
42-
43-
auto serial (BN_new());
44-
BOOST_REQUIRE(serial);
45-
46-
BOOST_REQUIRE(X509_set_version(cert, 0x2));
47-
BOOST_REQUIRE(BN_to_ASN1_INTEGER(serial, X509_get_serialNumber(cert)));
48-
BOOST_REQUIRE(X509_NAME_add_entry_by_NID(X509_get_issuer_name(cert), NID_commonName, MBSTRING_ASC, (unsigned char*)issuer, -1, -1, 0));
49-
setTimes(X509_get_notBefore(cert), X509_get_notAfter(cert));
50-
BOOST_REQUIRE(X509_NAME_add_entry_by_NID(X509_get_subject_name(cert), NID_commonName, MBSTRING_ASC, (unsigned char*)subject, -1, -1, 0));
51-
BOOST_REQUIRE(X509_set_pubkey(cert, pubkey));
52-
BOOST_REQUIRE(X509_sign(cert, signer, EVP_sha256()));
22+
String TmpDir;
23+
String PreviousDataDir;
24+
25+
ConfigurationConstantsFixture()
26+
{
27+
TmpDir = boost::filesystem::detail::temp_directory_path().string() + "/icinga2";
28+
PreviousDataDir = Configuration::DataDir;
29+
Configuration::DataDir = TmpDir;
30+
}
5331

54-
return std::shared_ptr<X509>(cert, X509_free);
55-
}
32+
~ConfigurationConstantsFixture()
33+
{
34+
Configuration::DataDir = PreviousDataDir;
35+
Utility::RemoveDirRecursive(TmpDir);
36+
}
37+
};
5638

5739
static const long l_2016 = 1480000000; // Thu Nov 24 15:06:40 UTC 2016
5840
static const long l_2017 = 1490000000; // Mon Mar 20 08:53:20 UTC 2017
@@ -85,51 +67,128 @@ BOOST_AUTO_TEST_CASE(sha1)
8567
}
8668
}
8769

88-
BOOST_AUTO_TEST_CASE(iscauptodate_ok)
70+
BOOST_AUTO_TEST_CASE(create_verify_ca)
8971
{
90-
auto key (GenKeypair());
72+
ConfigurationConstantsFixture f;
9173

92-
BOOST_CHECK(IsCaUptodate(MakeCert("Icinga CA", key, "Icinga CA", key, [](ASN1_TIME* notBefore, ASN1_TIME* notAfter) {
93-
BOOST_REQUIRE(X509_gmtime_adj(notBefore, 0));
94-
BOOST_REQUIRE(X509_gmtime_adj(notAfter, LEAF_VALID_FOR + 60 * 60));
95-
}).get()));
96-
}
74+
BOOST_CHECK_EQUAL(0, PkiUtility::NewCa());
9775

98-
BOOST_AUTO_TEST_CASE(iscauptodate_expiring)
99-
{
100-
auto key (GenKeypair());
76+
auto cacert(GetX509Certificate(ApiListener::GetCaDir()+"/ca.crt"));
77+
if (OPENSSL_VERSION_NUMBER >= 0x10100000L) {
78+
// OpenSSL 1.1.x provides https://www.openssl.org/docs/man1.1.0/man3/X509_check_ca.html
79+
BOOST_CHECK_EQUAL(true, IsCa(cacert));
80+
} else {
81+
BOOST_CHECK_THROW(IsCa(cacert), std::invalid_argument);
82+
}
83+
BOOST_CHECK_EQUAL(true, VerifyCertificate(cacert, cacert, String()));
84+
BOOST_CHECK_EQUAL(true, IsCaUptodate(cacert.get()));
10185

102-
BOOST_CHECK(!IsCaUptodate(MakeCert("Icinga CA", key, "Icinga CA", key, [](ASN1_TIME* notBefore, ASN1_TIME* notAfter) {
103-
BOOST_REQUIRE(X509_gmtime_adj(notBefore, 0));
104-
BOOST_REQUIRE(X509_gmtime_adj(notAfter, LEAF_VALID_FOR - 60 * 60));
105-
}).get()));
106-
}
86+
time_t caValidUntil(time(nullptr) + ROOT_VALID_FOR);
87+
BOOST_CHECK_EQUAL(-1, X509_cmp_time(X509_get_notAfter(cacert.get()), &caValidUntil));
10788

108-
BOOST_AUTO_TEST_CASE(iscertuptodate_ok)
109-
{
110-
BOOST_CHECK(IsCertUptodate(MakeCert("Icinga CA", GenKeypair(), "example.com", GenKeypair(), [](ASN1_TIME* notBefore, ASN1_TIME* notAfter) {
111-
time_t epoch = 0;
112-
BOOST_REQUIRE(X509_time_adj(notBefore, l_2017, &epoch));
113-
BOOST_REQUIRE(X509_gmtime_adj(notAfter, RENEW_THRESHOLD + 60 * 60));
114-
})));
115-
}
89+
// Set the CA certificate to expire in 100 days, i.e. less than the LEAF_VALID_FOR threshold of 397 days.
90+
BOOST_CHECK(X509_gmtime_adj(X509_get_notAfter(cacert.get()), 60*60*24*100));
91+
BOOST_CHECK_EQUAL(false, IsCaUptodate(cacert.get()));
11692

117-
BOOST_AUTO_TEST_CASE(iscertuptodate_expiring)
118-
{
119-
BOOST_CHECK(!IsCertUptodate(MakeCert("Icinga CA", GenKeypair(), "example.com", GenKeypair(), [](ASN1_TIME* notBefore, ASN1_TIME* notAfter) {
120-
time_t epoch = 0;
121-
BOOST_REQUIRE(X509_time_adj(notBefore, l_2017, &epoch));
122-
BOOST_REQUIRE(X509_gmtime_adj(notAfter, RENEW_THRESHOLD - 60 * 60));
123-
})));
93+
// Even if the CA is going to expire at exactly the same time as the LEAF_VALID_FOR threshold,
94+
// it is still considered to be outdated.
95+
BOOST_CHECK(X509_gmtime_adj(X509_get_notAfter(cacert.get()), 60*60*24*397));
96+
BOOST_CHECK_EQUAL(false, IsCaUptodate(cacert.get()));
97+
98+
// Reset the CA expiration date to the original value, i.e. 15 years.
99+
BOOST_CHECK(X509_gmtime_adj(X509_get_notAfter(cacert.get()), ROOT_VALID_FOR));
100+
BOOST_CHECK_EQUAL(true, IsCaUptodate(cacert.get()));
124101
}
125102

126-
BOOST_AUTO_TEST_CASE(iscertuptodate_old)
103+
BOOST_AUTO_TEST_CASE(create_verify_leaf_certs)
127104
{
128-
BOOST_CHECK(!IsCertUptodate(MakeCert("Icinga CA", GenKeypair(), "example.com", GenKeypair(), [](ASN1_TIME* notBefore, ASN1_TIME* notAfter) {
129-
time_t epoch = 0;
130-
BOOST_REQUIRE(X509_time_adj(notBefore, l_2016, &epoch));
131-
BOOST_REQUIRE(X509_gmtime_adj(notAfter, RENEW_THRESHOLD + 60 * 60));
132-
})));
105+
ConfigurationConstantsFixture f;
106+
107+
String caDir = ApiListener::GetCaDir();
108+
String certsDir = ApiListener::GetCertsDir();
109+
Utility::MkDirP(certsDir, 0700);
110+
111+
BOOST_CHECK_EQUAL(0, PkiUtility::NewCa());
112+
113+
auto cacert(GetX509Certificate(caDir+"/ca.crt"));
114+
BOOST_CHECK_EQUAL(true, IsCaUptodate(cacert.get()));
115+
116+
BOOST_CHECK_EQUAL(0, PkiUtility::NewCert("example.com", certsDir+"example.key", certsDir+"example.csr", certsDir+"example.crt"));
117+
BOOST_CHECK_EQUAL(0, PkiUtility::SignCsr(certsDir+"example.csr", certsDir+"example.crt"));
118+
119+
auto cert(GetX509Certificate(certsDir+"/example.crt"));
120+
if (OPENSSL_VERSION_NUMBER >= 0x10100000L) {
121+
BOOST_CHECK_EQUAL(false, IsCa(cert));
122+
} else {
123+
BOOST_CHECK_THROW(IsCa(cert), std::invalid_argument);
124+
}
125+
BOOST_CHECK_EQUAL(true, IsCertUptodate(cert));
126+
BOOST_CHECK_EQUAL(true, VerifyCertificate(cacert, cert, String()));
127+
128+
time_t certValidUntil(time(nullptr) + LEAF_VALID_FOR);
129+
BOOST_CHECK_EQUAL(-1, X509_cmp_time(X509_get_notAfter(cert.get()), &certValidUntil));
130+
131+
// Set the certificate to expire in 20 days, i.e. less than the RENEW_THRESHOLD of 30 days.
132+
BOOST_CHECK(X509_gmtime_adj(X509_get_notAfter(cert.get()), 60*60*24*20));
133+
BOOST_CHECK_EQUAL(false, IsCertUptodate(cert));
134+
135+
// Check whether expired certificates are correctly detected and verification fails.
136+
BOOST_CHECK(X509_gmtime_adj(X509_get_notAfter(cert.get()), -10));
137+
BOOST_CHECK_EQUAL(false, IsCertUptodate(cert));
138+
BOOST_CHECK_THROW(VerifyCertificate(cacert, cert, String()), openssl_error);
139+
140+
// Reset the certificate expiration date to the original value, i.e. 397 days.
141+
BOOST_CHECK(X509_gmtime_adj(X509_get_notAfter(cert.get()), LEAF_VALID_FOR));
142+
BOOST_CHECK_EQUAL(true, IsCertUptodate(cert));
143+
BOOST_CHECK_EQUAL(true, VerifyCertificate(cacert, cert, String()));
144+
145+
// Set the certificate validity start date to 2016, all certificates created before 2017 are considered outdated.
146+
BOOST_CHECK(X509_gmtime_adj(X509_get_notBefore(cert.get()), -(time(nullptr)-l_2016)));
147+
BOOST_CHECK_EQUAL(false, IsCertUptodate(cert));
148+
BOOST_CHECK_EQUAL(true, VerifyCertificate(cacert, cert, String()));
149+
150+
// Reset the certificate validity start date to the least acceptable value, i.e. 2017.
151+
BOOST_CHECK(X509_gmtime_adj(X509_get_notBefore(cert.get()), -(time(nullptr)-l_2017)));
152+
BOOST_CHECK_EQUAL(true, IsCertUptodate(cert));
153+
BOOST_CHECK_EQUAL(true, VerifyCertificate(cacert, cert, String()));
154+
155+
// Even if the leaf is up-to-date, the root CA has expired 10 days ago, so verification should fail.
156+
BOOST_CHECK(X509_gmtime_adj(X509_get_notAfter(cacert.get()), -10));
157+
BOOST_CHECK_EQUAL(false, IsCaUptodate(cacert.get()));
158+
BOOST_CHECK_THROW(VerifyCertificate(cacert, cert, String()), openssl_error);
159+
160+
// Generate a new CA certificate to simulate a renewal and check whether verification still works.
161+
std::shared_ptr<EVP_PKEY> caPubKey(X509_get_pubkey(cacert.get()), EVP_PKEY_free);
162+
auto subject(X509_get_subject_name(cacert.get()));
163+
auto newCACert(CreateCertIcingaCA(caPubKey.get(), subject, true));
164+
BOOST_REQUIRE(newCACert);
165+
BOOST_CHECK_EQUAL(true, IsCaUptodate(newCACert.get()));
166+
BOOST_CHECK_EQUAL(true, VerifyCertificate(newCACert, newCACert, String()));
167+
BOOST_CHECK_EQUAL(true, VerifyCertificate(newCACert, cert, String()));
168+
BOOST_CHECK_THROW(VerifyCertificate(cacert, newCACert, String()), openssl_error);
169+
170+
// Remove the previously generated CA before regenerating a new one, PkiUtility::NewCa() would fail otherwise.
171+
Utility::RemoveDirRecursive(caDir);
172+
BOOST_CHECK_EQUAL(0, PkiUtility::NewCa());
173+
174+
newCACert = GetX509Certificate(caDir+"/ca.crt");
175+
BOOST_REQUIRE(newCACert);
176+
BOOST_CHECK_NE(0, ASN1_INTEGER_cmp(X509_get_serialNumber(cacert.get()), X509_get_serialNumber(newCACert.get())));
177+
BOOST_CHECK_NE(1, EVP_PKEY_cmp(X509_get_pubkey(cacert.get()), X509_get_pubkey(newCACert.get())));
178+
BOOST_CHECK_EQUAL(true, IsCaUptodate(newCACert.get()));
179+
BOOST_CHECK_EQUAL(true, VerifyCertificate(newCACert, newCACert, String())); // Self-signed CA!
180+
// Verification should fail because the leaf certificate was signed by the old CA.
181+
BOOST_CHECK_THROW(VerifyCertificate(newCACert, cert, String()), openssl_error);
182+
183+
// Renew the leaf certificate and check whether verification works with the new CA.
184+
std::shared_ptr<EVP_PKEY> pubkey(X509_get_pubkey(cert.get()), EVP_PKEY_free);
185+
auto leafSubject(X509_get_subject_name(cert.get()));
186+
auto newCert(CreateCertIcingaCA(pubkey.get(), leafSubject, false));
187+
BOOST_REQUIRE(newCert);
188+
BOOST_CHECK_EQUAL(true, IsCertUptodate(newCert));
189+
BOOST_CHECK_EQUAL(true, VerifyCertificate(newCACert, newCert, String()));
190+
// Verification should fail because the new leaf certificate was signed by the newly generated CA.
191+
BOOST_CHECK_THROW(VerifyCertificate(cacert, newCert, String()), openssl_error);
133192
}
134193

135194
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)