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