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