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