Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsauthcertutils.cpp
3 : : ---------------------
4 : : begin : May 1, 2015
5 : : copyright : (C) 2015 by Boundless Spatial, Inc. USA
6 : : author : Larry Shaffer
7 : : email : lshaffer at boundlessgeo dot com
8 : : ***************************************************************************
9 : : * *
10 : : * This program is free software; you can redistribute it and/or modify *
11 : : * it under the terms of the GNU General Public License as published by *
12 : : * the Free Software Foundation; either version 2 of the License, or *
13 : : * (at your option) any later version. *
14 : : * *
15 : : ***************************************************************************/
16 : :
17 : : #include "qgsauthcertutils.h"
18 : :
19 : : #include <QColor>
20 : : #include <QDir>
21 : : #include <QFile>
22 : : #include <QObject>
23 : : #include <QSslCertificate>
24 : : #include <QUuid>
25 : :
26 : : #include "qgsapplication.h"
27 : : #include "qgsauthmanager.h"
28 : : #include "qgslogger.h"
29 : :
30 : : #ifdef Q_OS_MAC
31 : : #include <string.h>
32 : : #include "libtasn1.h"
33 : : #endif
34 : :
35 : :
36 : 0 : QString QgsAuthCertUtils::getSslProtocolName( QSsl::SslProtocol protocol )
37 : : {
38 : 0 : switch ( protocol )
39 : : {
40 : : case QSsl::SecureProtocols:
41 : 0 : return QObject::tr( "SecureProtocols" );
42 : : #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
43 : : case QSsl::TlsV1SslV3:
44 : : return QObject::tr( "TlsV1SslV3" );
45 : : #endif
46 : : case QSsl::TlsV1_0:
47 : 0 : return QObject::tr( "TlsV1" );
48 : : #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
49 : : // not supported by Qt 5.15+
50 : : case QSsl::SslV3:
51 : : return QObject::tr( "SslV3" );
52 : : case QSsl::SslV2:
53 : : return QObject::tr( "SslV2" );
54 : : #endif
55 : : default:
56 : 0 : return QString();
57 : : }
58 : 0 : }
59 : :
60 : 0 : QMap<QString, QSslCertificate> QgsAuthCertUtils::mapDigestToCerts( const QList<QSslCertificate> &certs )
61 : : {
62 : 0 : QMap<QString, QSslCertificate> digestmap;
63 : 0 : for ( const auto &cert : certs )
64 : : {
65 : 0 : digestmap.insert( shaHexForCert( cert ), cert );
66 : : }
67 : 0 : return digestmap;
68 : 0 : }
69 : :
70 : 0 : QMap<QString, QList<QSslCertificate> > QgsAuthCertUtils::certsGroupedByOrg( const QList<QSslCertificate> &certs )
71 : : {
72 : 0 : QMap< QString, QList<QSslCertificate> > orgcerts;
73 : 0 : for ( const auto &cert : certs )
74 : : {
75 : 0 : QString org( SSL_SUBJECT_INFO( cert, QSslCertificate::Organization ) );
76 : 0 : if ( org.isEmpty() )
77 : 0 : org = QStringLiteral( "(Organization not defined)" );
78 : 0 : QList<QSslCertificate> valist = orgcerts.contains( org ) ? orgcerts.value( org ) : QList<QSslCertificate>();
79 : 0 : orgcerts.insert( org, valist << cert );
80 : 0 : }
81 : 0 : return orgcerts;
82 : 0 : }
83 : :
84 : 0 : QMap<QString, QgsAuthConfigSslServer> QgsAuthCertUtils::mapDigestToSslConfigs( const QList<QgsAuthConfigSslServer> &configs )
85 : : {
86 : 0 : QMap<QString, QgsAuthConfigSslServer> digestmap;
87 : 0 : for ( const auto &config : configs )
88 : : {
89 : 0 : digestmap.insert( shaHexForCert( config.sslCertificate() ), config );
90 : : }
91 : 0 : return digestmap;
92 : 0 : }
93 : :
94 : 0 : QMap<QString, QList<QgsAuthConfigSslServer> > QgsAuthCertUtils::sslConfigsGroupedByOrg( const QList<QgsAuthConfigSslServer> &configs )
95 : : {
96 : 0 : QMap< QString, QList<QgsAuthConfigSslServer> > orgconfigs;
97 : 0 : for ( const auto &config : configs )
98 : : {
99 : 0 : QString org( SSL_SUBJECT_INFO( config.sslCertificate(), QSslCertificate::Organization ) );
100 : :
101 : 0 : if ( org.isEmpty() )
102 : 0 : org = QObject::tr( "(Organization not defined)" );
103 : 0 : QList<QgsAuthConfigSslServer> valist = orgconfigs.contains( org ) ? orgconfigs.value( org ) : QList<QgsAuthConfigSslServer>();
104 : 0 : orgconfigs.insert( org, valist << config );
105 : 0 : }
106 : 0 : return orgconfigs;
107 : 0 : }
108 : :
109 : 0 : QByteArray QgsAuthCertUtils::fileData( const QString &path )
110 : : {
111 : 0 : QByteArray data;
112 : 0 : QFile file( path );
113 : 0 : if ( !file.exists() )
114 : : {
115 : 0 : QgsDebugMsg( QStringLiteral( "Read file error, file not found: %1" ).arg( path ) );
116 : 0 : return data;
117 : : }
118 : : // TODO: add checks for locked file, etc., to ensure it can be read
119 : 0 : QFile::OpenMode openflags( QIODevice::ReadOnly );
120 : 0 : bool ret = file.open( openflags );
121 : 0 : if ( ret )
122 : : {
123 : 0 : data = file.readAll();
124 : 0 : }
125 : 0 : file.close();
126 : :
127 : 0 : return data;
128 : 0 : }
129 : :
130 : 0 : QList<QSslCertificate> QgsAuthCertUtils::certsFromFile( const QString &certspath )
131 : : {
132 : 0 : QList<QSslCertificate> certs;
133 : 0 : const QByteArray payload( QgsAuthCertUtils::fileData( certspath ) );
134 : 0 : certs = QSslCertificate::fromData( payload, sniffEncoding( payload ) );
135 : 0 : if ( certs.isEmpty() )
136 : : {
137 : 0 : QgsDebugMsg( QStringLiteral( "Parsed cert(s) EMPTY for path: %1" ).arg( certspath ) );
138 : 0 : }
139 : 0 : return certs;
140 : 0 : }
141 : :
142 : 0 : QList<QSslCertificate> QgsAuthCertUtils::casFromFile( const QString &certspath )
143 : : {
144 : 0 : QList<QSslCertificate> cas;
145 : 0 : const QList<QSslCertificate> certs( certsFromFile( certspath ) );
146 : 0 : for ( const auto &cert : certs )
147 : : {
148 : 0 : if ( certificateIsAuthority( cert ) )
149 : : {
150 : 0 : cas.append( cert );
151 : 0 : }
152 : : }
153 : 0 : return cas;
154 : 0 : }
155 : :
156 : 0 : QList<QSslCertificate> QgsAuthCertUtils::casMerge( const QList<QSslCertificate> &bundle1, const QList<QSslCertificate> &bundle2 )
157 : : {
158 : 0 : QStringList shas;
159 : 0 : QList<QSslCertificate> result( bundle1 );
160 : 0 : const QList<QSslCertificate> c_bundle1( bundle1 );
161 : 0 : for ( const auto &cert : c_bundle1 )
162 : : {
163 : 0 : shas.append( shaHexForCert( cert ) );
164 : : }
165 : 0 : const QList<QSslCertificate> c_bundle2( bundle2 );
166 : 0 : for ( const auto &cert : c_bundle2 )
167 : : {
168 : 0 : if ( ! shas.contains( shaHexForCert( cert ) ) )
169 : : {
170 : 0 : result.append( cert );
171 : 0 : }
172 : : }
173 : 0 : return result;
174 : 0 : }
175 : :
176 : :
177 : :
178 : 0 : QSslCertificate QgsAuthCertUtils::certFromFile( const QString &certpath )
179 : : {
180 : 0 : QSslCertificate cert;
181 : 0 : QList<QSslCertificate> certs( QgsAuthCertUtils::certsFromFile( certpath ) );
182 : 0 : if ( !certs.isEmpty() )
183 : : {
184 : 0 : cert = certs.first();
185 : 0 : }
186 : 0 : if ( cert.isNull() )
187 : : {
188 : 0 : QgsDebugMsg( QStringLiteral( "Parsed cert is NULL for path: %1" ).arg( certpath ) );
189 : 0 : }
190 : 0 : return cert;
191 : 0 : }
192 : :
193 : 0 : QSslKey QgsAuthCertUtils::keyFromFile( const QString &keypath,
194 : : const QString &keypass,
195 : : QString *algtype )
196 : : {
197 : : // The approach here is to try all possible encodings and algorithms
198 : 0 : QByteArray keydata( QgsAuthCertUtils::fileData( keypath ) );
199 : 0 : QSslKey clientkey;
200 : :
201 : 0 : QSsl::EncodingFormat keyEncoding( sniffEncoding( keydata ) );
202 : :
203 : 0 : const std::vector<QSsl::KeyAlgorithm> algs
204 : 0 : {
205 : : QSsl::KeyAlgorithm::Rsa,
206 : : QSsl::KeyAlgorithm::Dsa,
207 : : QSsl::KeyAlgorithm::Ec,
208 : : QSsl::KeyAlgorithm::Opaque
209 : : };
210 : :
211 : 0 : for ( const auto &alg : algs )
212 : : {
213 : 0 : clientkey = QSslKey( keydata,
214 : 0 : alg,
215 : 0 : keyEncoding,
216 : : QSsl::PrivateKey,
217 : 0 : !keypass.isEmpty() ? keypass.toUtf8() : QByteArray() );
218 : 0 : if ( ! clientkey.isNull() )
219 : : {
220 : 0 : if ( algtype )
221 : : {
222 : 0 : switch ( alg )
223 : : {
224 : : case QSsl::KeyAlgorithm::Rsa:
225 : 0 : *algtype = QStringLiteral( "rsa" );
226 : 0 : break;
227 : : case QSsl::KeyAlgorithm::Dsa:
228 : 0 : *algtype = QStringLiteral( "dsa" );
229 : 0 : break;
230 : : case QSsl::KeyAlgorithm::Ec:
231 : 0 : *algtype = QStringLiteral( "ec" );
232 : 0 : break;
233 : : case QSsl::KeyAlgorithm::Opaque:
234 : 0 : *algtype = QStringLiteral( "opaque" );
235 : 0 : break;
236 : : #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
237 : : case QSsl::KeyAlgorithm::Dh:
238 : 0 : *algtype = QStringLiteral( "dh" );
239 : 0 : break;
240 : : #endif
241 : : }
242 : 0 : }
243 : 0 : return clientkey;
244 : : }
245 : : }
246 : 0 : return QSslKey();
247 : 0 : }
248 : :
249 : 0 : QList<QSslCertificate> QgsAuthCertUtils::certsFromString( const QString &pemtext )
250 : : {
251 : 0 : QList<QSslCertificate> certs;
252 : 0 : certs = QSslCertificate::fromData( pemtext.toLatin1(), QSsl::Pem );
253 : 0 : if ( certs.isEmpty() )
254 : : {
255 : 0 : QgsDebugMsg( QStringLiteral( "Parsed cert(s) EMPTY" ) );
256 : 0 : }
257 : 0 : return certs;
258 : 0 : }
259 : :
260 : 0 : QList<QSslCertificate> QgsAuthCertUtils::casRemoveSelfSigned( const QList<QSslCertificate> &caList )
261 : : {
262 : 0 : QList<QSslCertificate> certs;
263 : 0 : for ( const auto &cert : caList )
264 : : {
265 : 0 : if ( ! cert.isSelfSigned( ) )
266 : : {
267 : 0 : certs.append( cert );
268 : 0 : }
269 : : }
270 : 0 : return certs;
271 : 0 : }
272 : :
273 : 0 : QStringList QgsAuthCertUtils::certKeyBundleToPem( const QString &certpath,
274 : : const QString &keypath,
275 : : const QString &keypass,
276 : : bool reencrypt )
277 : : {
278 : 0 : QString certpem;
279 : 0 : QSslCertificate clientcert = QgsAuthCertUtils::certFromFile( certpath );
280 : 0 : if ( !clientcert.isNull() )
281 : : {
282 : 0 : certpem = QString( clientcert.toPem() );
283 : 0 : }
284 : :
285 : 0 : QString keypem;
286 : 0 : QString algtype;
287 : 0 : QSslKey clientkey = QgsAuthCertUtils::keyFromFile( keypath, keypass, &algtype );
288 : :
289 : : // reapply passphrase if protection is requested and passphrase exists
290 : 0 : if ( !clientkey.isNull() )
291 : : {
292 : 0 : keypem = QString( clientkey.toPem( ( reencrypt && !keypass.isEmpty() ) ? keypass.toUtf8() : QByteArray() ) );
293 : 0 : }
294 : :
295 : 0 : return QStringList() << certpem << keypem << algtype;
296 : 0 : }
297 : :
298 : 0 : bool QgsAuthCertUtils::pemIsPkcs8( const QString &keyPemTxt )
299 : : {
300 : 0 : QString pkcs8Header = QStringLiteral( "-----BEGIN PRIVATE KEY-----" );
301 : 0 : QString pkcs8Footer = QStringLiteral( "-----END PRIVATE KEY-----" );
302 : 0 : return keyPemTxt.contains( pkcs8Header ) && keyPemTxt.contains( pkcs8Footer );
303 : 0 : }
304 : :
305 : : #ifdef Q_OS_MAC
306 : : QByteArray QgsAuthCertUtils::pkcs8PrivateKey( QByteArray &pkcs8Der )
307 : : {
308 : : QByteArray pkcs1;
309 : :
310 : : if ( pkcs8Der.isEmpty() )
311 : : {
312 : : QgsDebugMsg( QStringLiteral( "ERROR, passed DER is empty" ) );
313 : : return pkcs1;
314 : : }
315 : : // Dump as unarmored PEM format, e.g. missing '-----BEGIN|END...' wrapper
316 : : //QgsDebugMsg ( QStringLiteral( "pkcs8Der: %1" ).arg( QString( pkcs8Der.toBase64() ) ) );
317 : :
318 : : QFileInfo asnDefsRsrc( QgsApplication::pkgDataPath() + QStringLiteral( "/resources/pkcs8.asn" ) );
319 : : if ( ! asnDefsRsrc.exists() )
320 : : {
321 : : QgsDebugMsg( QStringLiteral( "ERROR, pkcs.asn resource file not found: %1" ).arg( asnDefsRsrc.filePath() ) );
322 : : return pkcs1;
323 : : }
324 : : const char *asnDefsFile = asnDefsRsrc.absoluteFilePath().toLocal8Bit().constData();
325 : :
326 : : int asn1_result = ASN1_SUCCESS, der_len = 0, oct_len = 0;
327 : : asn1_node definitions = NULL, structure = NULL;
328 : : char errorDescription[ASN1_MAX_ERROR_DESCRIPTION_SIZE], oct_data[1024];
329 : : unsigned char *der = NULL;
330 : : unsigned int flags = 0; //TODO: see if any or all ASN1_DECODE_FLAG_* flags can be set
331 : : unsigned oct_etype;
332 : :
333 : : // Base PKCS#8 element to decode
334 : : QString typeName( QStringLiteral( "PKCS-8.PrivateKeyInfo" ) );
335 : :
336 : : asn1_result = asn1_parser2tree( asnDefsFile, &definitions, errorDescription );
337 : :
338 : : switch ( asn1_result )
339 : : {
340 : : case ASN1_SUCCESS:
341 : : QgsDebugMsgLevel( QStringLiteral( "Parse: done.\n" ), 4 );
342 : : break;
343 : : case ASN1_FILE_NOT_FOUND:
344 : : QgsDebugMsg( QStringLiteral( "ERROR, file not found: %1" ).arg( asnDefsFile ) );
345 : : return pkcs1;
346 : : case ASN1_SYNTAX_ERROR:
347 : : case ASN1_IDENTIFIER_NOT_FOUND:
348 : : case ASN1_NAME_TOO_LONG:
349 : : QgsDebugMsg( QStringLiteral( "ERROR, asn1 parsing: %1" ).arg( errorDescription ) );
350 : : return pkcs1;
351 : : default:
352 : : QgsDebugMsg( QStringLiteral( "ERROR, libtasn1: %1" ).arg( asn1_strerror( asn1_result ) ) );
353 : : return pkcs1;
354 : : }
355 : :
356 : : // Generate the ASN.1 structure
357 : : asn1_result = asn1_create_element( definitions, typeName.toLatin1().constData(), &structure );
358 : :
359 : : //asn1_print_structure( stdout, structure, "", ASN1_PRINT_ALL);
360 : :
361 : : if ( asn1_result != ASN1_SUCCESS )
362 : : {
363 : : QgsDebugMsg( QStringLiteral( "ERROR, structure creation: %1" ).arg( asn1_strerror( asn1_result ) ) );
364 : : goto PKCS1DONE;
365 : : }
366 : :
367 : : // Populate the ASN.1 structure with decoded DER data
368 : : der = reinterpret_cast<unsigned char *>( pkcs8Der.data() );
369 : : der_len = pkcs8Der.size();
370 : :
371 : : if ( flags != 0 )
372 : : {
373 : : asn1_result = asn1_der_decoding2( &structure, der, &der_len, flags, errorDescription );
374 : : }
375 : : else
376 : : {
377 : : asn1_result = asn1_der_decoding( &structure, der, der_len, errorDescription );
378 : : }
379 : :
380 : : if ( asn1_result != ASN1_SUCCESS )
381 : : {
382 : : QgsDebugMsg( QStringLiteral( "ERROR, decoding: %1" ).arg( errorDescription ) );
383 : : goto PKCS1DONE;
384 : : }
385 : : else
386 : : {
387 : : QgsDebugMsgLevel( QStringLiteral( "Decoding: %1" ).arg( asn1_strerror( asn1_result ) ), 4 );
388 : : }
389 : :
390 : : if ( QgsLogger::debugLevel() >= 4 )
391 : : {
392 : : QgsDebugMsg( QStringLiteral( "DECODING RESULT:" ) );
393 : : asn1_print_structure( stdout, structure, "", ASN1_PRINT_NAME_TYPE_VALUE );
394 : : }
395 : :
396 : : // Validate and extract privateKey value
397 : : QgsDebugMsgLevel( QStringLiteral( "Validating privateKey type..." ), 4 );
398 : : typeName.append( QStringLiteral( ".privateKey" ) );
399 : : QgsDebugMsgLevel( QStringLiteral( "privateKey element name: %1" ).arg( typeName ), 4 );
400 : :
401 : : asn1_result = asn1_read_value_type( structure, "privateKey", NULL, &oct_len, &oct_etype );
402 : :
403 : : if ( asn1_result != ASN1_MEM_ERROR ) // not sure why ASN1_MEM_ERROR = success, but it does
404 : : {
405 : : QgsDebugMsg( QStringLiteral( "ERROR, asn1 read privateKey value type: %1" ).arg( asn1_strerror( asn1_result ) ) );
406 : : goto PKCS1DONE;
407 : : }
408 : :
409 : : if ( oct_etype != ASN1_ETYPE_OCTET_STRING )
410 : : {
411 : : QgsDebugMsg( QStringLiteral( "ERROR, asn1 privateKey value not octet string, but type: %1" ).arg( static_cast<int>( oct_etype ) ) );
412 : : goto PKCS1DONE;
413 : : }
414 : :
415 : : if ( oct_len == 0 )
416 : : {
417 : : QgsDebugMsg( QStringLiteral( "ERROR, asn1 privateKey octet string empty" ) );
418 : : goto PKCS1DONE;
419 : : }
420 : :
421 : : QgsDebugMsgLevel( QStringLiteral( "Reading privateKey value..." ), 4 );
422 : : asn1_result = asn1_read_value( structure, "privateKey", oct_data, &oct_len );
423 : :
424 : : if ( asn1_result != ASN1_SUCCESS )
425 : : {
426 : : QgsDebugMsg( QStringLiteral( "ERROR, asn1 read privateKey value: %1" ).arg( asn1_strerror( asn1_result ) ) );
427 : : goto PKCS1DONE;
428 : : }
429 : :
430 : : if ( oct_len == 0 )
431 : : {
432 : : QgsDebugMsg( QStringLiteral( "ERROR, asn1 privateKey value octet string empty" ) );
433 : : goto PKCS1DONE;
434 : : }
435 : :
436 : : pkcs1 = QByteArray( oct_data, oct_len );
437 : :
438 : : // !!! SENSITIVE DATA - DO NOT LEAVE UNCOMMENTED !!!
439 : : //QgsDebugMsgLevel( QStringLiteral( "privateKey octet data as PEM: %1" ).arg( QString( pkcs1.toBase64() ) ), 4 );
440 : :
441 : : PKCS1DONE:
442 : :
443 : : asn1_delete_structure( &structure );
444 : : return pkcs1;
445 : : }
446 : : #endif
447 : :
448 : 0 : QStringList QgsAuthCertUtils::pkcs12BundleToPem( const QString &bundlepath,
449 : : const QString &bundlepass,
450 : : bool reencrypt )
451 : : {
452 : 0 : QStringList empty;
453 : 0 : if ( !QCA::isSupported( "pkcs12" ) )
454 : : {
455 : 0 : QgsDebugMsg( QStringLiteral( "QCA does not support PKCS#12" ) );
456 : 0 : return empty;
457 : : }
458 : :
459 : 0 : QCA::KeyBundle bundle( QgsAuthCertUtils::qcaKeyBundle( bundlepath, bundlepass ) );
460 : 0 : if ( bundle.isNull() )
461 : : {
462 : 0 : QgsDebugMsg( QStringLiteral( "FAILED to convert PKCS#12 file to QCA key bundle: %1" ).arg( bundlepath ) );
463 : 0 : return empty;
464 : : }
465 : :
466 : 0 : QCA::SecureArray passarray;
467 : 0 : if ( reencrypt && !bundlepass.isEmpty() )
468 : : {
469 : 0 : passarray = QCA::SecureArray( bundlepass.toUtf8() );
470 : 0 : }
471 : :
472 : 0 : QString algtype;
473 : 0 : QSsl::KeyAlgorithm keyalg = QSsl::Opaque;
474 : 0 : if ( bundle.privateKey().isRSA() )
475 : : {
476 : 0 : algtype = QStringLiteral( "rsa" );
477 : 0 : keyalg = QSsl::Rsa;
478 : 0 : }
479 : 0 : else if ( bundle.privateKey().isDSA() )
480 : : {
481 : 0 : algtype = QStringLiteral( "dsa" );
482 : 0 : keyalg = QSsl::Dsa;
483 : 0 : }
484 : 0 : else if ( bundle.privateKey().isDH() )
485 : : {
486 : 0 : algtype = QStringLiteral( "dh" );
487 : 0 : }
488 : : // TODO: add support for EC keys, once QCA supports them
489 : :
490 : : // can currently only support RSA and DSA between QCA and Qt
491 : 0 : if ( keyalg == QSsl::Opaque )
492 : : {
493 : 0 : QgsDebugMsg( QStringLiteral( "FAILED to read PKCS#12 key (only RSA and DSA algorithms supported): %1" ).arg( bundlepath ) );
494 : 0 : return empty;
495 : : }
496 : :
497 : 0 : QString keyPem;
498 : : #ifdef Q_OS_MAC
499 : : if ( keyalg == QSsl::Rsa && QgsAuthCertUtils::pemIsPkcs8( bundle.privateKey().toPEM() ) )
500 : : {
501 : : QgsDebugMsgLevel( QStringLiteral( "Private key is PKCS#8: attempting conversion to PKCS#1..." ), 4 );
502 : : // if RSA, convert from PKCS#8 key to 'traditional' OpenSSL RSA format, which Qt prefers
503 : : // note: QCA uses OpenSSL, regardless of the Qt SSL backend, and 1.0.2+ OpenSSL versions return
504 : : // RSA private keys as PKCS#8, which choke Qt upon QSslKey creation
505 : :
506 : : QByteArray pkcs8Der = bundle.privateKey().toDER().toByteArray();
507 : : if ( pkcs8Der.isEmpty() )
508 : : {
509 : : QgsDebugMsg( QStringLiteral( "FAILED to convert PKCS#12 key to DER-encoded format: %1" ).arg( bundlepath ) );
510 : : return empty;
511 : : }
512 : :
513 : : QByteArray pkcs1Der = QgsAuthCertUtils::pkcs8PrivateKey( pkcs8Der );
514 : : if ( pkcs1Der.isEmpty() )
515 : : {
516 : : QgsDebugMsg( QStringLiteral( "FAILED to convert PKCS#12 key from PKCS#8 to PKCS#1: %1" ).arg( bundlepath ) );
517 : : return empty;
518 : : }
519 : :
520 : : QSslKey pkcs1Key( pkcs1Der, QSsl::Rsa, QSsl::Der, QSsl::PrivateKey );
521 : : if ( pkcs1Key.isNull() )
522 : : {
523 : : QgsDebugMsg( QStringLiteral( "FAILED to convert PKCS#12 key from PKCS#8 to PKCS#1 QSslKey: %1" ).arg( bundlepath ) );
524 : : return empty;
525 : : }
526 : : keyPem = QString( pkcs1Key.toPem( passarray.toByteArray() ) );
527 : : }
528 : : else
529 : : {
530 : : keyPem = bundle.privateKey().toPEM( passarray );
531 : : }
532 : : #else
533 : 0 : keyPem = bundle.privateKey().toPEM( passarray );
534 : : #endif
535 : :
536 : 0 : QgsDebugMsgLevel( QStringLiteral( "PKCS#12 cert as PEM:\n%1" ).arg( QString( bundle.certificateChain().primary().toPEM() ) ), 4 );
537 : : // !!! SENSITIVE DATA - DO NOT LEAVE UNCOMMENTED !!!
538 : : //QgsDebugMsgLevel( QStringLiteral( "PKCS#12 key as PEM:\n%1" ).arg( QString( keyPem ) ), 4 );
539 : :
540 : 0 : return QStringList() << bundle.certificateChain().primary().toPEM() << keyPem << algtype;
541 : 0 : }
542 : :
543 : 0 : QList<QSslCertificate> QgsAuthCertUtils::pkcs12BundleCas( const QString &bundlepath, const QString &bundlepass )
544 : : {
545 : 0 : QList<QSslCertificate> result;
546 : 0 : if ( !QCA::isSupported( "pkcs12" ) )
547 : 0 : return result;
548 : :
549 : 0 : QCA::KeyBundle bundle( QgsAuthCertUtils::qcaKeyBundle( bundlepath, bundlepass ) );
550 : 0 : if ( bundle.isNull() )
551 : 0 : return result;
552 : :
553 : 0 : const QCA::CertificateChain chain( bundle.certificateChain() );
554 : 0 : for ( const auto &cert : chain )
555 : : {
556 : 0 : if ( cert.isCA( ) )
557 : : {
558 : 0 : result.append( QSslCertificate::fromData( cert.toPEM().toLatin1() ) );
559 : 0 : }
560 : : }
561 : 0 : return result;
562 : 0 : }
563 : :
564 : 0 : QByteArray QgsAuthCertUtils::certsToPemText( const QList<QSslCertificate> &certs )
565 : : {
566 : 0 : QByteArray capem;
567 : 0 : if ( !certs.isEmpty() )
568 : : {
569 : 0 : QStringList certslist;
570 : 0 : for ( const auto &cert : certs )
571 : : {
572 : 0 : certslist << cert.toPem();
573 : : }
574 : 0 : capem = certslist.join( QLatin1Char( '\n' ) ).toLatin1(); //+ "\n";
575 : 0 : }
576 : 0 : return capem;
577 : 0 : }
578 : :
579 : 0 : QString QgsAuthCertUtils::pemTextToTempFile( const QString &name, const QByteArray &pemtext )
580 : : {
581 : 0 : QFile pemFile( QDir::tempPath() + QDir::separator() + name );
582 : 0 : QString pemFilePath( pemFile.fileName() );
583 : :
584 : 0 : if ( pemFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
585 : : {
586 : 0 : qint64 bytesWritten = pemFile.write( pemtext );
587 : 0 : if ( bytesWritten == -1 )
588 : : {
589 : 0 : QgsDebugMsg( QStringLiteral( "FAILED to write to temp PEM file: %1" ).arg( pemFilePath ) );
590 : 0 : pemFilePath.clear();
591 : 0 : }
592 : 0 : }
593 : : else
594 : : {
595 : 0 : QgsDebugMsg( QStringLiteral( "FAILED to open writing for temp PEM file: %1" ).arg( pemFilePath ) );
596 : 0 : pemFilePath.clear();
597 : : }
598 : :
599 : 0 : if ( !pemFile.setPermissions( QFile::ReadUser ) )
600 : : {
601 : 0 : QgsDebugMsg( QStringLiteral( "FAILED to set permissions on temp PEM file: %1" ).arg( pemFilePath ) );
602 : 0 : pemFilePath.clear();
603 : 0 : }
604 : :
605 : 0 : return pemFilePath;
606 : 0 : }
607 : :
608 : 0 : QString QgsAuthCertUtils::getCaSourceName( QgsAuthCertUtils::CaCertSource source, bool single )
609 : : {
610 : 0 : switch ( source )
611 : : {
612 : : case SystemRoot:
613 : 0 : return single ? QObject::tr( "System Root CA" ) : QObject::tr( "System Root Authorities" );
614 : : case FromFile:
615 : 0 : return single ? QObject::tr( "File CA" ) : QObject::tr( "Authorities from File" );
616 : : case InDatabase:
617 : 0 : return single ? QObject::tr( "Database CA" ) : QObject::tr( "Authorities in Database" );
618 : : case Connection:
619 : 0 : return single ? QObject::tr( "Connection CA" ) : QObject::tr( "Authorities from connection" );
620 : : default:
621 : 0 : return QString();
622 : : }
623 : 0 : }
624 : :
625 : 0 : QString QgsAuthCertUtils::resolvedCertName( const QSslCertificate &cert, bool issuer )
626 : : {
627 : 0 : QString name( issuer ? SSL_ISSUER_INFO( cert, QSslCertificate::CommonName )
628 : 0 : : SSL_SUBJECT_INFO( cert, QSslCertificate::CommonName ) );
629 : :
630 : 0 : if ( name.isEmpty() )
631 : 0 : name = issuer ? SSL_ISSUER_INFO( cert, QSslCertificate::OrganizationalUnitName )
632 : 0 : : SSL_SUBJECT_INFO( cert, QSslCertificate::OrganizationalUnitName );
633 : :
634 : 0 : if ( name.isEmpty() )
635 : 0 : name = issuer ? SSL_ISSUER_INFO( cert, QSslCertificate::Organization )
636 : 0 : : SSL_SUBJECT_INFO( cert, QSslCertificate::Organization );
637 : :
638 : 0 : if ( name.isEmpty() )
639 : 0 : name = issuer ? SSL_ISSUER_INFO( cert, QSslCertificate::LocalityName )
640 : 0 : : SSL_SUBJECT_INFO( cert, QSslCertificate::LocalityName );
641 : :
642 : 0 : if ( name.isEmpty() )
643 : 0 : name = issuer ? SSL_ISSUER_INFO( cert, QSslCertificate::StateOrProvinceName )
644 : 0 : : SSL_SUBJECT_INFO( cert, QSslCertificate::StateOrProvinceName );
645 : :
646 : 0 : if ( name.isEmpty() )
647 : 0 : name = issuer ? SSL_ISSUER_INFO( cert, QSslCertificate::CountryName )
648 : 0 : : SSL_SUBJECT_INFO( cert, QSslCertificate::CountryName );
649 : :
650 : 0 : return name;
651 : 0 : }
652 : :
653 : : // private
654 : 0 : void QgsAuthCertUtils::appendDirSegment_( QStringList &dirname,
655 : : const QString &segment, QString value )
656 : : {
657 : 0 : if ( !value.isEmpty() )
658 : : {
659 : 0 : dirname.append( segment + '=' + value.replace( ',', QLatin1String( "\\," ) ) );
660 : 0 : }
661 : 0 : }
662 : :
663 : 0 : QSsl::EncodingFormat QgsAuthCertUtils::sniffEncoding( const QByteArray &payload )
664 : : {
665 : 0 : return payload.contains( QByteArrayLiteral( "-----BEGIN " ) ) ?
666 : : QSsl::Pem :
667 : : QSsl::Der;
668 : 0 : }
669 : :
670 : 0 : QString QgsAuthCertUtils::getCertDistinguishedName( const QSslCertificate &qcert,
671 : : const QCA::Certificate &acert,
672 : : bool issuer )
673 : : {
674 : 0 : if ( QgsApplication::authManager()->isDisabled() )
675 : 0 : return QString();
676 : :
677 : 0 : if ( acert.isNull() )
678 : : {
679 : : QCA::ConvertResult res;
680 : 0 : QCA::Certificate acert( QCA::Certificate::fromPEM( qcert.toPem(), &res, QStringLiteral( "qca-ossl" ) ) );
681 : 0 : if ( res != QCA::ConvertGood || acert.isNull() )
682 : : {
683 : 0 : QgsDebugMsg( QStringLiteral( "Certificate could not be converted to QCA cert" ) );
684 : 0 : return QString();
685 : : }
686 : 0 : }
687 : : // E=testcert@boundlessgeo.com,
688 : : // CN=Boundless Test Root CA,
689 : : // OU=Certificate Authority,
690 : : // O=Boundless Test CA,
691 : : // L=District of Columbia,
692 : : // ST=Washington\, DC,
693 : : // C=US
694 : 0 : QStringList dirname;
695 : 0 : QgsAuthCertUtils::appendDirSegment_(
696 : 0 : dirname, QStringLiteral( "E" ), issuer ? acert.issuerInfo().value( QCA::Email )
697 : 0 : : acert.subjectInfo().value( QCA::Email ) );
698 : 0 : QgsAuthCertUtils::appendDirSegment_(
699 : 0 : dirname, QStringLiteral( "CN" ), issuer ? SSL_ISSUER_INFO( qcert, QSslCertificate::CommonName )
700 : 0 : : SSL_SUBJECT_INFO( qcert, QSslCertificate::CommonName ) );
701 : 0 : QgsAuthCertUtils::appendDirSegment_(
702 : 0 : dirname, QStringLiteral( "OU" ), issuer ? SSL_ISSUER_INFO( qcert, QSslCertificate::OrganizationalUnitName )
703 : 0 : : SSL_SUBJECT_INFO( qcert, QSslCertificate::OrganizationalUnitName ) );
704 : 0 : QgsAuthCertUtils::appendDirSegment_(
705 : 0 : dirname, QStringLiteral( "O" ), issuer ? SSL_ISSUER_INFO( qcert, QSslCertificate::Organization )
706 : 0 : : SSL_SUBJECT_INFO( qcert, QSslCertificate::Organization ) );
707 : 0 : QgsAuthCertUtils::appendDirSegment_(
708 : 0 : dirname, QStringLiteral( "L" ), issuer ? SSL_ISSUER_INFO( qcert, QSslCertificate::LocalityName )
709 : 0 : : SSL_SUBJECT_INFO( qcert, QSslCertificate::LocalityName ) );
710 : 0 : QgsAuthCertUtils::appendDirSegment_(
711 : 0 : dirname, QStringLiteral( "ST" ), issuer ? SSL_ISSUER_INFO( qcert, QSslCertificate::StateOrProvinceName )
712 : 0 : : SSL_SUBJECT_INFO( qcert, QSslCertificate::StateOrProvinceName ) );
713 : 0 : QgsAuthCertUtils::appendDirSegment_(
714 : 0 : dirname, QStringLiteral( "C" ), issuer ? SSL_ISSUER_INFO( qcert, QSslCertificate::CountryName )
715 : 0 : : SSL_SUBJECT_INFO( qcert, QSslCertificate::CountryName ) );
716 : :
717 : 0 : return dirname.join( QLatin1Char( ',' ) );
718 : 0 : }
719 : :
720 : 0 : QString QgsAuthCertUtils::getCertTrustName( QgsAuthCertUtils::CertTrustPolicy trust )
721 : : {
722 : 0 : switch ( trust )
723 : : {
724 : : case DefaultTrust:
725 : 0 : return QObject::tr( "Default" );
726 : : case Trusted:
727 : 0 : return QObject::tr( "Trusted" );
728 : : case Untrusted:
729 : 0 : return QObject::tr( "Untrusted" );
730 : : default:
731 : 0 : return QString();
732 : : }
733 : 0 : }
734 : :
735 : 0 : QString QgsAuthCertUtils::getColonDelimited( const QString &txt )
736 : : {
737 : : // 64321c05b0ebab8e2b67ec0d7d9e2b6d4bc3c303
738 : : // -> 64:32:1c:05:b0:eb:ab:8e:2b:67:ec:0d:7d:9e:2b:6d:4b:c3:c3:03
739 : 0 : QStringList sl;
740 : 0 : sl.reserve( txt.size() );
741 : 0 : for ( int i = 0; i < txt.size(); i += 2 )
742 : : {
743 : 0 : sl << txt.mid( i, ( i + 2 > txt.size() ) ? -1 : 2 );
744 : 0 : }
745 : 0 : return sl.join( QLatin1Char( ':' ) );
746 : 0 : }
747 : :
748 : 0 : QString QgsAuthCertUtils::shaHexForCert( const QSslCertificate &cert, bool formatted )
749 : : {
750 : 0 : QString sha( cert.digest( QCryptographicHash::Sha1 ).toHex() );
751 : 0 : if ( formatted )
752 : : {
753 : 0 : return QgsAuthCertUtils::getColonDelimited( sha );
754 : : }
755 : 0 : return sha;
756 : 0 : }
757 : :
758 : 0 : QCA::Certificate QgsAuthCertUtils::qtCertToQcaCert( const QSslCertificate &cert )
759 : : {
760 : 0 : if ( QgsApplication::authManager()->isDisabled() )
761 : 0 : return QCA::Certificate();
762 : :
763 : : QCA::ConvertResult res;
764 : 0 : QCA::Certificate qcacert( QCA::Certificate::fromPEM( cert.toPem(), &res, QStringLiteral( "qca-ossl" ) ) );
765 : 0 : if ( res != QCA::ConvertGood || qcacert.isNull() )
766 : : {
767 : 0 : QgsDebugMsg( QStringLiteral( "Certificate could not be converted to QCA cert" ) );
768 : 0 : qcacert = QCA::Certificate();
769 : 0 : }
770 : 0 : return qcacert;
771 : 0 : }
772 : :
773 : 0 : QCA::CertificateCollection QgsAuthCertUtils::qtCertsToQcaCollection( const QList<QSslCertificate> &certs )
774 : : {
775 : 0 : QCA::CertificateCollection qcacoll;
776 : 0 : if ( QgsApplication::authManager()->isDisabled() )
777 : 0 : return qcacoll;
778 : :
779 : 0 : for ( const auto &cert : certs )
780 : : {
781 : 0 : QCA::Certificate qcacert( qtCertToQcaCert( cert ) );
782 : 0 : if ( !qcacert.isNull() )
783 : : {
784 : 0 : qcacoll.addCertificate( qcacert );
785 : 0 : }
786 : 0 : }
787 : 0 : return qcacoll;
788 : 0 : }
789 : :
790 : 0 : QCA::KeyBundle QgsAuthCertUtils::qcaKeyBundle( const QString &path, const QString &pass )
791 : : {
792 : 0 : QCA::SecureArray passarray;
793 : 0 : if ( !pass.isEmpty() )
794 : 0 : passarray = QCA::SecureArray( pass.toUtf8() );
795 : :
796 : : QCA::ConvertResult res;
797 : 0 : QCA::KeyBundle bundle( QCA::KeyBundle::fromFile( path, passarray, &res, QStringLiteral( "qca-ossl" ) ) );
798 : :
799 : 0 : return ( res == QCA::ConvertGood ? bundle : QCA::KeyBundle() );
800 : 0 : }
801 : :
802 : 0 : QString QgsAuthCertUtils::qcaValidityMessage( QCA::Validity validity )
803 : : {
804 : 0 : switch ( validity )
805 : : {
806 : : case QCA::ValidityGood:
807 : 0 : return QObject::tr( "Certificate is valid." );
808 : : case QCA::ErrorRejected:
809 : 0 : return QObject::tr( "Root CA rejected the certificate purpose." );
810 : : case QCA::ErrorUntrusted:
811 : 0 : return QObject::tr( "Certificate is not trusted." );
812 : : case QCA::ErrorSignatureFailed:
813 : 0 : return QObject::tr( "Signature does not match." );
814 : : case QCA::ErrorInvalidCA:
815 : 0 : return QObject::tr( "Certificate Authority is invalid or not found." );
816 : : case QCA::ErrorInvalidPurpose:
817 : 0 : return QObject::tr( "Purpose does not match the intended usage." );
818 : : case QCA::ErrorSelfSigned:
819 : 0 : return QObject::tr( "Certificate is self-signed, and is not found in the list of trusted certificates." );
820 : : case QCA::ErrorRevoked:
821 : 0 : return QObject::tr( "Certificate has been revoked." );
822 : : case QCA::ErrorPathLengthExceeded:
823 : 0 : return QObject::tr( "Path length from the root CA to this certificate is too long." );
824 : : case QCA::ErrorExpired:
825 : 0 : return QObject::tr( "Certificate has expired or is not yet valid." );
826 : : case QCA::ErrorExpiredCA:
827 : 0 : return QObject::tr( "Certificate Authority has expired." );
828 : : case QCA::ErrorValidityUnknown:
829 : 0 : return QObject::tr( "Validity is unknown." );
830 : : default:
831 : 0 : return QString();
832 : : }
833 : 0 : }
834 : :
835 : 0 : QString QgsAuthCertUtils::qcaSignatureAlgorithm( QCA::SignatureAlgorithm algorithm )
836 : : {
837 : 0 : switch ( algorithm )
838 : : {
839 : : case QCA::EMSA1_SHA1:
840 : 0 : return QObject::tr( "SHA1, with EMSA1" );
841 : : case QCA::EMSA3_SHA1:
842 : 0 : return QObject::tr( "SHA1, with EMSA3" );
843 : : case QCA::EMSA3_MD5:
844 : 0 : return QObject::tr( "MD5, with EMSA3" );
845 : : case QCA::EMSA3_MD2:
846 : 0 : return QObject::tr( "MD2, with EMSA3" );
847 : : case QCA::EMSA3_RIPEMD160:
848 : 0 : return QObject::tr( "RIPEMD160, with EMSA3" );
849 : : case QCA::EMSA3_Raw:
850 : 0 : return QObject::tr( "EMSA3, without digest" );
851 : : #if QCA_VERSION >= 0x020100
852 : : case QCA::EMSA3_SHA224:
853 : 0 : return QObject::tr( "SHA224, with EMSA3" );
854 : : case QCA::EMSA3_SHA256:
855 : 0 : return QObject::tr( "SHA256, with EMSA3" );
856 : : case QCA::EMSA3_SHA384:
857 : 0 : return QObject::tr( "SHA384, with EMSA3" );
858 : : case QCA::EMSA3_SHA512:
859 : 0 : return QObject::tr( "SHA512, with EMSA3" );
860 : : #endif
861 : : default:
862 : 0 : return QObject::tr( "Unknown (possibly Elliptic Curve)" );
863 : : }
864 : 0 : }
865 : :
866 : 0 : QString QgsAuthCertUtils::qcaKnownConstraint( QCA::ConstraintTypeKnown constraint )
867 : : {
868 : 0 : switch ( constraint )
869 : : {
870 : : case QCA::DigitalSignature:
871 : 0 : return QObject::tr( "Digital Signature" );
872 : : case QCA::NonRepudiation:
873 : 0 : return QObject::tr( "Non-repudiation" );
874 : : case QCA::KeyEncipherment:
875 : 0 : return QObject::tr( "Key Encipherment" );
876 : : case QCA::DataEncipherment:
877 : 0 : return QObject::tr( "Data Encipherment" );
878 : : case QCA::KeyAgreement:
879 : 0 : return QObject::tr( "Key Agreement" );
880 : : case QCA::KeyCertificateSign:
881 : 0 : return QObject::tr( "Key Certificate Sign" );
882 : : case QCA::CRLSign:
883 : 0 : return QObject::tr( "CRL Sign" );
884 : : case QCA::EncipherOnly:
885 : 0 : return QObject::tr( "Encipher Only" );
886 : : case QCA::DecipherOnly:
887 : 0 : return QObject::tr( "Decipher Only" );
888 : : case QCA::ServerAuth:
889 : 0 : return QObject::tr( "Server Authentication" );
890 : : case QCA::ClientAuth:
891 : 0 : return QObject::tr( "Client Authentication" );
892 : : case QCA::CodeSigning:
893 : 0 : return QObject::tr( "Code Signing" );
894 : : case QCA::EmailProtection:
895 : 0 : return QObject::tr( "Email Protection" );
896 : : case QCA::IPSecEndSystem:
897 : 0 : return QObject::tr( "IPSec Endpoint" );
898 : : case QCA::IPSecTunnel:
899 : 0 : return QObject::tr( "IPSec Tunnel" );
900 : : case QCA::IPSecUser:
901 : 0 : return QObject::tr( "IPSec User" );
902 : : case QCA::TimeStamping:
903 : 0 : return QObject::tr( "Time Stamping" );
904 : : case QCA::OCSPSigning:
905 : 0 : return QObject::tr( "OCSP Signing" );
906 : : default:
907 : 0 : return QString();
908 : : }
909 : 0 : }
910 : :
911 : 0 : QString QgsAuthCertUtils::certificateUsageTypeString( QgsAuthCertUtils::CertUsageType usagetype )
912 : : {
913 : 0 : switch ( usagetype )
914 : : {
915 : : case QgsAuthCertUtils::AnyOrUnspecifiedUsage:
916 : 0 : return QObject::tr( "Any or unspecified" );
917 : : case QgsAuthCertUtils::CertAuthorityUsage:
918 : 0 : return QObject::tr( "Certificate Authority" );
919 : : case QgsAuthCertUtils::CertIssuerUsage:
920 : 0 : return QObject::tr( "Certificate Issuer" );
921 : : case QgsAuthCertUtils::TlsServerUsage:
922 : 0 : return QObject::tr( "TLS/SSL Server" );
923 : : case QgsAuthCertUtils::TlsServerEvUsage:
924 : 0 : return QObject::tr( "TLS/SSL Server EV" );
925 : : case QgsAuthCertUtils::TlsClientUsage:
926 : 0 : return QObject::tr( "TLS/SSL Client" );
927 : : case QgsAuthCertUtils::CodeSigningUsage:
928 : 0 : return QObject::tr( "Code Signing" );
929 : : case QgsAuthCertUtils::EmailProtectionUsage:
930 : 0 : return QObject::tr( "Email Protection" );
931 : : case QgsAuthCertUtils::TimeStampingUsage:
932 : 0 : return QObject::tr( "Time Stamping" );
933 : : case QgsAuthCertUtils::CRLSigningUsage:
934 : 0 : return QObject::tr( "CRL Signing" );
935 : : case QgsAuthCertUtils::UndeterminedUsage:
936 : : default:
937 : 0 : return QObject::tr( "Undetermined usage" );
938 : : }
939 : 0 : }
940 : :
941 : 0 : QList<QgsAuthCertUtils::CertUsageType> QgsAuthCertUtils::certificateUsageTypes( const QSslCertificate &cert )
942 : : {
943 : 0 : QList<QgsAuthCertUtils::CertUsageType> usages;
944 : :
945 : 0 : if ( QgsApplication::authManager()->isDisabled() )
946 : 0 : return usages;
947 : :
948 : : QCA::ConvertResult res;
949 : 0 : QCA::Certificate qcacert( QCA::Certificate::fromPEM( cert.toPem(), &res, QStringLiteral( "qca-ossl" ) ) );
950 : 0 : if ( res != QCA::ConvertGood || qcacert.isNull() )
951 : : {
952 : 0 : QgsDebugMsg( QStringLiteral( "Certificate could not be converted to QCA cert" ) );
953 : 0 : return usages;
954 : : }
955 : :
956 : 0 : if ( qcacert.isCA() )
957 : : {
958 : 0 : QgsDebugMsg( QStringLiteral( "Certificate has 'CA:TRUE' basic constraint" ) );
959 : 0 : usages << QgsAuthCertUtils::CertAuthorityUsage;
960 : 0 : }
961 : :
962 : 0 : const QList<QCA::ConstraintType> certconsts = qcacert.constraints();
963 : 0 : for ( const auto &certconst : certconsts )
964 : : {
965 : 0 : if ( certconst.known() == QCA::KeyCertificateSign )
966 : : {
967 : 0 : QgsDebugMsg( QStringLiteral( "Certificate has 'Certificate Sign' key usage" ) );
968 : 0 : usages << QgsAuthCertUtils::CertIssuerUsage;
969 : 0 : }
970 : 0 : else if ( certconst.known() == QCA::ServerAuth )
971 : : {
972 : 0 : QgsDebugMsg( QStringLiteral( "Certificate has 'server authentication' extended key usage" ) );
973 : 0 : usages << QgsAuthCertUtils::TlsServerUsage;
974 : 0 : }
975 : : }
976 : :
977 : : // ask QCA what it thinks about potential usages
978 : : QCA::CertificateCollection trustedCAs(
979 : 0 : qtCertsToQcaCollection( QgsApplication::authManager()->trustedCaCertsCache() ) );
980 : : QCA::CertificateCollection untrustedCAs(
981 : 0 : qtCertsToQcaCollection( QgsApplication::authManager()->untrustedCaCerts() ) );
982 : :
983 : : QCA::Validity v_any;
984 : 0 : v_any = qcacert.validate( trustedCAs, untrustedCAs, QCA::UsageAny, QCA::ValidateAll );
985 : 0 : if ( v_any == QCA::ValidityGood )
986 : : {
987 : 0 : usages << QgsAuthCertUtils::AnyOrUnspecifiedUsage;
988 : 0 : }
989 : :
990 : : QCA::Validity v_tlsserver;
991 : 0 : v_tlsserver = qcacert.validate( trustedCAs, untrustedCAs, QCA::UsageTLSServer, QCA::ValidateAll );
992 : 0 : if ( v_tlsserver == QCA::ValidityGood )
993 : : {
994 : 0 : if ( !usages.contains( QgsAuthCertUtils::TlsServerUsage ) )
995 : : {
996 : 0 : usages << QgsAuthCertUtils::TlsServerUsage;
997 : 0 : }
998 : 0 : }
999 : :
1000 : : // TODO: why doesn't this tag client certs?
1001 : : // always seems to return QCA::ErrorInvalidPurpose (enum #5)
1002 : : QCA::Validity v_tlsclient;
1003 : 0 : v_tlsclient = qcacert.validate( trustedCAs, untrustedCAs, QCA::UsageTLSClient, QCA::ValidateAll );
1004 : : //QgsDebugMsg( QStringLiteral( "QCA::UsageTLSClient validity: %1" ).arg( static_cast<int>(v_tlsclient) ) );
1005 : 0 : if ( v_tlsclient == QCA::ValidityGood )
1006 : : {
1007 : 0 : usages << QgsAuthCertUtils::TlsClientUsage;
1008 : 0 : }
1009 : :
1010 : : // TODO: add TlsServerEvUsage, CodeSigningUsage, EmailProtectionUsage, TimeStampingUsage, CRLSigningUsage
1011 : : // as they become necessary, since we do not want the overhead of checking just yet.
1012 : :
1013 : 0 : return usages;
1014 : 0 : }
1015 : :
1016 : 0 : bool QgsAuthCertUtils::certificateIsAuthority( const QSslCertificate &cert )
1017 : : {
1018 : 0 : return QgsAuthCertUtils::certificateUsageTypes( cert ).contains( QgsAuthCertUtils::CertAuthorityUsage );
1019 : 0 : }
1020 : :
1021 : 0 : bool QgsAuthCertUtils::certificateIsIssuer( const QSslCertificate &cert )
1022 : : {
1023 : 0 : return QgsAuthCertUtils::certificateUsageTypes( cert ).contains( QgsAuthCertUtils::CertIssuerUsage );
1024 : 0 : }
1025 : :
1026 : 0 : bool QgsAuthCertUtils::certificateIsAuthorityOrIssuer( const QSslCertificate &cert )
1027 : : {
1028 : 0 : return ( QgsAuthCertUtils::certificateIsAuthority( cert )
1029 : 0 : || QgsAuthCertUtils::certificateIsIssuer( cert ) );
1030 : : }
1031 : :
1032 : 0 : bool QgsAuthCertUtils::certificateIsSslServer( const QSslCertificate &cert )
1033 : : {
1034 : 0 : return ( QgsAuthCertUtils::certificateUsageTypes( cert ).contains( QgsAuthCertUtils::TlsServerUsage )
1035 : 0 : || QgsAuthCertUtils::certificateUsageTypes( cert ).contains( QgsAuthCertUtils::TlsServerEvUsage ) );
1036 : 0 : }
1037 : :
1038 : : #if 0
1039 : : bool QgsAuthCertUtils::certificateIsSslServer( const QSslCertificate &cert )
1040 : : {
1041 : : // TODO: There is no difinitive method for strictly enforcing what determines an SSL server cert;
1042 : : // only what it should not be able to do (cert sign, etc.). The logic here may need refined
1043 : : // see: http://security.stackexchange.com/a/26650
1044 : :
1045 : : if ( QgsApplication::authManager()->isDisabled() )
1046 : : return false;
1047 : :
1048 : : QCA::ConvertResult res;
1049 : : QCA::Certificate qcacert( QCA::Certificate::fromPEM( cert.toPem(), &res, QString( "qca-ossl" ) ) );
1050 : : if ( res != QCA::ConvertGood || qcacert.isNull() )
1051 : : {
1052 : : QgsDebugMsg( QStringLiteral( "Certificate could not be converted to QCA cert" ) );
1053 : : return false;
1054 : : }
1055 : :
1056 : : if ( qcacert.isCA() )
1057 : : {
1058 : : QgsDebugMsg( QStringLiteral( "SSL server certificate has 'CA:TRUE' basic constraint (and should not)" ) );
1059 : : return false;
1060 : : }
1061 : :
1062 : : const QList<QCA::ConstraintType> certconsts = qcacert.constraints();
1063 : : for ( const auto & certconst, certconsts )
1064 : : {
1065 : : if ( certconst.known() == QCA::KeyCertificateSign )
1066 : : {
1067 : : QgsDebugMsg( QStringLiteral( "SSL server certificate has 'Certificate Sign' key usage (and should not)" ) );
1068 : : return false;
1069 : : }
1070 : : }
1071 : :
1072 : : // check for common key usage and extended key usage constraints
1073 : : // see: https://www.ietf.org/rfc/rfc3280.txt 4.2.1.3(Key Usage) and 4.2.1.13(Extended Key Usage)
1074 : : bool serverauth = false;
1075 : : bool dsignature = false;
1076 : : bool keyencrypt = false;
1077 : : for ( const auto &certconst : certconsts )
1078 : : {
1079 : : if ( certconst.known() == QCA::DigitalSignature )
1080 : : {
1081 : : QgsDebugMsg( QStringLiteral( "SSL server certificate has 'digital signature' key usage" ) );
1082 : : dsignature = true;
1083 : : }
1084 : : else if ( certconst.known() == QCA::KeyEncipherment )
1085 : : {
1086 : : QgsDebugMsg( QStringLiteral( "SSL server certificate has 'key encipherment' key usage" ) );
1087 : : keyencrypt = true;
1088 : : }
1089 : : else if ( certconst.known() == QCA::KeyAgreement )
1090 : : {
1091 : : QgsDebugMsg( QStringLiteral( "SSL server certificate has 'key agreement' key usage" ) );
1092 : : keyencrypt = true;
1093 : : }
1094 : : else if ( certconst.known() == QCA::ServerAuth )
1095 : : {
1096 : : QgsDebugMsg( QStringLiteral( "SSL server certificate has 'server authentication' extended key usage" ) );
1097 : : serverauth = true;
1098 : : }
1099 : : }
1100 : : // From 4.2.1.13(Extended Key Usage):
1101 : : // "If a certificate contains both a key usage extension and an extended
1102 : : // key usage extension, then both extensions MUST be processed
1103 : : // independently and the certificate MUST only be used for a purpose
1104 : : // consistent with both extensions. If there is no purpose consistent
1105 : : // with both extensions, then the certificate MUST NOT be used for any
1106 : : // purpose."
1107 : :
1108 : : if ( serverauth && dsignature && keyencrypt )
1109 : : {
1110 : : return true;
1111 : : }
1112 : : if ( dsignature && keyencrypt )
1113 : : {
1114 : : return true;
1115 : : }
1116 : :
1117 : : // lastly, check for DH key and key agreement
1118 : : bool keyagree = false;
1119 : : bool encipheronly = false;
1120 : : bool decipheronly = false;
1121 : :
1122 : : QCA::PublicKey pubkey( qcacert.subjectPublicKey() );
1123 : : // key size may be 0 for eliptical curve-based keys, in which case isDH() crashes QCA
1124 : : if ( pubkey.bitSize() > 0 && pubkey.isDH() )
1125 : : {
1126 : : keyagree = pubkey.canKeyAgree();
1127 : : if ( !keyagree )
1128 : : {
1129 : : return false;
1130 : : }
1131 : : for ( const auto &certconst : certconsts )
1132 : : {
1133 : : if ( certconst.known() == QCA::EncipherOnly )
1134 : : {
1135 : : QgsDebugMsg( QStringLiteral( "SSL server public key has 'encipher only' key usage" ) );
1136 : : encipheronly = true;
1137 : : }
1138 : : else if ( certconst.known() == QCA::DecipherOnly )
1139 : : {
1140 : : QgsDebugMsg( QStringLiteral( "SSL server public key has 'decipher only' key usage" ) );
1141 : : decipheronly = true;
1142 : : }
1143 : : }
1144 : : if ( !encipheronly && !decipheronly )
1145 : : {
1146 : : return true;
1147 : : }
1148 : : }
1149 : : return false;
1150 : : }
1151 : : #endif
1152 : :
1153 : 0 : bool QgsAuthCertUtils::certificateIsSslClient( const QSslCertificate &cert )
1154 : : {
1155 : 0 : return QgsAuthCertUtils::certificateUsageTypes( cert ).contains( QgsAuthCertUtils::TlsClientUsage );
1156 : 0 : }
1157 : :
1158 : 0 : QString QgsAuthCertUtils::sslErrorEnumString( QSslError::SslError errenum )
1159 : : {
1160 : 0 : switch ( errenum )
1161 : : {
1162 : : case QSslError::UnableToGetIssuerCertificate:
1163 : 0 : return QObject::tr( "Unable to Get Issuer Certificate" );
1164 : : case QSslError::UnableToDecryptCertificateSignature:
1165 : 0 : return QObject::tr( "Unable to Decrypt Certificate Signature" );
1166 : : case QSslError::UnableToDecodeIssuerPublicKey:
1167 : 0 : return QObject::tr( "Unable to Decode Issuer Public Key" );
1168 : : case QSslError::CertificateSignatureFailed:
1169 : 0 : return QObject::tr( "Certificate Signature Failed" );
1170 : : case QSslError::CertificateNotYetValid:
1171 : 0 : return QObject::tr( "Certificate Not Yet Valid" );
1172 : : case QSslError::CertificateExpired:
1173 : 0 : return QObject::tr( "Certificate Expired" );
1174 : : case QSslError::InvalidNotBeforeField:
1175 : 0 : return QObject::tr( "Invalid Not Before Field" );
1176 : : case QSslError::InvalidNotAfterField:
1177 : 0 : return QObject::tr( "Invalid Not After Field" );
1178 : : case QSslError::SelfSignedCertificate:
1179 : 0 : return QObject::tr( "Self-signed Certificate" );
1180 : : case QSslError::SelfSignedCertificateInChain:
1181 : 0 : return QObject::tr( "Self-signed Certificate In Chain" );
1182 : : case QSslError::UnableToGetLocalIssuerCertificate:
1183 : 0 : return QObject::tr( "Unable to Get Local Issuer Certificate" );
1184 : : case QSslError::UnableToVerifyFirstCertificate:
1185 : 0 : return QObject::tr( "Unable to Verify First Certificate" );
1186 : : case QSslError::CertificateRevoked:
1187 : 0 : return QObject::tr( "Certificate Revoked" );
1188 : : case QSslError::InvalidCaCertificate:
1189 : 0 : return QObject::tr( "Invalid CA Certificate" );
1190 : : case QSslError::PathLengthExceeded:
1191 : 0 : return QObject::tr( "Path Length Exceeded" );
1192 : : case QSslError::InvalidPurpose:
1193 : 0 : return QObject::tr( "Invalid Purpose" );
1194 : : case QSslError::CertificateUntrusted:
1195 : 0 : return QObject::tr( "Certificate Untrusted" );
1196 : : case QSslError::CertificateRejected:
1197 : 0 : return QObject::tr( "Certificate Rejected" );
1198 : : case QSslError::SubjectIssuerMismatch:
1199 : 0 : return QObject::tr( "Subject Issuer Mismatch" );
1200 : : case QSslError::AuthorityIssuerSerialNumberMismatch:
1201 : 0 : return QObject::tr( "Authority Issuer Serial Number Mismatch" );
1202 : : case QSslError::NoPeerCertificate:
1203 : 0 : return QObject::tr( "No Peer Certificate" );
1204 : : case QSslError::HostNameMismatch:
1205 : 0 : return QObject::tr( "Host Name Mismatch" );
1206 : : case QSslError::UnspecifiedError:
1207 : 0 : return QObject::tr( "Unspecified Error" );
1208 : : case QSslError::CertificateBlacklisted:
1209 : 0 : return QObject::tr( "Certificate Blacklisted" );
1210 : : case QSslError::NoError:
1211 : 0 : return QObject::tr( "No Error" );
1212 : : case QSslError::NoSslSupport:
1213 : 0 : return QObject::tr( "No SSL Support" );
1214 : : default:
1215 : 0 : return QString();
1216 : : }
1217 : 0 : }
1218 : :
1219 : 0 : QList<QPair<QSslError::SslError, QString> > QgsAuthCertUtils::sslErrorEnumStrings()
1220 : : {
1221 : 0 : QList<QPair<QSslError::SslError, QString> > errenums;
1222 : 0 : errenums << qMakePair( QSslError::UnableToGetIssuerCertificate,
1223 : 0 : QgsAuthCertUtils::sslErrorEnumString( QSslError::UnableToGetIssuerCertificate ) );
1224 : 0 : errenums << qMakePair( QSslError::UnableToDecryptCertificateSignature,
1225 : 0 : QgsAuthCertUtils::sslErrorEnumString( QSslError::UnableToDecryptCertificateSignature ) );
1226 : 0 : errenums << qMakePair( QSslError::UnableToDecodeIssuerPublicKey,
1227 : 0 : QgsAuthCertUtils::sslErrorEnumString( QSslError::UnableToDecodeIssuerPublicKey ) );
1228 : 0 : errenums << qMakePair( QSslError::CertificateSignatureFailed,
1229 : 0 : QgsAuthCertUtils::sslErrorEnumString( QSslError::CertificateSignatureFailed ) );
1230 : 0 : errenums << qMakePair( QSslError::CertificateNotYetValid,
1231 : 0 : QgsAuthCertUtils::sslErrorEnumString( QSslError::CertificateNotYetValid ) );
1232 : 0 : errenums << qMakePair( QSslError::CertificateExpired,
1233 : 0 : QgsAuthCertUtils::sslErrorEnumString( QSslError::CertificateExpired ) );
1234 : 0 : errenums << qMakePair( QSslError::InvalidNotBeforeField,
1235 : 0 : QgsAuthCertUtils::sslErrorEnumString( QSslError::InvalidNotBeforeField ) );
1236 : 0 : errenums << qMakePair( QSslError::InvalidNotAfterField,
1237 : 0 : QgsAuthCertUtils::sslErrorEnumString( QSslError::InvalidNotAfterField ) );
1238 : 0 : errenums << qMakePair( QSslError::SelfSignedCertificate,
1239 : 0 : QgsAuthCertUtils::sslErrorEnumString( QSslError::SelfSignedCertificate ) );
1240 : 0 : errenums << qMakePair( QSslError::SelfSignedCertificateInChain,
1241 : 0 : QgsAuthCertUtils::sslErrorEnumString( QSslError::SelfSignedCertificateInChain ) );
1242 : 0 : errenums << qMakePair( QSslError::UnableToGetLocalIssuerCertificate,
1243 : 0 : QgsAuthCertUtils::sslErrorEnumString( QSslError::UnableToGetLocalIssuerCertificate ) );
1244 : 0 : errenums << qMakePair( QSslError::UnableToVerifyFirstCertificate,
1245 : 0 : QgsAuthCertUtils::sslErrorEnumString( QSslError::UnableToVerifyFirstCertificate ) );
1246 : 0 : errenums << qMakePair( QSslError::CertificateRevoked,
1247 : 0 : QgsAuthCertUtils::sslErrorEnumString( QSslError::CertificateRevoked ) );
1248 : 0 : errenums << qMakePair( QSslError::InvalidCaCertificate,
1249 : 0 : QgsAuthCertUtils::sslErrorEnumString( QSslError::InvalidCaCertificate ) );
1250 : 0 : errenums << qMakePair( QSslError::PathLengthExceeded,
1251 : 0 : QgsAuthCertUtils::sslErrorEnumString( QSslError::PathLengthExceeded ) );
1252 : 0 : errenums << qMakePair( QSslError::InvalidPurpose,
1253 : 0 : QgsAuthCertUtils::sslErrorEnumString( QSslError::InvalidPurpose ) );
1254 : 0 : errenums << qMakePair( QSslError::CertificateUntrusted,
1255 : 0 : QgsAuthCertUtils::sslErrorEnumString( QSslError::CertificateUntrusted ) );
1256 : 0 : errenums << qMakePair( QSslError::CertificateRejected,
1257 : 0 : QgsAuthCertUtils::sslErrorEnumString( QSslError::CertificateRejected ) );
1258 : 0 : errenums << qMakePair( QSslError::SubjectIssuerMismatch,
1259 : 0 : QgsAuthCertUtils::sslErrorEnumString( QSslError::SubjectIssuerMismatch ) );
1260 : 0 : errenums << qMakePair( QSslError::AuthorityIssuerSerialNumberMismatch,
1261 : 0 : QgsAuthCertUtils::sslErrorEnumString( QSslError::AuthorityIssuerSerialNumberMismatch ) );
1262 : 0 : errenums << qMakePair( QSslError::NoPeerCertificate,
1263 : 0 : QgsAuthCertUtils::sslErrorEnumString( QSslError::NoPeerCertificate ) );
1264 : 0 : errenums << qMakePair( QSslError::HostNameMismatch,
1265 : 0 : QgsAuthCertUtils::sslErrorEnumString( QSslError::HostNameMismatch ) );
1266 : 0 : errenums << qMakePair( QSslError::UnspecifiedError,
1267 : 0 : QgsAuthCertUtils::sslErrorEnumString( QSslError::UnspecifiedError ) );
1268 : 0 : errenums << qMakePair( QSslError::CertificateBlacklisted,
1269 : 0 : QgsAuthCertUtils::sslErrorEnumString( QSslError::CertificateBlacklisted ) );
1270 : 0 : return errenums;
1271 : 0 : }
1272 : :
1273 : 0 : bool QgsAuthCertUtils::certIsCurrent( const QSslCertificate &cert )
1274 : : {
1275 : 0 : if ( cert.isNull() )
1276 : 0 : return false;
1277 : 0 : const QDateTime currentTime = QDateTime::currentDateTime();
1278 : 0 : return cert.effectiveDate() <= currentTime && cert.expiryDate() >= currentTime;
1279 : 0 : }
1280 : :
1281 : 0 : QList<QSslError> QgsAuthCertUtils::certViabilityErrors( const QSslCertificate &cert )
1282 : : {
1283 : 0 : QList<QSslError> sslErrors;
1284 : :
1285 : 0 : if ( cert.isNull() )
1286 : 0 : return sslErrors;
1287 : :
1288 : 0 : const QDateTime currentTime = QDateTime::currentDateTime();
1289 : 0 : if ( cert.expiryDate() <= currentTime )
1290 : : {
1291 : 0 : sslErrors << QSslError( QSslError::SslError::CertificateExpired, cert );
1292 : 0 : }
1293 : 0 : if ( cert.effectiveDate() >= QDateTime::currentDateTime() )
1294 : : {
1295 : 0 : sslErrors << QSslError( QSslError::SslError::CertificateNotYetValid, cert );
1296 : 0 : }
1297 : 0 : if ( cert.isBlacklisted() )
1298 : : {
1299 : 0 : sslErrors << QSslError( QSslError::SslError::CertificateBlacklisted, cert );
1300 : 0 : }
1301 : :
1302 : 0 : return sslErrors;
1303 : 0 : }
1304 : :
1305 : 0 : bool QgsAuthCertUtils::certIsViable( const QSslCertificate &cert )
1306 : : {
1307 : 0 : return !cert.isNull() && QgsAuthCertUtils::certViabilityErrors( cert ).isEmpty();
1308 : : }
1309 : :
1310 : 0 : QList<QSslError> QgsAuthCertUtils::validateCertChain( const QList<QSslCertificate> &certificateChain,
1311 : : const QString &hostName,
1312 : : bool trustRootCa )
1313 : : {
1314 : 0 : QList<QSslError> sslErrors;
1315 : 0 : QList<QSslCertificate> trustedChain;
1316 : : // Filter out all CAs that are not trusted from QgsAuthManager
1317 : 0 : for ( const auto &cert : certificateChain )
1318 : : {
1319 : 0 : bool untrusted = false;
1320 : 0 : for ( const auto &untrustedCert : QgsApplication::authManager()->untrustedCaCerts() )
1321 : : {
1322 : 0 : if ( cert.digest( ) == untrustedCert.digest( ) )
1323 : : {
1324 : 0 : untrusted = true;
1325 : 0 : break;
1326 : : }
1327 : : }
1328 : 0 : if ( ! untrusted )
1329 : : {
1330 : 0 : trustedChain << cert;
1331 : 0 : }
1332 : : }
1333 : :
1334 : : // Check that no certs in the chain are expired or not yet valid or blocklisted
1335 : 0 : const QList<QSslCertificate> constTrustedChain( trustedChain );
1336 : 0 : for ( const auto &cert : constTrustedChain )
1337 : : {
1338 : 0 : sslErrors << QgsAuthCertUtils::certViabilityErrors( cert );
1339 : : }
1340 : :
1341 : : // Merge in the root CA if present and asked for
1342 : 0 : if ( trustRootCa && trustedChain.count() > 1 && trustedChain.last().isSelfSigned() )
1343 : : {
1344 : 0 : static QMutex sMutex;
1345 : 0 : QMutexLocker lock( &sMutex );
1346 : 0 : QSslConfiguration oldSslConfig( QSslConfiguration::defaultConfiguration() );
1347 : 0 : QSslConfiguration sslConfig( oldSslConfig );
1348 : 0 : sslConfig.setCaCertificates( casMerge( sslConfig.caCertificates(), QList<QSslCertificate>() << trustedChain.last() ) );
1349 : 0 : QSslConfiguration::setDefaultConfiguration( sslConfig );
1350 : 0 : sslErrors = QSslCertificate::verify( trustedChain, hostName );
1351 : 0 : QSslConfiguration::setDefaultConfiguration( oldSslConfig );
1352 : 0 : }
1353 : : else
1354 : : {
1355 : 0 : sslErrors = QSslCertificate::verify( trustedChain, hostName );
1356 : : }
1357 : 0 : return sslErrors;
1358 : 0 : }
1359 : :
1360 : 0 : QStringList QgsAuthCertUtils::validatePKIBundle( QgsPkiBundle &bundle, bool useIntermediates, bool trustRootCa )
1361 : : {
1362 : 0 : QStringList errors;
1363 : 0 : if ( bundle.clientCert().isNull() )
1364 : 0 : errors << QObject::tr( "Client certificate is NULL." );
1365 : :
1366 : 0 : if ( bundle.clientKey().isNull() )
1367 : 0 : errors << QObject::tr( "Client certificate key is NULL." );
1368 : :
1369 : : // immediately bail out if cert or key is NULL
1370 : 0 : if ( !errors.isEmpty() )
1371 : 0 : return errors;
1372 : :
1373 : 0 : QList<QSslError> sslErrors;
1374 : 0 : if ( useIntermediates )
1375 : : {
1376 : 0 : QList<QSslCertificate> certsList( bundle.caChain() );
1377 : 0 : certsList.insert( 0, bundle.clientCert( ) );
1378 : 0 : sslErrors = QgsAuthCertUtils::validateCertChain( certsList, QString(), trustRootCa );
1379 : 0 : }
1380 : : else
1381 : : {
1382 : 0 : sslErrors = QSslCertificate::verify( QList<QSslCertificate>() << bundle.clientCert() );
1383 : : }
1384 : 0 : const QList<QSslError> constSslErrors( sslErrors );
1385 : 0 : for ( const auto &sslError : constSslErrors )
1386 : : {
1387 : 0 : if ( sslError.error() != QSslError::NoError )
1388 : : {
1389 : 0 : errors << sslError.errorString();
1390 : 0 : }
1391 : : }
1392 : : // Now check the key with QCA!
1393 : 0 : QCA::PrivateKey pvtKey( QCA::PrivateKey::fromPEM( bundle.clientKey().toPem() ) );
1394 : 0 : QCA::PublicKey pubKey( QCA::PublicKey::fromPEM( bundle.clientCert().publicKey().toPem( ) ) );
1395 : 0 : bool keyValid( ! pvtKey.isNull() );
1396 : 0 : if ( keyValid && !( pubKey.toRSA().isNull( ) || pvtKey.toRSA().isNull( ) ) )
1397 : : {
1398 : 0 : keyValid = pubKey.toRSA().n() == pvtKey.toRSA().n();
1399 : 0 : }
1400 : 0 : else if ( keyValid && !( pubKey.toDSA().isNull( ) || pvtKey.toDSA().isNull( ) ) )
1401 : : {
1402 : 0 : keyValid = pubKey == QCA::DSAPublicKey( pvtKey.toDSA() );
1403 : 0 : }
1404 : : else
1405 : : {
1406 : 0 : QgsDebugMsg( QStringLiteral( "Key is not DSA, RSA: validation is not supported by QCA" ) );
1407 : : }
1408 : 0 : if ( ! keyValid )
1409 : : {
1410 : 0 : errors << QObject::tr( "Private key does not match client certificate public key." );
1411 : 0 : }
1412 : 0 : return errors;
1413 : 0 : }
|