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