1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qopcuax509certificatesigningrequest.h"
5#include "openssl_symbols_p.h"
6#include "qopcuakeypair_p.h"
7#include "qopcuax509utils_p.h"
8#include "qopcuax509distinguishedname.h"
9#include "qopcuax509extensionsubjectalternativename.h"
10#include "qopcuax509certificatesigningrequest_p.h"
11#include <QOpcUaX509ExtensionBasicConstraints>
12#include <QOpcUaX509ExtensionKeyUsage>
13#include <QOpcUaX509ExtensionExtendedKeyUsage>
14
15
16QT_BEGIN_NAMESPACE
17
18using namespace Qt::Literals::StringLiterals;
19
20QOpcUaX509CertificateSigningRequestPrivate::QOpcUaX509CertificateSigningRequestPrivate()
21{
22
23}
24
25QOpcUaX509CertificateSigningRequestPrivate::~QOpcUaX509CertificateSigningRequestPrivate()
26{
27 qDeleteAll(c: m_extensions);
28}
29
30void QOpcUaX509CertificateSigningRequestPrivate::setMessageDigest(QOpcUaX509CertificateSigningRequest::MessageDigest digest)
31{
32 m_messageDigest = digest;
33}
34
35QOpcUaX509CertificateSigningRequest::MessageDigest QOpcUaX509CertificateSigningRequestPrivate::messageDigest() const
36{
37 return m_messageDigest;
38}
39
40void QOpcUaX509CertificateSigningRequestPrivate::addExtension(QOpcUaX509Extension *extension)
41{
42 m_extensions.append(t: extension);
43}
44
45void QOpcUaX509CertificateSigningRequestPrivate::setSubject(const QOpcUaX509DistinguishedName &subject)
46{
47 m_subject = subject;
48}
49
50QOpcUaX509CertificateSigningRequest::Encoding QOpcUaX509CertificateSigningRequestPrivate::encoding() const
51{
52 return m_encoding;
53}
54
55void QOpcUaX509CertificateSigningRequestPrivate::setEncoding(QOpcUaX509CertificateSigningRequest::Encoding encoding)
56{
57 m_encoding = encoding;
58}
59
60const QOpcUaX509DistinguishedName &QOpcUaX509CertificateSigningRequestPrivate::subject() const
61{
62 return m_subject;
63}
64
65static X509_EXTENSION *createExtension(QOpcUaX509Extension *extension)
66{
67 X509_EXTENSION *ex = nullptr;
68
69 if (const auto *san = dynamic_cast<const QOpcUaX509ExtensionSubjectAlternativeName *>(extension)) {
70 QStringList data;
71
72 for (const auto &pair : std::as_const(t: san->entries())) {
73 QString prefix;
74 if (pair.first == QOpcUaX509ExtensionSubjectAlternativeName::Type::DNS)
75 prefix = u"DNS:"_s;
76 else if (pair.first == QOpcUaX509ExtensionSubjectAlternativeName::Type::Email)
77 prefix = u"EMAIL:"_s;
78 else if (pair.first == QOpcUaX509ExtensionSubjectAlternativeName::Type::IP)
79 prefix = u"IP:"_s;
80 else if (pair.first == QOpcUaX509ExtensionSubjectAlternativeName::Type::URI)
81 prefix = u"URI:"_s;
82 else {
83 qCWarning(lcSsl()) << "Invalid SubjectAlternativeName type";
84 return nullptr;
85 }
86
87 if (pair.second.isEmpty() || pair.second.contains(c: QChar::fromLatin1(c: ','))) {
88 qCWarning(lcSsl()) << "Invalid SubjectAlternativeName value";
89 return nullptr;
90 }
91
92 data.append(t: prefix + pair.second);
93 }
94
95 ex = q_X509V3_EXT_conf_nid(NULL, NULL, NID_subject_alt_name, value: data.join(sep: QLatin1Char(',')).toUtf8().data());
96 if (!ex) {
97 qCWarning(lcSsl()) << "Failed to create X509 extension" << data;
98 return nullptr;
99 }
100 q_X509_EXTENSION_set_critical(ex, crit: san->critical() ? 1 : 0);
101 } else if (const auto *bc = dynamic_cast<const QOpcUaX509ExtensionBasicConstraints *>(extension)) {
102 QString data = u"CA:"_s + (bc->ca() ? u"true"_s : u"false"_s);
103 if (bc->ca() && bc->pathLength() >= 0)
104 data.append(s: u",pathlen:"_s + QString::number(bc->pathLength()));
105
106 ex = q_X509V3_EXT_conf_nid(NULL, NULL, NID_basic_constraints, value: data.toUtf8().data());
107 if (!ex) {
108 qCWarning(lcSsl()) << "Failed to create X509 extension" << data;
109 return nullptr;
110 }
111 q_X509_EXTENSION_set_critical(ex, crit: bc->critical() ? 1 : 0);
112 } else if (const auto *ku = dynamic_cast<const QOpcUaX509ExtensionKeyUsage *>(extension)) {
113 QStringList data;
114
115 if (ku->keyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::DigitalSignature))
116 data.append(t: u"digitalSignature"_s);
117 if (ku->keyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::NonRepudiation))
118 data.append(t: u"nonRepudiation"_s);
119 if (ku->keyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::KeyEncipherment))
120 data.append(t: u"keyEncipherment"_s);
121 if (ku->keyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::DataEncipherment))
122 data.append(t: u"dataEncipherment"_s);
123 if (ku->keyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::KeyAgreement))
124 data.append(t: u"keyAgreement"_s);
125 if (ku->keyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::CertificateSigning))
126 data.append(t: u"keyCertSign"_s);
127 if (ku->keyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::CrlSigning))
128 data.append(t: u"cRLSign"_s);
129 if (ku->keyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::EnciptherOnly))
130 data.append(t: u"encipherOnly"_s);
131 if (ku->keyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::DecipherOnly))
132 data.append(t: u"decipherOnly"_s);
133
134 ex = q_X509V3_EXT_conf_nid(NULL, NULL, NID_key_usage, value: data.join(sep: QLatin1Char(',')).toUtf8().data());
135 if (!ex) {
136 qCWarning(lcSsl()) << "Failed to create X509 extension" << data;
137 return nullptr;
138 }
139 q_X509_EXTENSION_set_critical(ex, crit: ku->critical() ? 1 : 0);
140 } else if (const auto *eku = dynamic_cast<const QOpcUaX509ExtensionExtendedKeyUsage *>(extension)) {
141 QStringList data;
142
143 if (eku->keyUsage(QOpcUaX509ExtensionExtendedKeyUsage::KeyUsage::TlsWebServerAuthentication))
144 data.append(t: u"serverAuth"_s);
145 if (eku->keyUsage(QOpcUaX509ExtensionExtendedKeyUsage::KeyUsage::TlsWebClientAuthentication))
146 data.append(t: u"clientAuth"_s);
147 if (eku->keyUsage(QOpcUaX509ExtensionExtendedKeyUsage::KeyUsage::SignExecutableCode))
148 data.append(t: u"codeSigning"_s);
149 if (eku->keyUsage(QOpcUaX509ExtensionExtendedKeyUsage::KeyUsage::EmailProtection))
150 data.append(t: u"emailProtection"_s);
151
152 // NID_ext_key_usage
153 ex = q_X509V3_EXT_conf_nid(NULL, NULL, NID_ext_key_usage, value: data.join(sep: QLatin1Char(',')).toUtf8().data());
154 if (!ex) {
155 qCWarning(lcSsl()) << "Failed to create X509 extension" << data;
156 return nullptr;
157 }
158 q_X509_EXTENSION_set_critical(ex, crit: eku->critical() ? 1 : 0);
159 } else {
160 qCWarning(lcSsl) << "Unknown X509 extension";
161 return nullptr;
162 }
163 return ex;
164}
165
166static bool setSubjectName(X509_NAME *subject, const QOpcUaX509DistinguishedName &dn) {
167 QList<QOpcUaX509DistinguishedName::Type> entries = {
168 QOpcUaX509DistinguishedName::Type::CommonName,
169 QOpcUaX509DistinguishedName::Type::CountryName,
170 QOpcUaX509DistinguishedName::Type::LocalityName,
171 QOpcUaX509DistinguishedName::Type::StateOrProvinceName,
172 QOpcUaX509DistinguishedName::Type::OrganizationName,
173 };
174
175 for (const auto &type : entries) {
176 const auto value = dn.entry(type);
177 if (value.isEmpty())
178 continue;
179
180 ASN1_OBJECT *obj = q_OBJ_txt2obj(s: QOpcUaX509DistinguishedName::typeToOid(type).toLatin1().constData(), no_name: 1 /* no names allowed */);
181 if (!obj) {
182 qCWarning(lcSsl()) << "Invalid distinguishedName type";
183 return false;
184 }
185
186 if (!q_X509_NAME_add_entry_by_OBJ(name: subject, obj, MBSTRING_UTF8, bytes: (const unsigned char*)(value.toUtf8().constData()), len: -1, loc: -1, set: 0)) {
187 qCWarning(lcSsl) << "Failed to set CSR entry:" << getOpenSslError();
188 return false;
189 }
190 }
191 return true;
192}
193
194// Creates the request and returns a PEM encoded byte array
195QByteArray QOpcUaX509CertificateSigningRequestPrivate::createRequest(const QOpcUaKeyPair &privateKey)
196{
197 if (!privateKey.hasPrivateKey()) {
198 qCWarning(lcSsl) << "Key has no private key";
199 return QByteArray();
200 }
201
202 auto keyData = privateKey.d_func()->m_keyData;
203
204 X509_REQ *req = q_X509_REQ_new();
205 if (!req) {
206 qCWarning(lcSsl) << "Failed to create CSR:" << getOpenSslError();
207 return QByteArray();
208 }
209 Deleter<X509_REQ> reqDeleter(req, q_X509_REQ_free);
210
211 if (!q_X509_REQ_set_version(x: req, version: 0 /* version */)) {
212 qCWarning(lcSsl) << "Failed to set CSR version:" << getOpenSslError();
213 return QByteArray();
214 }
215
216 X509_NAME *subj = q_X509_REQ_get_subject_name(req);
217 if (!subj) {
218 qCWarning(lcSsl) << "Invalid subject pointer";
219 return QByteArray();
220 }
221
222 if (!setSubjectName(subject: subj, dn: m_subject)) {
223 qCWarning(lcSsl) << "Failed to set subject";
224 return QByteArray();
225 }
226
227 if (m_extensions.size() > 0) {
228 auto exts = q_sk_X509_EXTENSION_new_null();
229
230 for (auto extension : std::as_const(t&: m_extensions)) {
231 auto ex = createExtension(extension);
232 if (ex)
233 q_sk_X509_EXTENSION_push(exts, ex); // returns void
234 }
235 if (q_X509_REQ_add_extensions(req, exts: (STACK_OF(X509_EXTENSION) *)exts) == 0) {
236 qCWarning(lcSsl) << "Failed to add X509 extensions";
237 return QByteArray();
238 }
239 q_sk_X509_EXTENSION_pop_free(exts, (void(*)(void*))q_X509_EXTENSION_free); // frees the whole stack, returns void
240 } // end of for loop
241
242 if (!q_X509_REQ_set_pubkey(x: req, pkey: keyData)) {
243 qCWarning(lcSsl) << "Failed to set public key:" << getOpenSslError();
244 return QByteArray();
245 }
246
247 const EVP_MD *digest = nullptr;
248 if (m_messageDigest == QOpcUaX509CertificateSigningRequest::MessageDigest::SHA256)
249 digest = q_EVP_sha256();
250
251 if (!digest) {
252 qCWarning(lcSsl) << "Invalid message digest";
253 return QByteArray();
254 }
255
256 if (q_X509_REQ_sign(x: req, pkey: keyData, md: digest) <= 0) {
257 qCWarning(lcSsl) << "Failed to sign CSR:" << getOpenSslError();
258 return QByteArray();
259 }
260
261 BIO *bio = q_BIO_new(a: q_BIO_s_mem());
262 if (!bio) {
263 qCWarning(lcSsl) << "Failed to allocate a buffer:" << getOpenSslError();
264 return QByteArray();
265 }
266 Deleter<BIO> bioDeleter(bio, q_BIO_free_all);
267
268 int result = 0;
269
270 if (m_encoding == QOpcUaX509CertificateSigningRequest::Encoding::PEM) {
271 // Some CAs require to use q_PEM_write_bio_X509_REW_NEW
272 result = q_PEM_write_bio_X509_REQ(bp: bio, x: req);
273 } else if (m_encoding == QOpcUaX509CertificateSigningRequest::Encoding::DER) {
274 result = q_i2d_X509_REQ_bio(bp: bio, req);
275 }
276 if (result != 1) {
277 qCWarning(lcSsl) << "Failed to export certificate request";
278 return QByteArray();
279 }
280
281 char *buf;
282 int length = q_BIO_get_mem_data(bio, &buf);
283 QByteArray data(buf, length);
284 return data;
285}
286
287QByteArray QOpcUaX509CertificateSigningRequestPrivate::createSelfSignedCertificate(const QOpcUaKeyPair &privateKey, int validityInDays)
288{
289 if (!privateKey.hasPrivateKey()) {
290 qCWarning(lcSsl) << "Key has no private key";
291 return QByteArray();
292 }
293
294 auto keyData = privateKey.d_func()->m_keyData;
295
296 X509 *x509 = q_X509_new();
297 if (!x509)
298 return QByteArray();
299
300 Deleter<X509> x509Deleter(x509, q_X509_free);
301
302 if (!q_X509_set_version(x: x509, version: 2 /* version */)) {
303 qCWarning(lcSsl) << "Failed to set version";
304 return QByteArray();
305 }
306 q_X509_gmtime_adj(s: q_X509_getm_notBefore(a: x509), adj: 0); // current time
307 q_X509_gmtime_adj(s: q_X509_getm_notAfter(a: x509), adj: (long)60 * 60 * 24 * validityInDays);
308
309 if (!q_X509_set_pubkey(x: x509, key: keyData)) {
310 qCWarning(lcSsl) << "Failed to set public key:" << getOpenSslError();
311 return QByteArray();
312 }
313
314 X509_NAME *subj = q_X509_get_subject_name(a: x509);
315 if (!subj) {
316 qCWarning(lcSsl) << "Invalid subject pointer";
317 return QByteArray();
318 }
319
320 if (!setSubjectName(subject: subj, dn: m_subject)) {
321 qCWarning(lcSsl) << "Failed to set subject";
322 return QByteArray();
323 }
324
325 X509_NAME *issuer = q_X509_get_issuer_name(a: x509);
326 if (!issuer) {
327 qCWarning(lcSsl) << "Invalid issuer pointer";
328 return QByteArray();
329 }
330
331 if (!setSubjectName(subject: issuer, dn: m_subject)) {
332 qCWarning(lcSsl) << "Failed to set issuer";
333 return QByteArray();
334 }
335
336 for (auto extension : std::as_const(t&: m_extensions)) {
337 auto ex = createExtension(extension);
338 if (ex) {
339 if (!q_X509_add_ext(x: x509, ex, location: -1)) {
340 qCWarning(lcSsl) << "Failed to add extension";
341 return QByteArray();
342 }
343 q_X509_EXTENSION_free(ext: ex);
344 } else {
345 qCWarning(lcSsl) << "Invalid extension";
346 return QByteArray();
347 }
348 }
349
350 // Hash of public key
351 unsigned char publicKeyHash[SHA_DIGEST_LENGTH];
352 unsigned int len;
353 if (!q_X509_pubkey_digest(data: x509, type: q_EVP_sha1(), md: publicKeyHash, len: &len)) {
354 qCWarning(lcSsl) << "Failed to hash public key";
355 return QByteArray();
356 }
357
358 // Set subject key identifier
359 ASN1_OCTET_STRING *subjectKeyIdentifier = q_ASN1_OCTET_STRING_new();
360 if (!subjectKeyIdentifier) {
361 qCWarning(lcSsl) << "Failed to allocate ASN1 string";
362 return QByteArray();
363 }
364 Deleter<ASN1_OCTET_STRING> subjectKeyIdentifierDeleter(subjectKeyIdentifier, q_ASN1_OCTET_STRING_free);
365
366 if (!q_ASN1_OCTET_STRING_set(str: subjectKeyIdentifier, data: publicKeyHash, SHA_DIGEST_LENGTH)) {
367 qCWarning(lcSsl) << "Failed set ASN1 string";
368 return QByteArray();
369 }
370
371 if (!q_X509_add1_ext_i2d(x: x509, NID_subject_key_identifier, value: subjectKeyIdentifier, crit: 0, X509V3_ADD_DEFAULT)) {
372 qCWarning(lcSsl) << "Failed to add subject key identifier extension";
373 return QByteArray();
374 }
375
376 // Set serial number
377 unsigned char subjHash[SHA_DIGEST_LENGTH];
378 unsigned char finalHash[SHA_DIGEST_LENGTH];
379
380 if (!q_X509_NAME_digest(data: subj, type: q_EVP_sha1(), md: subjHash, len: &len)) {
381 qCWarning(lcSsl) << "failed";
382 return QByteArray();
383 }
384 for (unsigned int i = 0; i < len; i++)
385 finalHash[i] = subjHash[i] ^ publicKeyHash[i];
386
387 ASN1_INTEGER *serial_num = q_ASN1_INTEGER_new();
388 if (!serial_num) {
389 qCWarning(lcSsl) << "Failed to allocate ASN1 integer";
390 return QByteArray();
391 }
392 Deleter<ASN1_OCTET_STRING> serial_numDeleter(serial_num, q_ASN1_INTEGER_free);
393
394 if (!q_ASN1_OCTET_STRING_set(str: serial_num, data: finalHash, len)) {
395 qCWarning(lcSsl) << "Failed to set ASN1 integer";
396 return QByteArray();
397 }
398 if (!q_X509_set_serialNumber(x: x509, serial: serial_num)) {
399 qCWarning(lcSsl) << "Failed to set serial number";
400 return QByteArray();
401 }
402
403 // Set authority key identifier
404 AUTHORITY_KEYID *akid = q_AUTHORITY_KEYID_new();
405 if (!akid) {
406 qCWarning(lcSsl) << "Failed to allocate authority key id";
407 return QByteArray();
408 }
409 Deleter<AUTHORITY_KEYID> akidDeleter(akid, q_AUTHORITY_KEYID_free);
410
411 akid->issuer = q_GENERAL_NAMES_new();
412 if (!akid->issuer) {
413 qCWarning(lcSsl) << "Failed to set authority key id";
414 return QByteArray();
415 }
416
417 GENERAL_NAME *generalName = q_GENERAL_NAME_new();
418 if (!generalName) {
419 qCWarning(lcSsl) << "Failed to set authority key id";
420 return QByteArray();
421 }
422 generalName->type = GEN_DIRNAME;
423 generalName->d.directoryName = q_X509_NAME_dup(xn: q_X509_get_subject_name(a: x509));
424 q_sk_GENERAL_NAME_push((OPENSSL_STACK*)akid->issuer, generalName);
425 akid->keyid = (ASN1_OCTET_STRING*)q_X509_get_ext_d2i(a: x509, NID_subject_key_identifier, NULL, NULL);
426 akid->serial = q_ASN1_INTEGER_dup(x: q_X509_get_serialNumber(a: x509));
427
428 if (!q_X509_add1_ext_i2d(x: x509, NID_authority_key_identifier, value: akid, crit: 0, X509V3_ADD_DEFAULT)) {
429 qCWarning(lcSsl) << "Failed to add authority key id extension";
430 return QByteArray();
431 }
432
433 const EVP_MD *digest = nullptr;
434 if (m_messageDigest == QOpcUaX509CertificateSigningRequest::MessageDigest::SHA256)
435 digest = q_EVP_sha256();
436
437 if (!digest) {
438 qCWarning(lcSsl) << "Invalid message digest";
439 return QByteArray();
440 }
441
442 if (q_X509_sign(x: x509, key: keyData, md: digest) <= 0) {
443 qCWarning(lcSsl) << "Failed to sign certificate:" << getOpenSslError();
444 return QByteArray();
445 }
446
447 BIO *bio = q_BIO_new(a: q_BIO_s_mem());
448 if (!bio) {
449 qCWarning(lcSsl) << "Failed to allocate a buffer:" << getOpenSslError();
450 return QByteArray();
451 }
452 Deleter<BIO> bioDeleter(bio, q_BIO_free_all);
453
454 int result = 0;
455
456 if (m_encoding == QOpcUaX509CertificateSigningRequest::Encoding::PEM) {
457 result = q_PEM_write_bio_X509(bp: bio, x: x509);
458 } else if (m_encoding == QOpcUaX509CertificateSigningRequest::Encoding::DER) {
459 result = q_i2d_X509_bio(bp: bio, x509);
460 }
461 if (result != 1) {
462 qCWarning(lcSsl) << "Failed to export certificate";
463 return QByteArray();
464 }
465
466 char *buf;
467 int length = q_BIO_get_mem_data(bio, &buf);
468 QByteArray data(buf, length);
469 return data;
470}
471
472QT_END_NAMESPACE
473
474

source code of qtopcua/src/opcua/x509/qopcuax509certificatesigningrequest_openssl.cpp