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