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