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