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 "qopcuakeypair_p.h" |
5 | #include "openssl_symbols_p.h" |
6 | #include "qopcuax509utils_p.h" |
7 | |
8 | QT_BEGIN_NAMESPACE |
9 | |
10 | QOpcUaKeyPairPrivate::QOpcUaKeyPairPrivate() |
11 | : QObjectPrivate() |
12 | { |
13 | if (!q_resolveOpenSslSymbols()) |
14 | qFatal(msg: "Failed to resolve symbols" ); |
15 | |
16 | q_ERR_load_crypto_strings(); |
17 | #if OPENSSL_VERSION_NUMBER < 0x10100000L |
18 | q_OPENSSL_add_all_algorithms_noconf(); |
19 | #endif |
20 | } |
21 | |
22 | QOpcUaKeyPairPrivate::~QOpcUaKeyPairPrivate() |
23 | { |
24 | if (m_keyData) { |
25 | q_EVP_PKEY_free(a: m_keyData); |
26 | m_keyData = nullptr; |
27 | } |
28 | } |
29 | |
30 | static int passwordCallback(char *passphraseBuffer, int maximumPassphraseSize, int writeOperation, void *userData) { |
31 | QOpcUaKeyPair *source = reinterpret_cast<QOpcUaKeyPair*>(userData); |
32 | QString passphrase; |
33 | source->passphraseNeeded(passphrase, maximumLength: maximumPassphraseSize, writeOperation: writeOperation == 1); |
34 | |
35 | if (passphrase.isEmpty()) |
36 | return -1; |
37 | |
38 | memcpy(dest: passphraseBuffer, src: passphrase.toUtf8().constData(), n: qMin(a: maximumPassphraseSize, b: passphrase.size())); |
39 | return passphrase.size(); |
40 | } |
41 | |
42 | bool QOpcUaKeyPairPrivate::loadFromPemData(const QByteArray &data) { |
43 | Q_Q(QOpcUaKeyPair); |
44 | |
45 | if (m_keyData) { |
46 | q_EVP_PKEY_free(a: m_keyData); |
47 | m_keyData = nullptr; |
48 | } |
49 | m_hasPrivateKey = false; |
50 | |
51 | BIO *bio = q_BIO_new_mem_buf(a: (void *)data.constData(), b: data.size()); |
52 | if (!bio) { |
53 | qCWarning(lcSsl) << "Failed to allocate a buffer:" << getOpenSslError(); |
54 | return false; |
55 | } |
56 | Deleter<BIO> bioDeleter(bio, q_BIO_free_all); |
57 | |
58 | if (data.startsWith(bv: "-----BEGIN PRIVATE KEY-----" ) || data.startsWith(bv: "-----BEGIN ENCRYPTED PRIVATE KEY-----" )) { |
59 | if (!q_PEM_read_bio_PrivateKey(a: bio, b: &m_keyData, c: &passwordCallback, d: q /* userData */)) { |
60 | qCWarning(lcSsl) << "Failed to load private key:" << getOpenSslError(); |
61 | return false; |
62 | } |
63 | m_hasPrivateKey = true; |
64 | } else { |
65 | if (!q_PEM_read_bio_PUBKEY(a: bio, b: &m_keyData, NULL, NULL)) { |
66 | qCWarning(lcSsl) << "Failed to load public key:" << getOpenSslError(); |
67 | return false; |
68 | } |
69 | } |
70 | |
71 | return true; |
72 | } |
73 | |
74 | QByteArray QOpcUaKeyPairPrivate::publicKeyToByteArray() const |
75 | { |
76 | if (!m_keyData) { |
77 | qCWarning(lcSsl) << "No public key to write" ; |
78 | return QByteArray(); |
79 | } |
80 | |
81 | BIO *bio = q_BIO_new(a: q_BIO_s_mem()); |
82 | if (!bio) { |
83 | qCWarning(lcSsl) << "Failed to allocate a buffer:" << getOpenSslError(); |
84 | return QByteArray(); |
85 | } |
86 | Deleter<BIO> bioDeleter(bio, q_BIO_free_all); |
87 | |
88 | if (0 == q_PEM_write_bio_PUBKEY(a: bio, b: m_keyData)) { |
89 | qCWarning(lcSsl) << "Failed to write public key:" << getOpenSslError(); |
90 | return QByteArray(); |
91 | } |
92 | |
93 | char *buf; |
94 | int length = q_BIO_get_mem_data(bio, &buf); |
95 | QByteArray data(buf, length); |
96 | return data; |
97 | } |
98 | |
99 | bool QOpcUaKeyPairPrivate::generateRsaKey(QOpcUaKeyPair::RsaKeyStrength strength) |
100 | { |
101 | if (m_keyData) { |
102 | q_EVP_PKEY_free(a: m_keyData); |
103 | m_keyData = nullptr; |
104 | } |
105 | |
106 | #if OPENSSL_VERSION_NUMBER >= 0x10100000L |
107 | EVP_PKEY_CTX *ctx = q_EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, e: nullptr); |
108 | if (!ctx) { |
109 | qCWarning(lcSsl) << "Failed to allocate context:" << getOpenSslError(); |
110 | return false; |
111 | } |
112 | Deleter<EVP_PKEY_CTX> ctxDeleter(ctx, q_EVP_PKEY_CTX_free); |
113 | |
114 | if (q_EVP_PKEY_keygen_init(ctx) <= 0) { |
115 | qCWarning(lcSsl) << "Failed to initialize context:" << getOpenSslError(); |
116 | return false; |
117 | } |
118 | |
119 | if (q_EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, static_cast<int>(strength)) <= 0) { |
120 | qCWarning(lcSsl) << "Failed to set context property:" << getOpenSslError(); |
121 | return false; |
122 | } |
123 | |
124 | if (q_EVP_PKEY_keygen(ctx, pkey: &m_keyData) <= 0) { |
125 | qCWarning(lcSsl) << "Failed to generate key:" << getOpenSslError(); |
126 | return false; |
127 | } |
128 | |
129 | #else |
130 | RSA *rsa; |
131 | BIGNUM *publicExponent; |
132 | |
133 | publicExponent = q_BN_new(); |
134 | if (publicExponent == NULL) { |
135 | qCWarning(lcSsl) << "Failed to allocate public exponent:" << getOpenSslError(); |
136 | return false; |
137 | } |
138 | Deleter<BIGNUM> publicExponentDeleter(publicExponent, q_BN_free); |
139 | |
140 | if (q_BN_set_word(publicExponent, RSA_F4) == 0) { |
141 | qCWarning(lcSsl) << "Failed to set public exponent:" << getOpenSslError(); |
142 | return false; |
143 | } |
144 | |
145 | rsa = q_RSA_new(); |
146 | if (rsa == NULL) { |
147 | qCWarning(lcSsl) << "Failed to allocate RSA:" << getOpenSslError(); |
148 | return false; |
149 | } |
150 | Deleter<RSA> rsaDeleter(rsa, q_RSA_free); |
151 | |
152 | int result = q_RSA_generate_key_ex(rsa, static_cast<int>(strength), publicExponent, nullptr /* progress callback */); |
153 | if (result == 0) { |
154 | qCWarning(lcSsl) << "Failed to generate key:" << getOpenSslError(); |
155 | return false; |
156 | } |
157 | |
158 | m_keyData = q_EVP_PKEY_new(); |
159 | if (!m_keyData) { |
160 | qCWarning(lcSsl) << "Failed to allocate key data:" << getOpenSslError(); |
161 | return false; |
162 | } |
163 | |
164 | if (!q_EVP_PKEY_set1_RSA(m_keyData, rsa)) { |
165 | qCWarning(lcSsl) << "Failed to transfer key data:" << getOpenSslError(); |
166 | return false; |
167 | } |
168 | #endif |
169 | m_hasPrivateKey = true; |
170 | return true; |
171 | } |
172 | |
173 | QOpcUaKeyPair::KeyType QOpcUaKeyPairPrivate::keyType() const |
174 | { |
175 | if (!m_keyData) |
176 | return QOpcUaKeyPair::KeyType::Empty; |
177 | switch (q_EVP_PKEY_base_id(a: m_keyData)) { |
178 | case EVP_PKEY_RSA: |
179 | return QOpcUaKeyPair::KeyType::Rsa; |
180 | default: |
181 | return QOpcUaKeyPair::KeyType::Unknown; |
182 | } |
183 | } |
184 | |
185 | QByteArray QOpcUaKeyPairPrivate::privateKeyToByteArray(QOpcUaKeyPair::Cipher cipher, const QString &password) const |
186 | { |
187 | if (!m_keyData) { |
188 | qCWarning(lcSsl) << "No private key to write" ; |
189 | return QByteArray(); |
190 | } |
191 | |
192 | BIO *bio = q_BIO_new(a: q_BIO_s_mem()); |
193 | if (!bio) { |
194 | qCWarning(lcSsl) << "Failed to allocate a buffer:" << getOpenSslError(); |
195 | return QByteArray(); |
196 | } |
197 | Deleter<BIO> bioDeleter(bio, q_BIO_free_all); |
198 | |
199 | const EVP_CIPHER *enc = NULL; |
200 | if (cipher == QOpcUaKeyPair::Cipher::Unencrypted) |
201 | enc = NULL; |
202 | else if (cipher == QOpcUaKeyPair::Cipher::Aes128Cbc) |
203 | enc = q_EVP_aes_128_cbc(); |
204 | else { |
205 | qCWarning(lcSsl) << "Unknown cipher given" ; |
206 | return QByteArray(); |
207 | } |
208 | |
209 | if (0 == q_PEM_write_bio_PKCS8PrivateKey(a: bio, b: m_keyData, c: enc, |
210 | d: enc ? password.toUtf8().data() : NULL, |
211 | e: enc ? password.size() : 0, |
212 | NULL /* callback */, NULL /* userdata */)) { |
213 | qCWarning(lcSsl) << "Failed to write private key:" << getOpenSslError(); |
214 | return QByteArray(); |
215 | } |
216 | |
217 | char *buf; |
218 | int length = q_BIO_get_mem_data(bio, &buf); |
219 | QByteArray data(buf, length); |
220 | return data; |
221 | } |
222 | |
223 | bool QOpcUaKeyPairPrivate::hasPrivateKey() const |
224 | { |
225 | if (!m_keyData) |
226 | return false; |
227 | |
228 | return m_hasPrivateKey; |
229 | } |
230 | |
231 | QT_END_NAMESPACE |
232 | |