QGIS API Documentation  3.0.2-Girona (307d082)
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( QString( "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( QString( "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( "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( QString( "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( QString( "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( QString( "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( QString( "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( QString( "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( QString( "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( QString( "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( QString( "PKCS#12 cert as PEM:\n%1" ).arg( QString( bundle.certificateChain().primary().toPEM() ) ), 4 );
527  // !!! SENSITIVE DATA - DO NOT LEAVE UNCOMMENTED !!!
528  //QgsDebugMsgLevel( QString( "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( QString( "FAILED to write to temp PEM file: %1" ).arg( pemFilePath ) );
580  pemFilePath.clear();
581  }
582  }
583  else
584  {
585  QgsDebugMsg( QString( "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( QString( "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( "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( "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( "Certificate could not be converted to QCA cert" );
943  return usages;
944  }
945 
946  if ( qcacert.isCA() )
947  {
948  QgsDebugMsg( "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( "Certificate has 'Certificate Sign' key usage" );
959  }
960  else if ( certconst.known() == QCA::ServerAuth )
961  {
962  QgsDebugMsg( "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( QString( "QCA::UsageTLSClient validity: %1" ).arg( ( 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( "Certificate could not be converted to QCA cert" );
1043  return false;
1044  }
1045 
1046  if ( qcacert.isCA() )
1047  {
1048  QgsDebugMsg( "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( "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( "SSL server certificate has 'digital signature' key usage" );
1072  dsignature = true;
1073  }
1074  else if ( certconst.known() == QCA::KeyEncipherment )
1075  {
1076  QgsDebugMsg( "SSL server certificate has 'key encipherment' key usage" );
1077  keyencrypt = true;
1078  }
1079  else if ( certconst.known() == QCA::KeyAgreement )
1080  {
1081  QgsDebugMsg( "SSL server certificate has 'key agreement' key usage" );
1082  keyencrypt = true;
1083  }
1084  else if ( certconst.known() == QCA::ServerAuth )
1085  {
1086  QgsDebugMsg( "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( "SSL server public key has 'encipher only' key usage" );
1126  encipheronly = true;
1127  }
1128  else if ( certconst.known() == QCA::DecipherOnly )
1129  {
1130  QgsDebugMsg( "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( "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)
Get 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)
Return non-encrypted key from a PEM or DER formatted file.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
static QList< QSslCertificate > casFromFile(const QString &certspath)
Return list of concatenated CAs from a PEM or DER formatted file.
static QString sslErrorEnumString(QSslError::SslError errenum)
Get short strings describing an SSL error.
static QList< QSslCertificate > certsFromFile(const QString &certspath)
Return 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)
Get 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)
Return list of certificate, private key and algorithm (as PEM text) for a PKCS#12 bundle...
static bool certificateIsAuthority(const QSslCertificate &cert)
Get whether a certificate is an Authority.
static QByteArray fileData(const QString &path)
Return 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.
static QStringList certKeyBundleToPem(const QString &certpath, const QString &keypath, const QString &keypass=QString(), bool reencrypt=true)
Return 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)
Get 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)
Get 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)
Return 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)
Get whether a certificate is probably used for a client identity.
static QString shaHexForCert(const QSslCertificate &cert, bool formatted=false)
Get 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())
Return 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)
Get 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()
Get short strings describing SSL errors.
static QString getCertTrustName(QgsAuthCertUtils::CertTrustPolicy trust)
Get the general name for certificate trust.
static QSslCertificate certFromFile(const QString &certpath)
Return first cert from a PEM or DER formatted file.
#define SSL_ISSUER_INFO(var, prop)
static QString getCaSourceName(QgsAuthCertUtils::CaCertSource source, bool single=false)
Get the general name for CA source enum type.
static QString resolvedCertName(const QSslCertificate &cert, bool issuer=false)
Get 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