1 /*
2  * \copyright Copyright (c) 2017-2021 Governikus GmbH & Co. KG, Germany
3  */
4 
5 
6 #include "KeyPair.h"
7 
8 #include "Randomizer.h"
9 
10 #include <openssl/bio.h>
11 #include <openssl/pem.h>
12 #include <openssl/rsa.h>
13 #include <openssl/x509.h>
14 
15 #include <QCoreApplication>
16 #include <QLoggingCategory>
17 #include <QScopedPointer>
18 
19 using namespace governikus;
20 
21 Q_DECLARE_LOGGING_CATEGORY(settings)
22 
23 namespace
24 {
25 struct OpenSslCustomDeleter
26 {
cleanup__anone536d6840111::OpenSslCustomDeleter27 	static inline void cleanup(EVP_PKEY* pData)
28 	{
29 		EVP_PKEY_free(pData);
30 	}
31 
32 
cleanup__anone536d6840111::OpenSslCustomDeleter33 	static inline void cleanup(EVP_PKEY_CTX* pData)
34 	{
35 		EVP_PKEY_CTX_free(pData);
36 	}
37 
38 
cleanup__anone536d6840111::OpenSslCustomDeleter39 	static inline void cleanup(BIO* pData)
40 	{
41 		BIO_free(pData);
42 	}
43 
44 
cleanup__anone536d6840111::OpenSslCustomDeleter45 	static inline void cleanup(X509_NAME* pData)
46 	{
47 		X509_NAME_free(pData);
48 	}
49 
50 
51 };
52 } // namespace
53 
KeyPair(const QSslKey & pKey,const QSslCertificate & pCert)54 KeyPair::KeyPair(const QSslKey& pKey, const QSslCertificate& pCert)
55 	: mKey(pKey)
56 	, mCertificate(pCert)
57 {
58 }
59 
60 
generate()61 KeyPair KeyPair::generate()
62 {
63 	if (!Randomizer::getInstance().isSecureRandom())
64 	{
65 		qCCritical(settings) << "Cannot get enough entropy";
66 		return KeyPair();
67 	}
68 
69 	auto pkey = createKey();
70 	if (pkey)
71 	{
72 		auto cert = createCertificate(pkey);
73 		if (cert)
74 		{
75 			return KeyPair(QSslKey(Qt::HANDLE(pkey), QSsl::PrivateKey), QSslCertificate(rewriteCertificate(cert.data())));
76 		}
77 		else
78 		{
79 			OpenSslCustomDeleter::cleanup(pkey);
80 		}
81 	}
82 
83 	return KeyPair();
84 }
85 
86 
getKey() const87 const QSslKey& KeyPair::getKey() const
88 {
89 	return mKey;
90 }
91 
92 
getCertificate() const93 const QSslCertificate& KeyPair::getCertificate() const
94 {
95 	return mCertificate;
96 }
97 
98 
createKey()99 EVP_PKEY* KeyPair::createKey()
100 {
101 	QScopedPointer<EVP_PKEY_CTX, OpenSslCustomDeleter> pkeyCtx(EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr));
102 
103 	if (pkeyCtx.isNull())
104 	{
105 		qCCritical(settings) << "Cannot create EVP_PKEY_CTX";
106 		return nullptr;
107 	}
108 
109 	if (!EVP_PKEY_keygen_init(pkeyCtx.data()))
110 	{
111 		qCCritical(settings) << "Cannot init rsa key ctx";
112 		return nullptr;
113 	}
114 
115 	if (!EVP_PKEY_CTX_set_rsa_keygen_bits(pkeyCtx.data(), 2048))
116 	{
117 		qCCritical(settings) << "Cannot generate rsa key bits";
118 		return nullptr;
119 	}
120 
121 	EVP_PKEY* pkey = nullptr;
122 	if (!EVP_PKEY_keygen(pkeyCtx.data(), &pkey))
123 	{
124 		qCCritical(settings) << "Cannot generate rsa key";
125 		return nullptr;
126 	}
127 
128 	return pkey;
129 }
130 
131 
createCertificate(EVP_PKEY * pPkey)132 QSharedPointer<X509> KeyPair::createCertificate(EVP_PKEY* pPkey)
133 {
134 	QSharedPointer<X509> x509(X509_new(), &X509_free);
135 	if (x509.isNull())
136 	{
137 		qCCritical(settings) << "Cannot create X509 structure";
138 		return nullptr;
139 	}
140 
141 	auto& randomizer = Randomizer::getInstance().getGenerator();
142 	std::uniform_int_distribution<long> uni_long(1);
143 	std::uniform_int_distribution<qulonglong> uni_qulonglong(1);
144 
145 	#if OPENSSL_VERSION_NUMBER < 0x10100000L
146 		#define X509_getm_notBefore X509_get_notBefore
147 		#define X509_getm_notAfter X509_get_notAfter
148 	#endif
149 
150 	ASN1_INTEGER_set(X509_get_serialNumber(x509.data()), uni_long(randomizer));
151 	// see: https://tools.ietf.org/html/rfc5280#section-4.1.2.5
152 	ASN1_TIME_set_string(X509_getm_notBefore(x509.data()), "19700101000000Z");
153 	ASN1_TIME_set_string(X509_getm_notAfter(x509.data()), "99991231235959Z");
154 	X509_set_pubkey(x509.data(), pPkey);
155 
156 	auto randomSerial = QByteArray::number(uni_qulonglong(randomizer));
157 	QScopedPointer<X509_NAME, OpenSslCustomDeleter> name(X509_NAME_dup(X509_get_subject_name(x509.data())));
158 	X509_NAME_add_entry_by_txt(name.data(), "CN", MBSTRING_ASC,
159 			reinterpret_cast<const uchar*>(QCoreApplication::applicationName().toLatin1().constData()), -1, -1, 0);
160 	X509_NAME_add_entry_by_txt(name.data(), "serialNumber", MBSTRING_ASC,
161 			reinterpret_cast<const uchar*>(randomSerial.constData()), -1, -1, 0);
162 	X509_set_subject_name(x509.data(), name.data());
163 	X509_set_issuer_name(x509.data(), name.data());
164 
165 	if (!X509_sign(x509.data(), pPkey, EVP_sha256()))
166 	{
167 		qCCritical(settings) << "Cannot sign certificate";
168 		return nullptr;
169 	}
170 
171 	return x509;
172 }
173 
174 
rewriteCertificate(X509 * pX509)175 QByteArray KeyPair::rewriteCertificate(X509* pX509)
176 {
177 	QScopedPointer<BIO, OpenSslCustomDeleter> bio(BIO_new(BIO_s_mem()));
178 	PEM_write_bio_X509(bio.data(), pX509);
179 	char* data = nullptr;
180 
181 	// See macro BIO_get_mem_data(bio, &data); if BIO_ctrl fails.
182 	// We do not use this to avoid -Wold-style-cast warning
183 	const long len = BIO_ctrl(bio.data(), BIO_CTRL_INFO, 0, &data);
184 	return QByteArray(data, static_cast<int>(len));
185 }
186 
187 
isValid() const188 bool KeyPair::isValid() const
189 {
190 	return !mKey.isNull() && !mCertificate.isNull();
191 }
192