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