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