QGIS API Documentation  2.12.0-Lyon
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 <QFile>
21 #include <QObject>
22 #include <QSslCertificate>
23 
24 #include "qgsauthmanager.h"
25 #include "qgslogger.h"
26 
27 QString QgsAuthCertUtils::getSslProtocolName( QSsl::SslProtocol protocol )
28 {
29  switch ( protocol )
30  {
31 #if QT_VERSION >= 0x040800
32  case QSsl::SecureProtocols:
33  return QObject::tr( "SecureProtocols" );
34  case QSsl::TlsV1SslV3:
35  return QObject::tr( "TlsV1SslV3" );
36 #endif
37  case QSsl::TlsV1:
38  return QObject::tr( "TlsV1" );
39  case QSsl::SslV3:
40  return QObject::tr( "SslV3" );
41  case QSsl::SslV2:
42  return QObject::tr( "SslV2" );
43  default:
44  return QString();
45  }
46 }
47 
49 {
51  Q_FOREACH ( const QSslCertificate& cert, certs )
52  {
53  digestmap.insert( shaHexForCert( cert ), cert );
54  }
55  return digestmap;
56 }
57 
59 {
61  Q_FOREACH ( const QSslCertificate& cert, certs )
62  {
63  QString org( SSL_SUBJECT_INFO( cert, QSslCertificate::Organization ) );
64  if ( org.isEmpty() )
65  org = "(Organization not defined)";
66  QList<QSslCertificate> valist = orgcerts.contains( org ) ? orgcerts.value( org ) : QList<QSslCertificate>();
67  orgcerts.insert( org, valist << cert );
68  }
69  return orgcerts;
70 }
71 
73 {
75  Q_FOREACH ( const QgsAuthConfigSslServer& config, configs )
76  {
77  digestmap.insert( shaHexForCert( config.sslCertificate() ), config );
78  }
79  return digestmap;
80 }
81 
83 {
85  Q_FOREACH ( const QgsAuthConfigSslServer& config, configs )
86  {
87  QString org( SSL_SUBJECT_INFO( config.sslCertificate(), QSslCertificate::Organization ) );
88 
89  if ( org.isEmpty() )
90  org = QObject::tr( "(Organization not defined)" );
91  QList<QgsAuthConfigSslServer> valist = orgconfigs.contains( org ) ? orgconfigs.value( org ) : QList<QgsAuthConfigSslServer>();
92  orgconfigs.insert( org, valist << config );
93  }
94  return orgconfigs;
95 }
96 
97 static QByteArray fileData_( const QString& path, bool astext = false )
98 {
99  QByteArray data;
100  QFile file( path );
101  if ( file.exists() )
102  {
103  QFile::OpenMode openflags( QIODevice::ReadOnly );
104  if ( astext )
105  openflags |= QIODevice::Text;
106  bool ret = file.open( openflags );
107  if ( ret )
108  {
109  data = file.readAll();
110  }
111  file.close();
112  }
113  return data;
114 }
115 
117 {
119  bool pem = certspath.endsWith( ".pem", Qt::CaseInsensitive );
120  certs = QSslCertificate::fromData( fileData_( certspath, pem ), pem ? QSsl::Pem : QSsl::Der );
121  if ( certs.isEmpty() )
122  {
123  QgsDebugMsg( QString( "Parsed cert(s) EMPTY for path: %1" ).arg( certspath ) );
124  }
125  return certs;
126 }
127 
129 {
130  QSslCertificate cert;
132  if ( certs.size() > 0 )
133  {
134  cert = certs.first();
135  }
136  if ( cert.isNull() )
137  {
138  QgsDebugMsg( QString( "Parsed cert is NULL for path: %1" ).arg( certpath ) );
139  }
140  return cert;
141 }
142 
144  const QString &keypass,
145  QString *algtype )
146 {
147  bool pem = keypath.endsWith( ".pem", Qt::CaseInsensitive );
148  QByteArray keydata( fileData_( keypath, pem ) );
149 
150  QSslKey clientkey;
151  clientkey = QSslKey( keydata,
152  QSsl::Rsa,
153  pem ? QSsl::Pem : QSsl::Der,
154  QSsl::PrivateKey,
155  !keypass.isEmpty() ? keypass.toUtf8() : QByteArray() );
156  if ( clientkey.isNull() )
157  {
158  // try DSA algorithm, since Qt can't seem to determine it otherwise
159  clientkey = QSslKey( keydata,
160  QSsl::Dsa,
161  pem ? QSsl::Pem : QSsl::Der,
162  QSsl::PrivateKey,
163  !keypass.isEmpty() ? keypass.toUtf8() : QByteArray() );
164  if ( clientkey.isNull() )
165  {
166  return QSslKey();
167  }
168  if ( algtype )
169  *algtype = "dsa";
170  }
171  else
172  {
173  if ( algtype )
174  *algtype = "rsa";
175  }
176 
177  return clientkey;
178 }
179 
181 {
183  certs = QSslCertificate::fromData( pemtext.toAscii(), QSsl::Pem );
184  if ( certs.isEmpty() )
185  {
186  QgsDebugMsg( "Parsed cert(s) EMPTY" );
187  }
188  return certs;
189 }
190 
192  const QString &keypath,
193  const QString &keypass,
194  bool reencrypt )
195 {
196  QString certpem;
197  QSslCertificate clientcert = QgsAuthCertUtils::certFromFile( certpath );
198  if ( !clientcert.isNull() )
199  {
200  certpem = QString( clientcert.toPem() );
201  }
202 
203  QString keypem;
204  QString algtype;
205  QSslKey clientkey = QgsAuthCertUtils::keyFromFile( keypath, keypass, &algtype );
206 
207  // reapply passphrase if protection is requested and passphrase exists
208  if ( !clientkey.isNull() )
209  {
210  keypem = QString( clientkey.toPem(( reencrypt && !keypass.isEmpty() ) ? keypass.toUtf8() : QByteArray() ) );
211  }
212 
213  return QStringList() << certpem << keypem << algtype;
214 }
215 
217  const QString &bundlepass,
218  bool reencrypt )
219 {
220  QStringList empty;
221  if ( !QCA::isSupported( "pkcs12" ) )
222  return empty;
223 
224  QCA::KeyBundle bundle( QgsAuthCertUtils::qcaKeyBundle( bundlepath, bundlepass ) );
225  if ( bundle.isNull() )
226  return empty;
227 
228  QCA::SecureArray passarray;
229  if ( reencrypt && !bundlepass.isEmpty() )
230  passarray = QCA::SecureArray( bundlepass.toUtf8() );
231 
232  QString algtype;
233  if ( bundle.privateKey().isRSA() )
234  {
235  algtype = "rsa";
236  }
237  else if ( bundle.privateKey().isDSA() )
238  {
239  algtype = "dsa";
240  }
241  else if ( bundle.privateKey().isDH() )
242  {
243  algtype = "dh";
244  }
245 
246  return QStringList() << bundle.certificateChain().primary().toPEM() << bundle.privateKey().toPEM( passarray ) << algtype;
247 }
248 
250 {
251  switch ( source )
252  {
253  case SystemRoot:
254  return single ? QObject::tr( "System Root CA" ) : QObject::tr( "System Root Authorities" );
255  case FromFile:
256  return single ? QObject::tr( "File CA" ) : QObject::tr( "Authorities from File" );
257  case InDatabase:
258  return single ? QObject::tr( "Database CA" ) : QObject::tr( "Authorities in Database" );
259  case Connection:
260  return single ? QObject::tr( "Connection CA" ) : QObject::tr( "Authorities from connection" );
261  default:
262  return QString();
263  }
264 }
265 
267 {
268  QString name( issuer ? SSL_ISSUER_INFO( cert, QSslCertificate::CommonName )
269  : SSL_SUBJECT_INFO( cert, QSslCertificate::CommonName ) );
270 
271  if ( name.isEmpty() )
272  name = issuer ? SSL_ISSUER_INFO( cert, QSslCertificate::OrganizationalUnitName )
273  : SSL_SUBJECT_INFO( cert, QSslCertificate::OrganizationalUnitName );
274 
275  if ( name.isEmpty() )
276  name = issuer ? SSL_ISSUER_INFO( cert, QSslCertificate::Organization )
277  : SSL_SUBJECT_INFO( cert, QSslCertificate::Organization );
278 
279  if ( name.isEmpty() )
280  name = issuer ? SSL_ISSUER_INFO( cert, QSslCertificate::LocalityName )
281  : SSL_SUBJECT_INFO( cert, QSslCertificate::LocalityName );
282 
283  if ( name.isEmpty() )
284  name = issuer ? SSL_ISSUER_INFO( cert, QSslCertificate::StateOrProvinceName )
285  : SSL_SUBJECT_INFO( cert, QSslCertificate::StateOrProvinceName );
286 
287  if ( name.isEmpty() )
288  name = issuer ? SSL_ISSUER_INFO( cert, QSslCertificate::CountryName )
289  : SSL_SUBJECT_INFO( cert, QSslCertificate::CountryName );
290 
291  return name;
292 }
293 
294 // private
295 void QgsAuthCertUtils::appendDirSegment_( QStringList &dirname,
296  const QString& segment, QString value )
297 {
298  if ( !value.isEmpty() )
299  {
300  dirname.append( segment + "=" + value.replace( ",", "\\," ) );
301  }
302 }
303 
305  const QCA::Certificate &acert ,
306  bool issuer )
307 {
308  if ( QgsAuthManager::instance()->isDisabled() )
309  return QString();
310 
311  if ( acert.isNull() )
312  {
313  QCA::ConvertResult res;
314  QCA::Certificate acert( QCA::Certificate::fromPEM( qcert.toPem(), &res, QString( "qca-ossl" ) ) );
315  if ( res != QCA::ConvertGood || acert.isNull() )
316  {
317  QgsDebugMsg( "Certificate could not be converted to QCA cert" );
318  return QString();
319  }
320  }
322  // CN=Boundless Test Root CA,
323  // OU=Certificate Authority,
324  // O=Boundless Test CA,
325  // L=District of Columbia,
326  // ST=Washington\, DC,
327  // C=US
328  QStringList dirname;
329  QgsAuthCertUtils::appendDirSegment_(
330  dirname, "E", issuer ? acert.issuerInfo().value( QCA::Email )
331  : acert.subjectInfo().value( QCA::Email ) );
332  QgsAuthCertUtils::appendDirSegment_(
333  dirname, "CN", issuer ? SSL_ISSUER_INFO( qcert, QSslCertificate::CommonName )
334  : SSL_SUBJECT_INFO( qcert, QSslCertificate::CommonName ) );
335  QgsAuthCertUtils::appendDirSegment_(
336  dirname, "OU", issuer ? SSL_ISSUER_INFO( qcert, QSslCertificate::OrganizationalUnitName )
337  : SSL_SUBJECT_INFO( qcert, QSslCertificate::OrganizationalUnitName ) );
338  QgsAuthCertUtils::appendDirSegment_(
339  dirname, "O", issuer ? SSL_ISSUER_INFO( qcert, QSslCertificate::Organization )
340  : SSL_SUBJECT_INFO( qcert, QSslCertificate::Organization ) );
341  QgsAuthCertUtils::appendDirSegment_(
342  dirname, "L", issuer ? SSL_ISSUER_INFO( qcert, QSslCertificate::LocalityName )
343  : SSL_SUBJECT_INFO( qcert, QSslCertificate::LocalityName ) );
344  QgsAuthCertUtils::appendDirSegment_(
345  dirname, "ST", issuer ? SSL_ISSUER_INFO( qcert, QSslCertificate::StateOrProvinceName )
346  : SSL_SUBJECT_INFO( qcert, QSslCertificate::StateOrProvinceName ) );
347  QgsAuthCertUtils::appendDirSegment_(
348  dirname, "C", issuer ? SSL_ISSUER_INFO( qcert, QSslCertificate::CountryName )
349  : SSL_SUBJECT_INFO( qcert, QSslCertificate::CountryName ) );
350 
351  return dirname.join( "," );
352 }
353 
355 {
356  switch ( trust )
357  {
358  case DefaultTrust:
359  return QObject::tr( "Default" );
360  case Trusted:
361  return QObject::tr( "Trusted" );
362  case Untrusted:
363  return QObject::tr( "Untrusted" );
364  default:
365  return QString();
366  }
367 }
368 
370 {
371  // 64321c05b0ebab8e2b67ec0d7d9e2b6d4bc3c303
372  // -> 64:32:1c:05:b0:eb:ab:8e:2b:67:ec:0d:7d:9e:2b:6d:4b:c3:c3:03
373  QStringList sl;
374  sl.reserve( txt.size() );
375  for ( int i = 0; i < txt.size(); i += 2 )
376  {
377  sl << txt.mid( i, ( i + 2 > txt.size() ) ? -1 : 2 );
378  }
379  return sl.join( ":" );
380 }
381 
383 {
384  QString sha( cert.digest( QCryptographicHash::Sha1 ).toHex() );
385  if ( formatted )
386  {
388  }
389  return sha;
390 }
391 
392 QCA::Certificate QgsAuthCertUtils::qtCertToQcaCert( const QSslCertificate &cert )
393 {
394  if ( QgsAuthManager::instance()->isDisabled() )
395  return QCA::Certificate();
396 
397  QCA::ConvertResult res;
398  QCA::Certificate qcacert( QCA::Certificate::fromPEM( cert.toPem(), &res, QString( "qca-ossl" ) ) );
399  if ( res != QCA::ConvertGood || qcacert.isNull() )
400  {
401  QgsDebugMsg( "Certificate could not be converted to QCA cert" );
402  qcacert = QCA::Certificate();
403  }
404  return qcacert;
405 }
406 
407 QCA::CertificateCollection QgsAuthCertUtils::qtCertsToQcaCollection( const QList<QSslCertificate> &certs )
408 {
409  QCA::CertificateCollection qcacoll;
410  if ( QgsAuthManager::instance()->isDisabled() )
411  return qcacoll;
412 
413  Q_FOREACH ( const QSslCertificate& cert, certs )
414  {
415  QCA::Certificate qcacert( qtCertToQcaCert( cert ) );
416  if ( !qcacert.isNull() )
417  {
418  qcacoll.addCertificate( qcacert );
419  }
420  }
421  return qcacoll;
422 }
423 
424 QCA::KeyBundle QgsAuthCertUtils::qcaKeyBundle( const QString &path, const QString &pass )
425 {
426  QCA::SecureArray passarray;
427  if ( !pass.isEmpty() )
428  passarray = QCA::SecureArray( pass.toUtf8() );
429 
430  QCA::ConvertResult res;
431  QCA::KeyBundle bundle( QCA::KeyBundle::fromFile( path, passarray, &res, QString( "qca-ossl" ) ) );
432 
433  return ( res == QCA::ConvertGood ? bundle : QCA::KeyBundle() );
434 }
435 
437 {
438  switch ( validity )
439  {
440  case QCA::ValidityGood:
441  return QObject::tr( "Certificate is valid." );
442  case QCA::ErrorRejected:
443  return QObject::tr( "Root CA rejected the certificate purpose." );
444  case QCA::ErrorUntrusted:
445  return QObject::tr( "Certificate is not trusted." );
446  case QCA::ErrorSignatureFailed:
447  return QObject::tr( "Signature does not match." );
448  case QCA::ErrorInvalidCA:
449  return QObject::tr( "Certificate Authority is invalid or not found." );
450  case QCA::ErrorInvalidPurpose:
451  return QObject::tr( "Purpose does not match the intended usage." );
452  case QCA::ErrorSelfSigned:
453  return QObject::tr( "Certificate is self-signed, and is not found in the list of trusted certificates." );
454  case QCA::ErrorRevoked:
455  return QObject::tr( "Certificate has been revoked." );
456  case QCA::ErrorPathLengthExceeded:
457  return QObject::tr( "Path length from the root CA to this certificate is too long." );
458  case QCA::ErrorExpired:
459  return QObject::tr( "Certificate has expired or is not yet valid." );
460  case QCA::ErrorExpiredCA:
461  return QObject::tr( "Certificate Authority has expired." );
462  case QCA::ErrorValidityUnknown:
463  return QObject::tr( "Validity is unknown." );
464  default:
465  return QString();
466  }
467 }
468 
469 QString QgsAuthCertUtils::qcaSignatureAlgorithm( QCA::SignatureAlgorithm algorithm )
470 {
471  switch ( algorithm )
472  {
473  case QCA::EMSA1_SHA1:
474  return QObject::tr( "SHA1, with EMSA1" );
475  case QCA::EMSA3_SHA1:
476  return QObject::tr( "SHA1, with EMSA3" );
477  case QCA::EMSA3_MD5:
478  return QObject::tr( "MD5, with EMSA3" );
479  case QCA::EMSA3_MD2:
480  return QObject::tr( "MD2, with EMSA3" );
481  case QCA::EMSA3_RIPEMD160:
482  return QObject::tr( "RIPEMD160, with EMSA3" );
483  case QCA::EMSA3_Raw:
484  return QObject::tr( "EMSA3, without digest" );
485 #if QCA_VERSION >= 0x020100
486  case QCA::EMSA3_SHA224:
487  return QObject::tr( "SHA224, with EMSA3" );
488  case QCA::EMSA3_SHA256:
489  return QObject::tr( "SHA256, with EMSA3" );
490  case QCA::EMSA3_SHA384:
491  return QObject::tr( "SHA384, with EMSA3" );
492  case QCA::EMSA3_SHA512:
493  return QObject::tr( "SHA512, with EMSA3" );
494 #endif
495  default:
496  return QObject::tr( "Unknown (possibly Elliptic Curve)" );
497  }
498 }
499 
500 QString QgsAuthCertUtils::qcaKnownConstraint( QCA::ConstraintTypeKnown constraint )
501 {
502  switch ( constraint )
503  {
504  case QCA::DigitalSignature:
505  return QObject::tr( "Digital Signature" );
506  case QCA::NonRepudiation:
507  return QObject::tr( "Non-repudiation" );
508  case QCA::KeyEncipherment:
509  return QObject::tr( "Key Encipherment" );
510  case QCA::DataEncipherment:
511  return QObject::tr( "Data Encipherment" );
512  case QCA::KeyAgreement:
513  return QObject::tr( "Key Agreement" );
514  case QCA::KeyCertificateSign:
515  return QObject::tr( "Key Certificate Sign" );
516  case QCA::CRLSign:
517  return QObject::tr( "CRL Sign" );
518  case QCA::EncipherOnly:
519  return QObject::tr( "Encipher Only" );
520  case QCA::DecipherOnly:
521  return QObject::tr( "Decipher Only" );
522  case QCA::ServerAuth:
523  return QObject::tr( "Server Authentication" );
524  case QCA::ClientAuth:
525  return QObject::tr( "Client Authentication" );
526  case QCA::CodeSigning:
527  return QObject::tr( "Code Signing" );
528  case QCA::EmailProtection:
529  return QObject::tr( "Email Protection" );
530  case QCA::IPSecEndSystem:
531  return QObject::tr( "IPSec Endpoint" );
532  case QCA::IPSecTunnel:
533  return QObject::tr( "IPSec Tunnel" );
534  case QCA::IPSecUser:
535  return QObject::tr( "IPSec User" );
536  case QCA::TimeStamping:
537  return QObject::tr( "Time Stamping" );
538  case QCA::OCSPSigning:
539  return QObject::tr( "OCSP Signing" );
540  default:
541  return QString();
542  }
543 }
544 
546 {
547  switch ( usagetype )
548  {
550  return QObject::tr( "Any or unspecified" );
552  return QObject::tr( "Certificate Authority" );
554  return QObject::tr( "Certificate Issuer" );
556  return QObject::tr( "TLS/SSL Server" );
558  return QObject::tr( "TLS/SSL Server EV" );
560  return QObject::tr( "TLS/SSL Client" );
562  return QObject::tr( "Code Signing" );
564  return QObject::tr( "Email Protection" );
566  return QObject::tr( "Time Stamping" );
568  return QObject::tr( "CRL Signing" );
570  default:
571  return QObject::tr( "Undetermined usage" );
572  }
573 }
574 
576 {
578 
579  if ( QgsAuthManager::instance()->isDisabled() )
580  return usages;
581 
582  QCA::ConvertResult res;
583  QCA::Certificate qcacert( QCA::Certificate::fromPEM( cert.toPem(), &res, QString( "qca-ossl" ) ) );
584  if ( res != QCA::ConvertGood || qcacert.isNull() )
585  {
586  QgsDebugMsg( "Certificate could not be converted to QCA cert" );
587  return usages;
588  }
589 
590  if ( qcacert.isCA() )
591  {
592  QgsDebugMsg( "Certificate has 'CA:TRUE' basic constraint" );
594  }
595 
596  QList<QCA::ConstraintType> certconsts = qcacert.constraints();
597  Q_FOREACH ( const QCA::ConstraintType& certconst, certconsts )
598  {
599  if ( certconst.known() == QCA::KeyCertificateSign )
600  {
601  QgsDebugMsg( "Certificate has 'Certificate Sign' key usage" );
603  }
604  else if ( certconst.known() == QCA::ServerAuth )
605  {
606  QgsDebugMsg( "Certificate has 'server authentication' extended key usage" );
608  }
609  }
610 
611  // ask QCA what it thinks about potential usages
612  QCA::CertificateCollection trustedCAs(
613  qtCertsToQcaCollection( QgsAuthManager::instance()->getTrustedCaCertsCache() ) );
614  QCA::CertificateCollection untrustedCAs(
615  qtCertsToQcaCollection( QgsAuthManager::instance()->getUntrustedCaCerts() ) );
616 
617  QCA::Validity v_any;
618  v_any = qcacert.validate( trustedCAs, untrustedCAs, QCA::UsageAny, QCA::ValidateAll );
619  if ( v_any == QCA::ValidityGood )
620  {
622  }
623 
624  QCA::Validity v_tlsserver;
625  v_tlsserver = qcacert.validate( trustedCAs, untrustedCAs, QCA::UsageTLSServer, QCA::ValidateAll );
626  if ( v_tlsserver == QCA::ValidityGood )
627  {
629  {
631  }
632  }
633 
634  // TODO: why doesn't this tag client certs?
635  // always seems to return QCA::ErrorInvalidPurpose (enum #5)
636  QCA::Validity v_tlsclient;
637  v_tlsclient = qcacert.validate( trustedCAs, untrustedCAs, QCA::UsageTLSClient, QCA::ValidateAll );
638  //QgsDebugMsg( QString( "QCA::UsageTLSClient validity: %1" ).arg( ( int )v_tlsclient ) );
639  if ( v_tlsclient == QCA::ValidityGood )
640  {
642  }
643 
644  // TODO: add TlsServerEvUsage, CodeSigningUsage, EmailProtectionUsage, TimeStampingUsage, CRLSigningUsage
645  // as they become necessary, since we do not want the overhead of checking just yet.
646 
647  return usages;
648 }
649 
651 {
653 }
654 
656 {
658 }
659 
661 {
664 }
665 
667 {
670 }
671 
672 #if 0
674 {
675  // TODO: There is no difinitive method for strictly enforcing what determines an SSL server cert;
676  // only what it should not be able to do (cert sign, etc.). The logic here may need refined
677  // see: http://security.stackexchange.com/a/26650
678 
679  if ( QgsAuthManager::instance()->isDisabled() )
680  return false;
681 
682  QCA::ConvertResult res;
683  QCA::Certificate qcacert( QCA::Certificate::fromPEM( cert.toPem(), &res, QString( "qca-ossl" ) ) );
684  if ( res != QCA::ConvertGood || qcacert.isNull() )
685  {
686  QgsDebugMsg( "Certificate could not be converted to QCA cert" );
687  return false;
688  }
689 
690  if ( qcacert.isCA() )
691  {
692  QgsDebugMsg( "SSL server certificate has 'CA:TRUE' basic constraint (and should not)" );
693  return false;
694  }
695 
696  QList<QCA::ConstraintType> certconsts = qcacert.constraints();
697  Q_FOREACH ( QCA::ConstraintType certconst, certconsts )
698  {
699  if ( certconst.known() == QCA::KeyCertificateSign )
700  {
701  QgsDebugMsg( "SSL server certificate has 'Certificate Sign' key usage (and should not)" );
702  return false;
703  }
704  }
705 
706  // check for common key usage and extended key usage constraints
707  // see: https://www.ietf.org/rfc/rfc3280.txt 4.2.1.3(Key Usage) and 4.2.1.13(Extended Key Usage)
708  bool serverauth = false;
709  bool dsignature = false;
710  bool keyencrypt = false;
711  Q_FOREACH ( QCA::ConstraintType certconst, certconsts )
712  {
713  if ( certconst.known() == QCA::DigitalSignature )
714  {
715  QgsDebugMsg( "SSL server certificate has 'digital signature' key usage" );
716  dsignature = true;
717  }
718  else if ( certconst.known() == QCA::KeyEncipherment )
719  {
720  QgsDebugMsg( "SSL server certificate has 'key encipherment' key usage" );
721  keyencrypt = true;
722  }
723  else if ( certconst.known() == QCA::KeyAgreement )
724  {
725  QgsDebugMsg( "SSL server certificate has 'key agreement' key usage" );
726  keyencrypt = true;
727  }
728  else if ( certconst.known() == QCA::ServerAuth )
729  {
730  QgsDebugMsg( "SSL server certificate has 'server authentication' extended key usage" );
731  serverauth = true;
732  }
733  }
734  // From 4.2.1.13(Extended Key Usage):
735  // "If a certificate contains both a key usage extension and an extended
736  // key usage extension, then both extensions MUST be processed
737  // independently and the certificate MUST only be used for a purpose
738  // consistent with both extensions. If there is no purpose consistent
739  // with both extensions, then the certificate MUST NOT be used for any
740  // purpose."
741 
742  if ( serverauth && dsignature && keyencrypt )
743  {
744  return true;
745  }
746  if ( dsignature && keyencrypt )
747  {
748  return true;
749  }
750 
751  // lastly, check for DH key and key agreement
752  bool keyagree = false;
753  bool encipheronly = false;
754  bool decipheronly = false;
755 
756  QCA::PublicKey pubkey( qcacert.subjectPublicKey() );
757  // key size may be 0 for eliptical curve-based keys, in which case isDH() crashes QCA
758  if ( pubkey.bitSize() > 0 && pubkey.isDH() )
759  {
760  keyagree = pubkey.canKeyAgree();
761  if ( !keyagree )
762  {
763  return false;
764  }
765  Q_FOREACH ( QCA::ConstraintType certconst, certconsts )
766  {
767  if ( certconst.known() == QCA::EncipherOnly )
768  {
769  QgsDebugMsg( "SSL server public key has 'encipher only' key usage" );
770  encipheronly = true;
771  }
772  else if ( certconst.known() == QCA::DecipherOnly )
773  {
774  QgsDebugMsg( "SSL server public key has 'decipher only' key usage" );
775  decipheronly = true;
776  }
777  }
778  if ( !encipheronly && !decipheronly )
779  {
780  return true;
781  }
782  }
783  return false;
784 }
785 #endif
786 
788 {
790 }
791 
792 QString QgsAuthCertUtils::sslErrorEnumString( QSslError::SslError errenum )
793 {
794  switch ( errenum )
795  {
796  case QSslError::UnableToGetIssuerCertificate:
797  return QObject::tr( "Unable To Get Issuer Certificate" );
798  case QSslError::UnableToDecryptCertificateSignature:
799  return QObject::tr( "Unable To Decrypt Certificate Signature" );
800  case QSslError::UnableToDecodeIssuerPublicKey:
801  return QObject::tr( "Unable To Decode Issuer Public Key" );
802  case QSslError::CertificateSignatureFailed:
803  return QObject::tr( "Certificate Signature Failed" );
804  case QSslError::CertificateNotYetValid:
805  return QObject::tr( "Certificate Not Yet Valid" );
806  case QSslError::CertificateExpired:
807  return QObject::tr( "Certificate Expired" );
808  case QSslError::InvalidNotBeforeField:
809  return QObject::tr( "Invalid Not Before Field" );
810  case QSslError::InvalidNotAfterField:
811  return QObject::tr( "Invalid Not After Field" );
812  case QSslError::SelfSignedCertificate:
813  return QObject::tr( "Self-signed Certificate" );
814  case QSslError::SelfSignedCertificateInChain:
815  return QObject::tr( "Self-signed Certificate In Chain" );
816  case QSslError::UnableToGetLocalIssuerCertificate:
817  return QObject::tr( "Unable To Get Local Issuer Certificate" );
818  case QSslError::UnableToVerifyFirstCertificate:
819  return QObject::tr( "Unable To Verify First Certificate" );
820  case QSslError::CertificateRevoked:
821  return QObject::tr( "Certificate Revoked" );
822  case QSslError::InvalidCaCertificate:
823  return QObject::tr( "Invalid CA Certificate" );
824  case QSslError::PathLengthExceeded:
825  return QObject::tr( "Path Length Exceeded" );
826  case QSslError::InvalidPurpose:
827  return QObject::tr( "Invalid Purpose" );
828  case QSslError::CertificateUntrusted:
829  return QObject::tr( "Certificate Untrusted" );
830  case QSslError::CertificateRejected:
831  return QObject::tr( "Certificate Rejected" );
832  case QSslError::SubjectIssuerMismatch:
833  return QObject::tr( "Subject Issuer Mismatch" );
834  case QSslError::AuthorityIssuerSerialNumberMismatch:
835  return QObject::tr( "Authority Issuer Serial Number Mismatch" );
836  case QSslError::NoPeerCertificate:
837  return QObject::tr( "No Peer Certificate" );
838  case QSslError::HostNameMismatch:
839  return QObject::tr( "Host Name Mismatch" );
840  case QSslError::UnspecifiedError:
841  return QObject::tr( "Unspecified Error" );
842  case QSslError::CertificateBlacklisted:
843  return QObject::tr( "Certificate Blacklisted" );
844  case QSslError::NoError:
845  return QObject::tr( "No Error" );
846  case QSslError::NoSslSupport:
847  return QObject::tr( "No SSL Support" );
848  default:
849  return QString();
850  }
851 }
852 
854 {
856  errenums << qMakePair( QSslError::UnableToGetIssuerCertificate,
857  QgsAuthCertUtils::sslErrorEnumString( QSslError::UnableToGetIssuerCertificate ) );
858  errenums << qMakePair( QSslError::UnableToDecryptCertificateSignature,
859  QgsAuthCertUtils::sslErrorEnumString( QSslError::UnableToDecryptCertificateSignature ) );
860  errenums << qMakePair( QSslError::UnableToDecodeIssuerPublicKey,
861  QgsAuthCertUtils::sslErrorEnumString( QSslError::UnableToDecodeIssuerPublicKey ) );
862  errenums << qMakePair( QSslError::CertificateSignatureFailed,
863  QgsAuthCertUtils::sslErrorEnumString( QSslError::CertificateSignatureFailed ) );
864  errenums << qMakePair( QSslError::CertificateNotYetValid,
865  QgsAuthCertUtils::sslErrorEnumString( QSslError::CertificateNotYetValid ) );
866  errenums << qMakePair( QSslError::CertificateExpired,
867  QgsAuthCertUtils::sslErrorEnumString( QSslError::CertificateExpired ) );
868  errenums << qMakePair( QSslError::InvalidNotBeforeField,
869  QgsAuthCertUtils::sslErrorEnumString( QSslError::InvalidNotBeforeField ) );
870  errenums << qMakePair( QSslError::InvalidNotAfterField,
871  QgsAuthCertUtils::sslErrorEnumString( QSslError::InvalidNotAfterField ) );
872  errenums << qMakePair( QSslError::SelfSignedCertificate,
873  QgsAuthCertUtils::sslErrorEnumString( QSslError::SelfSignedCertificate ) );
874  errenums << qMakePair( QSslError::SelfSignedCertificateInChain,
875  QgsAuthCertUtils::sslErrorEnumString( QSslError::SelfSignedCertificateInChain ) );
876  errenums << qMakePair( QSslError::UnableToGetLocalIssuerCertificate,
877  QgsAuthCertUtils::sslErrorEnumString( QSslError::UnableToGetLocalIssuerCertificate ) );
878  errenums << qMakePair( QSslError::UnableToVerifyFirstCertificate,
879  QgsAuthCertUtils::sslErrorEnumString( QSslError::UnableToVerifyFirstCertificate ) );
880  errenums << qMakePair( QSslError::CertificateRevoked,
881  QgsAuthCertUtils::sslErrorEnumString( QSslError::CertificateRevoked ) );
882  errenums << qMakePair( QSslError::InvalidCaCertificate,
883  QgsAuthCertUtils::sslErrorEnumString( QSslError::InvalidCaCertificate ) );
884  errenums << qMakePair( QSslError::PathLengthExceeded,
885  QgsAuthCertUtils::sslErrorEnumString( QSslError::PathLengthExceeded ) );
886  errenums << qMakePair( QSslError::InvalidPurpose,
887  QgsAuthCertUtils::sslErrorEnumString( QSslError::InvalidPurpose ) );
888  errenums << qMakePair( QSslError::CertificateUntrusted,
889  QgsAuthCertUtils::sslErrorEnumString( QSslError::CertificateUntrusted ) );
890  errenums << qMakePair( QSslError::CertificateRejected,
891  QgsAuthCertUtils::sslErrorEnumString( QSslError::CertificateRejected ) );
892  errenums << qMakePair( QSslError::SubjectIssuerMismatch,
893  QgsAuthCertUtils::sslErrorEnumString( QSslError::SubjectIssuerMismatch ) );
894  errenums << qMakePair( QSslError::AuthorityIssuerSerialNumberMismatch,
895  QgsAuthCertUtils::sslErrorEnumString( QSslError::AuthorityIssuerSerialNumberMismatch ) );
896  errenums << qMakePair( QSslError::NoPeerCertificate,
897  QgsAuthCertUtils::sslErrorEnumString( QSslError::NoPeerCertificate ) );
898  errenums << qMakePair( QSslError::HostNameMismatch,
899  QgsAuthCertUtils::sslErrorEnumString( QSslError::HostNameMismatch ) );
900  errenums << qMakePair( QSslError::UnspecifiedError,
901  QgsAuthCertUtils::sslErrorEnumString( QSslError::UnspecifiedError ) );
902  errenums << qMakePair( QSslError::CertificateBlacklisted,
903  QgsAuthCertUtils::sslErrorEnumString( QSslError::CertificateBlacklisted ) );
904  return errenums;
905 }
typedef OpenMode
static QString certificateUsageTypeString(QgsAuthCertUtils::CertUsageType usagetype)
Certificate usage type strings per enum.
bool contains(const Key &key) const
static QMap< QString, QgsAuthConfigSslServer > mapDigestToSslConfigs(const QList< QgsAuthConfigSslServer > &configs)
Map SSL custom configs' certificate sha1 to custom config as simple cache.
static QgsAuthManager * instance()
Enforce singleton pattern.
static bool certificateIsIssuer(const QSslCertificate &cert)
Get whether a certificate can sign other certificates.
bool isNull() const
QByteArray toHex() const
static QString qcaKnownConstraint(QCA::ConstraintTypeKnown constraint)
Certificate well-known constraint strings per enum.
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
void reserve(int alloc)
CertTrustPolicy
Type of certificate trust policy.
static QString sslErrorEnumString(QSslError::SslError errenum)
Get short strings describing an SSL error.
int size() const
static QList< QSslCertificate > certsFromFile(const QString &certspath)
Return list of concatenated certs from a PEM or DER formatted file.
static QMap< QString, QSslCertificate > mapDigestToCerts(const QList< QSslCertificate > &certs)
Map certificate sha1 to certificate as simple cache.
Configuration container for SSL server connection exceptions or overrides.
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.
QString join(const QString &separator) const
bool exists() const
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.
QString tr(const char *sourceText, const char *disambiguation, int n)
int size() const
T value(int i) const
static QByteArray fileData_(const QString &path, bool astext=false)
CertUsageType
Type of certificate usage.
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.
void append(const T &value)
bool isNull() const
bool isEmpty() const
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...
CaCertSource
Type of CA certificate source.
bool isEmpty() const
static QString getCertDistinguishedName(const QSslCertificate &qcert, const QCA::Certificate &acert=QCA::Certificate(), bool issuer=false)
Get combined distinguished name for certificate.
QByteArray readAll()
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const
static QMap< QString, QList< QgsAuthConfigSslServer > > sslConfigsGroupedByOrg(const QList< QgsAuthConfigSslServer > &configs)
Map SSL custom configs' certificates to their oraganization.
QByteArray toPem() const
T & first()
const QSslCertificate sslCertificate() const
Server certificate object.
QList< QSslCertificate > fromData(const QByteArray &data, QSsl::EncodingFormat format)
virtual bool open(QFlags< QIODevice::OpenModeFlag > mode)
static bool certificateIsSslServer(const QSslCertificate &cert)
Get whether a certificate is probably used for a SSL server.
static QList< QSslCertificate > certsFromString(const QString &pemtext)
Return list of concatenated certs from a PEM Base64 text block.
static QString getSslProtocolName(QSsl::SslProtocol protocol)
SSL Protocol name strings per enum.
bool contains(const T &value) const
QByteArray digest(QCryptographicHash::Algorithm algorithm) const
virtual void close()
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.
QString & replace(int position, int n, QChar after)
QString mid(int position, int n) const
#define SSL_SUBJECT_INFO(var, prop)
static QString qcaSignatureAlgorithm(QCA::SignatureAlgorithm algorithm)
Certificate signature algorithm strings per enum.
static QSslKey keyFromFile(const QString &keypath, const QString &keypass=QString(), QString *algtype=0)
Return non-encrypted key from a PEM or DER formatted file.
QByteArray toPem(const QByteArray &passPhrase) const
static QMap< QString, QList< QSslCertificate > > certsGroupedByOrg(const QList< QSslCertificate > &certs)
Map certificates to their oraganization.
iterator insert(const Key &key, const T &value)
static QString getColonDelimited(const QString &txt)
Get string with colon delimeters 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.
QByteArray toAscii() const
static QString qcaValidityMessage(QCA::Validity validity)
Certificate validity check messages per enum.
const T value(const Key &key) const
QByteArray toUtf8() const