QGIS API Documentation  3.21.0-Master (5b68dc587e)
qgsauthcertificateinfo.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsauthcertificateinfo.cpp
3  ---------------------
4  begin : April 26, 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 
18 #include "qgsauthcertificateinfo.h"
19 #include "ui_qgsauthcertificateinfo.h"
20 
21 #include <QtCrypto>
22 #include <QDialogButtonBox>
23 #include <QLineEdit>
24 #include <QPlainTextEdit>
25 #include <QPushButton>
26 #include <QTextEdit>
27 
28 #include "qgsapplication.h"
29 #include "qgsauthcertutils.h"
30 #include "qgsauthguiutils.h"
31 #include "qgsauthmanager.h"
32 #include "qgslogger.h"
33 
34 
35 static void setItemBold_( QTreeWidgetItem *item )
36 {
37  item->setFirstColumnSpanned( true );
38  QFont secf( item->font( 0 ) );
39  secf.setBold( true );
40  item->setFont( 0, secf );
41 }
42 
43 static void removeChildren_( QTreeWidgetItem *item )
44 {
45  const auto constTakeChildren = item->takeChildren();
46  for ( QTreeWidgetItem *child : constTakeChildren )
47  {
48  delete child;
49  }
50 }
51 
52 QgsAuthCertInfo::QgsAuthCertInfo( const QSslCertificate &cert,
53  bool manageCertTrust,
54  QWidget *parent,
55  const QList<QSslCertificate> &connectionCAs )
56  : QWidget( parent )
57  , mConnectionCAs( connectionCAs )
58  , mDefaultItemForeground( QBrush() )
59  , mManageTrust( manageCertTrust )
60  , mTrustCacheRebuilt( false )
61  , mDefaultTrustPolicy( QgsAuthCertUtils::DefaultTrust )
62  , mCurrentTrustPolicy( QgsAuthCertUtils::DefaultTrust )
63 
64 {
65  if ( QgsApplication::authManager()->isDisabled() )
66  {
67  mAuthNotifyLayout = new QVBoxLayout;
68  this->setLayout( mAuthNotifyLayout );
69  mAuthNotify = new QLabel( QgsApplication::authManager()->disabledMessage(), this );
70  mAuthNotifyLayout->addWidget( mAuthNotify );
71  }
72  else
73  {
74  setupUi( this );
75  connect( btnSaveTrust, &QToolButton::clicked, this, &QgsAuthCertInfo::btnSaveTrust_clicked );
76 
77  lblError->setHidden( true );
78 
79  treeHierarchy->setRootIsDecorated( false );
80 
81  connect( treeHierarchy, &QTreeWidget::currentItemChanged,
82  this, &QgsAuthCertInfo::currentCertItemChanged );
83 
84  mCaCertsCache = QgsApplication::authManager()->caCertsCache();
85 
86  setUpCertDetailsTree();
87 
88  grpbxTrust->setVisible( mManageTrust );
89 
90  // trust policy is still queried, even if not managing the policy, so public getter will work
91  mDefaultTrustPolicy = QgsApplication::authManager()->defaultCertTrustPolicy();
92  mCurrentTrustPolicy = QgsAuthCertUtils::DefaultTrust;
93 
94  bool res;
95  res = populateQcaCertCollection();
96  if ( res )
97  res = setQcaCertificate( cert );
98  if ( res )
99  res = populateCertChain();
100  if ( res )
101  setCertHierarchy();
102 
103  connect( cmbbxTrust, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ),
104  this, &QgsAuthCertInfo::currentPolicyIndexChanged );
105  }
106 }
107 
108 void QgsAuthCertInfo::setupError( const QString &msg )
109 {
110  lblError->setVisible( true );
111  QString out = tr( "<b>Setup ERROR:</b>\n\n" );
112  out += msg;
113  lblError->setText( out );
114  lblError->setStyleSheet( QgsAuthGuiUtils::redTextStyleSheet() );
115 }
116 
117 void QgsAuthCertInfo::currentCertItemChanged( QTreeWidgetItem *current, QTreeWidgetItem *previous )
118 {
119  Q_UNUSED( previous )
120  updateCurrentCert( current );
121 }
122 
123 void QgsAuthCertInfo::updateCurrentCert( QTreeWidgetItem *item )
124 {
125  if ( !item )
126  item = treeHierarchy->currentItem();
127  if ( !item )
128  return;
129 
130  const int indx( item->data( 0, Qt::UserRole ).toInt() );
131  updateCurrentCertInfo( indx );
132 }
133 
134 bool QgsAuthCertInfo::populateQcaCertCollection()
135 {
136  const QList<QPair<QgsAuthCertUtils::CaCertSource, QSslCertificate> > &certpairs( mCaCertsCache.values() );
137  for ( int i = 0; i < certpairs.size(); ++i )
138  {
139  QCA::ConvertResult res;
140  const QCA::Certificate acert = QCA::Certificate::fromPEM( certpairs.at( i ).second.toPem(), &res, QStringLiteral( "qca-ossl" ) );
141  if ( res == QCA::ConvertGood && !acert.isNull() )
142  {
143  mCaCerts.addCertificate( acert );
144  }
145  }
146  if ( !mConnectionCAs.isEmpty() )
147  {
148  const auto constMConnectionCAs = mConnectionCAs;
149  for ( const QSslCertificate &cert : constMConnectionCAs )
150  {
151  QCA::ConvertResult res;
152  const QCA::Certificate acert = QCA::Certificate::fromPEM( cert.toPem(), &res, QStringLiteral( "qca-ossl" ) );
153  if ( res == QCA::ConvertGood && !acert.isNull() )
154  {
155  mCaCerts.addCertificate( acert );
156  }
157  }
158  }
159 
160  if ( mCaCerts.certificates().empty() )
161  {
162  setupError( tr( "Could not populate QCA certificate collection" ) );
163  return false;
164  }
165  return true;
166 }
167 
168 bool QgsAuthCertInfo::setQcaCertificate( const QSslCertificate &cert )
169 {
170  QCA::ConvertResult res;
171  mCert = QCA::Certificate::fromPEM( cert.toPem(), &res, QStringLiteral( "qca-ossl" ) );
172  if ( res != QCA::ConvertGood || mCert.isNull() )
173  {
174  setupError( tr( "Could not set QCA certificate" ) );
175  return false;
176  }
177  return true;
178 }
179 
180 bool QgsAuthCertInfo::populateCertChain()
181 {
182  const QCA::CertificateChain certchain( mCert );
183  QCA::Validity valid;
184  mACertChain = certchain.complete( mCaCerts.certificates(), &valid );
185  if ( valid != QCA::ValidityGood && valid != QCA::ErrorInvalidCA )
186  {
187  // invalid CAs are skipped to allow an incomplete chain
188  setupError( tr( "Invalid population of QCA certificate chain.<br><br>"
189  "Validity message: %1" ).arg( QgsAuthCertUtils::qcaValidityMessage( valid ) ) );
190  return false;
191  }
192 
193  if ( mACertChain.isEmpty() )
194  {
195  QgsDebugMsg( QStringLiteral( "Could not populate QCA certificate chain" ) );
196  mACertChain = certchain;
197  }
198 
199  if ( !mACertChain.last().isSelfSigned() )
200  {
201  // chain is incomplete, add null certificate to signify local missing parent CA
202  mACertChain.append( QCA::Certificate() );
203  }
204 
205  // mirror chain to QSslCertificate
206  const auto constMACertChain = mACertChain;
207  for ( const QCA::Certificate &cert : constMACertChain )
208  {
209  QSslCertificate qcert;
210  if ( !cert.isNull() )
211  {
212  qcert = QSslCertificate( cert.toPEM().toLatin1() );
213  }
214  mQCertChain.append( qcert );
215  }
216  return true;
217 }
218 
219 void QgsAuthCertInfo::setCertHierarchy()
220 {
221  QListIterator<QSslCertificate> it( mQCertChain );
222  it.toBack();
223  int i = mQCertChain.size();
224  QTreeWidgetItem *item = nullptr;
225  QTreeWidgetItem *previtem = nullptr;
226  while ( it.hasPrevious() )
227  {
228  const QSslCertificate cert( it.previous() );
229  const bool missingCA = cert.isNull();
230  QString cert_source;
231  if ( missingCA && it.hasPrevious() )
232  {
233  cert_source = QgsAuthCertUtils::resolvedCertName( it.peekPrevious(), true );
234  cert_source += QStringLiteral( " (%1)" ).arg( tr( "Missing CA" ) );
235  }
236  else
237  {
238  cert_source = QgsAuthCertUtils::resolvedCertName( cert );
239  const QString sha = QgsAuthCertUtils::shaHexForCert( cert );
240  if ( mCaCertsCache.contains( sha ) )
241  {
242  const QPair<QgsAuthCertUtils::CaCertSource, QSslCertificate > &certpair( mCaCertsCache.value( sha ) );
243  cert_source += QStringLiteral( " (%1)" ).arg( QgsAuthCertUtils::getCaSourceName( certpair.first, true ) );
244  }
245  else if ( mConnectionCAs.contains( cert ) )
246  {
247  cert_source += QStringLiteral( " (%1)" )
249  }
250  }
251 
252  if ( !previtem )
253  {
254  item = new QTreeWidgetItem( treeHierarchy, QStringList() << cert_source );
255  }
256  else
257  {
258  item = new QTreeWidgetItem( previtem, QStringList() << cert_source );
259  }
260  if ( missingCA && it.hasPrevious() )
261  {
262  item->setFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
263  }
264 
265  item->setData( 0, Qt::UserRole, --i );
266 
267  if ( mDefaultItemForeground.style() == Qt::NoBrush )
268  {
269  mDefaultItemForeground = item->foreground( 0 );
270  }
271 
272  decorateCertTreeItem( cert, QgsApplication::authManager()->certificateTrustPolicy( cert ), item );
273 
274  item->setFirstColumnSpanned( true );
275  if ( !previtem )
276  treeHierarchy->addTopLevelItem( item );
277  previtem = item;
278  }
279  treeHierarchy->setCurrentItem( item, 0, QItemSelectionModel::ClearAndSelect );
280  treeHierarchy->expandAll();
281 }
282 
283 void QgsAuthCertInfo::updateCurrentCertInfo( int chainindx )
284 {
285  btnSaveTrust->setEnabled( false );
286 
287  mCurrentQCert = mQCertChain.at( chainindx );
288  mCurrentACert = mACertChain.at( chainindx );
289 
290  if ( mManageTrust )
291  {
292  grpbxTrust->setHidden( mCurrentQCert.isNull() );
293  }
294 
295  if ( !mCurrentQCert.isNull() )
296  {
297  const QgsAuthCertUtils::CertTrustPolicy trustpolicy( QgsApplication::authManager()->certificateTrustPolicy( mCurrentQCert ) );
298  mCurrentTrustPolicy = trustpolicy;
299 
300  cmbbxTrust->setTrustPolicy( trustpolicy );
301  if ( !QgsAuthCertUtils::certIsViable( mCurrentQCert ) )
302  {
303  cmbbxTrust->setDefaultTrustPolicy( QgsAuthCertUtils::Untrusted );
304  }
305  }
306 
307  populateCertInfo();
308 }
309 
310 void QgsAuthCertInfo::setUpCertDetailsTree()
311 {
312  treeDetails->setColumnCount( 2 );
313  treeDetails->setHeaderLabels( QStringList() << tr( "Field" ) << tr( "Value" ) );
314  treeDetails->setColumnWidth( 0, 200 );
315 
316  QTreeWidgetItem *headeritem = treeDetails->headerItem();
317  headeritem->setTextAlignment( 0, Qt::AlignRight );
318  headeritem->setTextAlignment( 1, Qt::AlignLeft );
319 
320  treeDetails->setRootIsDecorated( true );
321  treeDetails->setWordWrap( true );
322 
323  // add root items
324  mSecGeneral = new QTreeWidgetItem(
325  treeDetails,
326  QStringList( tr( "General" ) ),
327  static_cast<int>( DetailsSection ) );
328  setItemBold_( mSecGeneral );
329  mSecGeneral->setFirstColumnSpanned( true );
330  mSecGeneral->setFlags( Qt::ItemIsEnabled );
331  mSecGeneral->setExpanded( true );
332  treeDetails->insertTopLevelItem( 0, mSecGeneral );
333 
334  mSecDetails = new QTreeWidgetItem(
335  treeDetails,
336  QStringList( tr( "Details" ) ),
337  static_cast<int>( DetailsSection ) );
338  setItemBold_( mSecDetails );
339  mSecDetails->setFirstColumnSpanned( true );
340  mSecDetails->setFlags( Qt::ItemIsEnabled );
341  mSecDetails->setExpanded( false );
342  treeDetails->insertTopLevelItem( 0, mSecDetails );
343 
344  // add details groups
345  mGrpSubj = addGroupItem( mSecDetails, tr( "Subject Info" ) );
346  mGrpIssu = addGroupItem( mSecDetails, tr( "Issuer Info" ) );
347  mGrpCert = addGroupItem( mSecDetails, tr( "Certificate Info" ) );
348  mGrpPkey = addGroupItem( mSecDetails, tr( "Public Key Info" ) );
349  mGrpExts = addGroupItem( mSecDetails, tr( "Extensions" ) );
350 
351  mSecPemText = new QTreeWidgetItem(
352  treeDetails,
353  QStringList( tr( "PEM Text" ) ),
354  static_cast<int>( DetailsSection ) );
355  setItemBold_( mSecPemText );
356  mSecPemText->setFirstColumnSpanned( true );
357  mSecPemText->setFlags( Qt::ItemIsEnabled );
358  mSecPemText->setExpanded( false );
359  treeDetails->insertTopLevelItem( 0, mSecPemText );
360 }
361 
362 void QgsAuthCertInfo::populateCertInfo()
363 {
364  mSecDetails->setHidden( false );
365  mSecPemText->setHidden( false );
366 
367  populateInfoGeneralSection();
368  populateInfoDetailsSection();
369  populateInfoPemTextSection();
370 }
371 
372 QTreeWidgetItem *QgsAuthCertInfo::addGroupItem( QTreeWidgetItem *parent, const QString &group )
373 {
374  QTreeWidgetItem *grpitem = new QTreeWidgetItem(
375  parent,
376  QStringList( group ),
377  static_cast<int>( DetailsGroup ) );
378 
379  grpitem->setFirstColumnSpanned( true );
380  grpitem->setFlags( Qt::ItemIsEnabled );
381  grpitem->setExpanded( true );
382 
383  QBrush orgb( grpitem->foreground( 0 ) );
384  orgb.setColor( QColor::fromRgb( 90, 90, 90 ) );
385  grpitem->setForeground( 0, orgb );
386  QFont grpf( grpitem->font( 0 ) );
387  grpf.setItalic( true );
388  grpitem->setFont( 0, grpf );
389 
390  return grpitem;
391 }
392 
393 void QgsAuthCertInfo::addFieldItem( QTreeWidgetItem *parent, const QString &field, const QString &value,
394  QgsAuthCertInfo::FieldWidget wdgt, const QColor &color )
395 {
396  if ( value.isEmpty() )
397  return;
398 
399  QTreeWidgetItem *item = new QTreeWidgetItem(
400  parent,
401  QStringList() << field << ( wdgt == NoWidget ? value : QString() ),
402  static_cast<int>( DetailsField ) );
403 
404  item->setTextAlignment( 0, Qt::AlignRight );
405  item->setTextAlignment( 1, Qt::AlignLeft );
406 
407  QBrush fieldb( item->foreground( 0 ) );
408  fieldb.setColor( QColor::fromRgb( 90, 90, 90 ) );
409  item->setForeground( 0, fieldb );
410 
411  if ( wdgt == NoWidget )
412  {
413  if ( color.isValid() )
414  {
415  QBrush valueb( item->foreground( 1 ) );
416  valueb.setColor( color );
417  item->setForeground( 1, valueb );
418  }
419  }
420  else if ( wdgt == LineEdit )
421  {
422  QLineEdit *le = new QLineEdit( value, treeDetails );
423  le->setReadOnly( true );
424  le->setAlignment( Qt::AlignLeft );
425  le->setCursorPosition( 0 );
426  if ( color.isValid() )
427  {
428  le->setStyleSheet( QStringLiteral( "QLineEdit { color: %1; }" ).arg( color.name() ) );
429  }
430  item->treeWidget()->setItemWidget( item, 1, le );
431  }
432  else if ( wdgt == TextEdit )
433  {
434  QPlainTextEdit *pte = new QPlainTextEdit( value, treeDetails );
435  pte->setReadOnly( true );
436  pte->setMinimumHeight( 75 );
437  pte->setMaximumHeight( 75 );
438  pte->moveCursor( QTextCursor::Start );
439  if ( color.isValid() )
440  {
441  pte->setStyleSheet( QStringLiteral( "QPlainTextEdit { color: %1; }" ).arg( color.name() ) );
442  }
443  item->treeWidget()->setItemWidget( item, 1, pte );
444  }
445 
446 }
447 
448 void QgsAuthCertInfo::populateInfoGeneralSection()
449 {
450  removeChildren_( mSecGeneral );
451 
452  if ( mCurrentQCert.isNull() )
453  {
454  addFieldItem( mSecGeneral, tr( "Type" ),
455  tr( "Missing CA (incomplete local CA chain)" ),
456  LineEdit );
457  mSecGeneral->setExpanded( true );
458  mSecDetails->setHidden( true );
459  mSecPemText->setHidden( true );
460  return;
461  }
462 
463  QString certype;
464  const bool isselfsigned = mCurrentACert.isSelfSigned();
465  const QString selfsigned( tr( "self-signed" ) );
466 
467  const QList<QgsAuthCertUtils::CertUsageType> usagetypes(
468  QgsAuthCertUtils::certificateUsageTypes( mCurrentQCert ) );
469  const bool isca = usagetypes.contains( QgsAuthCertUtils::CertAuthorityUsage );
470  const bool isissuer = usagetypes.contains( QgsAuthCertUtils::CertIssuerUsage );
471  const bool issslserver = usagetypes.contains( QgsAuthCertUtils::TlsServerUsage );
472  const bool issslclient = usagetypes.contains( QgsAuthCertUtils::TlsClientUsage );
473 
474  if ( issslclient )
475  {
477  }
478  if ( issslserver )
479  {
481  }
482  if ( isissuer || ( isca && !isselfsigned ) )
483  {
485  }
486  if ( ( isissuer || isca ) && isselfsigned )
487  {
488  certype = QStringLiteral( "%1 %2" )
489  .arg( tr( "Root" ),
491  }
492  if ( isselfsigned )
493  {
494  certype.append( certype.isEmpty() ? selfsigned : QStringLiteral( " (%1)" ).arg( selfsigned ) );
495  }
496 
497  addFieldItem( mSecGeneral, tr( "Usage type" ),
498  certype,
499  LineEdit );
500  addFieldItem( mSecGeneral, tr( "Subject" ),
501  QgsAuthCertUtils::resolvedCertName( mCurrentQCert ),
502  LineEdit );
503  addFieldItem( mSecGeneral, tr( "Issuer" ),
504  QgsAuthCertUtils::resolvedCertName( mCurrentQCert, true ),
505  LineEdit );
506  addFieldItem( mSecGeneral, tr( "Not valid after" ),
507  mCurrentQCert.expiryDate().toString(),
508  LineEdit,
509  mCurrentQCert.expiryDate() < QDateTime::currentDateTime() ? QgsAuthGuiUtils::redColor() : QColor() );
510 
511  const QSslKey pubkey( mCurrentQCert.publicKey() );
512  const QString alg( pubkey.algorithm() == QSsl::Rsa ? "RSA" : "DSA" );
513  const int bitsize( pubkey.length() );
514  addFieldItem( mSecGeneral, tr( "Public key" ),
515  QStringLiteral( "%1, %2 bits" ).arg( alg, bitsize == -1 ? QStringLiteral( "?" ) : QString::number( bitsize ) ),
516  LineEdit );
517  addFieldItem( mSecGeneral, tr( "Signature algorithm" ),
518  QgsAuthCertUtils::qcaSignatureAlgorithm( mCurrentACert.signatureAlgorithm() ),
519  LineEdit );
520 }
521 
522 void QgsAuthCertInfo::populateInfoDetailsSection()
523 {
524  removeChildren_( mGrpSubj );
525  removeChildren_( mGrpIssu );
526  removeChildren_( mGrpCert );
527  removeChildren_( mGrpPkey );
528  removeChildren_( mGrpExts );
529 
530  if ( mCurrentQCert.isNull() )
531  return;
532 
533  // Subject Info
534  addFieldItem( mGrpSubj, tr( "Country (C)" ),
535  SSL_SUBJECT_INFO( mCurrentQCert, QSslCertificate::CountryName ),
536  LineEdit );
537  addFieldItem( mGrpSubj, tr( "State/Province (ST)" ),
538  SSL_SUBJECT_INFO( mCurrentQCert, QSslCertificate::StateOrProvinceName ),
539  LineEdit );
540  addFieldItem( mGrpSubj, tr( "Locality (L)" ),
541  SSL_SUBJECT_INFO( mCurrentQCert, QSslCertificate::LocalityName ),
542  LineEdit );
543  addFieldItem( mGrpSubj, tr( "Organization (O)" ),
544  SSL_SUBJECT_INFO( mCurrentQCert, QSslCertificate::Organization ),
545  LineEdit );
546  addFieldItem( mGrpSubj, tr( "Organizational unit (OU)" ),
547  SSL_SUBJECT_INFO( mCurrentQCert, QSslCertificate::OrganizationalUnitName ),
548  LineEdit );
549  addFieldItem( mGrpSubj, tr( "Common name (CN)" ),
550  SSL_SUBJECT_INFO( mCurrentQCert, QSslCertificate::CommonName ),
551  LineEdit );
552  addFieldItem( mGrpSubj, tr( "Email address (E)" ),
553  mCurrentACert.subjectInfo().value( QCA::Email ),
554  LineEdit );
555  addFieldItem( mGrpSubj, tr( "Distinguished name" ),
556  QgsAuthCertUtils::getCertDistinguishedName( mCurrentQCert, mCurrentACert, false ),
557  LineEdit );
558  addFieldItem( mGrpSubj, tr( "Email Legacy" ),
559  mCurrentACert.subjectInfo().value( QCA::EmailLegacy ),
560  LineEdit );
561  addFieldItem( mGrpSubj, tr( "Incorporation Country" ),
562  mCurrentACert.subjectInfo().value( QCA::IncorporationCountry ),
563  LineEdit );
564  addFieldItem( mGrpSubj, tr( "Incorporation State/Province" ),
565  mCurrentACert.subjectInfo().value( QCA::IncorporationState ),
566  LineEdit );
567  addFieldItem( mGrpSubj, tr( "Incorporation Locality" ),
568  mCurrentACert.subjectInfo().value( QCA::IncorporationLocality ),
569  LineEdit );
570  addFieldItem( mGrpSubj, tr( "URI" ),
571  mCurrentACert.subjectInfo().value( QCA::URI ),
572  LineEdit );
573  addFieldItem( mGrpSubj, tr( "DNS" ),
574  mCurrentACert.subjectInfo().value( QCA::DNS ),
575  LineEdit );
576  addFieldItem( mGrpSubj, tr( "IP Address" ),
577  mCurrentACert.subjectInfo().value( QCA::IPAddress ),
578  LineEdit );
579  addFieldItem( mGrpSubj, tr( "XMPP" ),
580  mCurrentACert.subjectInfo().value( QCA::XMPP ),
581  LineEdit );
582 
583  const QMultiMap<QSsl::AlternativeNameEntryType, QString> alts( mCurrentQCert.subjectAlternativeNames() );
584  QStringList altslist;
585  const QString email( tr( "Email: " ) );
586  const QStringList emails( alts.values( QSsl::EmailEntry ) );
587  if ( !emails.isEmpty() )
588  {
589  altslist << email + emails.join( '\n' + email );
590  }
591  const QString dns( tr( "DNS: " ) );
592  const QStringList dnss( alts.values( QSsl::DnsEntry ) );
593  if ( !dnss.isEmpty() )
594  {
595  altslist << dns + dnss.join( '\n' + dns );
596  }
597  addFieldItem( mGrpSubj, tr( "Alternate names" ),
598  altslist.join( QLatin1Char( '\n' ) ),
599  TextEdit );
600 
601  // Issuer Info
602  addFieldItem( mGrpIssu, tr( "Country (C)" ),
603  SSL_ISSUER_INFO( mCurrentQCert, QSslCertificate::CountryName ),
604  LineEdit );
605  addFieldItem( mGrpIssu, tr( "State/Province (ST)" ),
606  SSL_ISSUER_INFO( mCurrentQCert, QSslCertificate::StateOrProvinceName ),
607  LineEdit );
608  addFieldItem( mGrpIssu, tr( "Locality (L)" ),
609  SSL_ISSUER_INFO( mCurrentQCert, QSslCertificate::LocalityName ),
610  LineEdit );
611  addFieldItem( mGrpIssu, tr( "Organization (O)" ),
612  SSL_ISSUER_INFO( mCurrentQCert, QSslCertificate::Organization ),
613  LineEdit );
614  addFieldItem( mGrpIssu, tr( "Organizational unit (OU)" ),
615  SSL_ISSUER_INFO( mCurrentQCert, QSslCertificate::OrganizationalUnitName ),
616  LineEdit );
617  addFieldItem( mGrpIssu, tr( "Common name (CN)" ),
618  SSL_ISSUER_INFO( mCurrentQCert, QSslCertificate::CommonName ),
619  LineEdit );
620  addFieldItem( mGrpIssu, tr( "Email address (E)" ),
621  mCurrentACert.issuerInfo().value( QCA::Email ),
622  LineEdit );
623  addFieldItem( mGrpIssu, tr( "Distinguished name" ),
624  QgsAuthCertUtils::getCertDistinguishedName( mCurrentQCert, mCurrentACert, true ),
625  LineEdit );
626  addFieldItem( mGrpIssu, tr( "Email Legacy" ),
627  mCurrentACert.issuerInfo().value( QCA::EmailLegacy ),
628  LineEdit );
629  addFieldItem( mGrpIssu, tr( "Incorporation Country" ),
630  mCurrentACert.issuerInfo().value( QCA::IncorporationCountry ),
631  LineEdit );
632  addFieldItem( mGrpIssu, tr( "Incorporation State/Province" ),
633  mCurrentACert.issuerInfo().value( QCA::IncorporationState ),
634  LineEdit );
635  addFieldItem( mGrpIssu, tr( "Incorporation Locality" ),
636  mCurrentACert.issuerInfo().value( QCA::IncorporationLocality ),
637  LineEdit );
638  addFieldItem( mGrpIssu, tr( "URI" ),
639  mCurrentACert.issuerInfo().value( QCA::URI ),
640  LineEdit );
641  addFieldItem( mGrpIssu, tr( "DNS" ),
642  mCurrentACert.issuerInfo().value( QCA::DNS ),
643  LineEdit );
644  addFieldItem( mGrpIssu, tr( "IP Address" ),
645  mCurrentACert.issuerInfo().value( QCA::IPAddress ),
646  LineEdit );
647  addFieldItem( mGrpIssu, tr( "XMPP" ),
648  mCurrentACert.issuerInfo().value( QCA::XMPP ),
649  LineEdit );
650 
651  // Certificate Info
652  addFieldItem( mGrpCert, tr( "Version" ),
653  mCurrentQCert.version(),
654  LineEdit );
655  addFieldItem( mGrpCert, tr( "Serial #" ),
656  mCurrentQCert.serialNumber(),
657  LineEdit );
658  addFieldItem( mGrpCert, tr( "Not valid before" ),
659  mCurrentQCert.effectiveDate().toString(),
660  LineEdit,
661  mCurrentQCert.effectiveDate() > QDateTime::currentDateTime() ? QgsAuthGuiUtils::redColor() : QColor() );
662  addFieldItem( mGrpCert, tr( "Not valid after" ),
663  mCurrentQCert.expiryDate().toString(),
664  LineEdit,
665  mCurrentQCert.expiryDate() < QDateTime::currentDateTime() ? QgsAuthGuiUtils::redColor() : QColor() );
666  addFieldItem( mGrpCert, tr( "Signature algorithm" ),
667  QgsAuthCertUtils::qcaSignatureAlgorithm( mCurrentACert.signatureAlgorithm() ),
668  LineEdit );
669  addFieldItem( mGrpCert, tr( "MD5 fingerprint" ),
670  QgsAuthCertUtils::getColonDelimited( mCurrentQCert.digest().toHex().toUpper() ),
671  LineEdit );
672  addFieldItem( mGrpCert, tr( "SHA1 fingerprint" ),
673  QgsAuthCertUtils::shaHexForCert( mCurrentQCert, true ).toUpper(),
674  LineEdit );
675 
676  const QStringList crllocs( mCurrentACert.crlLocations() );
677  if ( !crllocs.isEmpty() )
678  {
679  addFieldItem( mGrpCert, tr( "CRL locations" ),
680  crllocs.join( QLatin1Char( '\n' ) ),
681  TextEdit );
682  }
683  const QStringList issulocs( mCurrentACert.issuerLocations() );
684  if ( !issulocs.isEmpty() )
685  {
686  addFieldItem( mGrpCert, tr( "Issuer locations" ),
687  issulocs.join( QLatin1Char( '\n' ) ),
688  TextEdit );
689  }
690  const QStringList ocsplocs( mCurrentACert.ocspLocations() );
691  if ( !ocsplocs.isEmpty() )
692  {
693  addFieldItem( mGrpCert, tr( "OCSP locations" ),
694  ocsplocs.join( QLatin1Char( '\n' ) ),
695  TextEdit );
696  }
697 
698  // Public Key Info
699  // TODO: handle ECC (Elliptic Curve) keys when Qt supports them
700  const QSslKey pubqkey( mCurrentQCert.publicKey() );
701  const QString alg( pubqkey.algorithm() == QSsl::Rsa ? "RSA" : "DSA" );
702  const int bitsize( pubqkey.length() );
703  addFieldItem( mGrpPkey, tr( "Algorithm" ),
704  bitsize == -1 ? QStringLiteral( "Unknown (possibly Elliptic Curve)" ) : alg,
705  LineEdit );
706  addFieldItem( mGrpPkey, tr( "Key size" ),
707  bitsize == -1 ? QStringLiteral( "?" ) : QString::number( bitsize ),
708  LineEdit );
709  if ( bitsize > 0 ) // ECC keys unsupported by Qt/QCA, so returned key size is 0
710  {
711  const QCA::PublicKey pubakey( mCurrentACert.subjectPublicKey() );
712 
713  if ( pubqkey.algorithm() == QSsl::Rsa )
714  {
715  const QCA::RSAPublicKey rsakey( pubakey.toRSA() );
716  const QCA::BigInteger modulus = rsakey.n();
717  QByteArray modarray( modulus.toArray().toByteArray().toHex() );
718  if ( modarray.size() > 2 && modarray.mid( 0, 2 ) == QByteArray( "00" ) )
719  {
720  modarray = modarray.mid( 2 );
721  }
722  const QCA::BigInteger exponent = rsakey.e();
723  addFieldItem( mGrpPkey, tr( "Public key" ),
724  QgsAuthCertUtils::getColonDelimited( modarray ).toUpper(),
725  TextEdit );
726  addFieldItem( mGrpPkey, tr( "Exponent" ),
727  exponent.toString(),
728  LineEdit );
729  }
730  // TODO: how is DSA textually represented using QCA?
731  // QCA::DSAPublicKey dsakey( pubakey.toDSA() );
732 
733  // TODO: how to get the signature and show it as hex?
734  // addFieldItem( mGrpPkey, tr( "Signature" ),
735  // mCurrentACert.???.toHex(),
736  // TextEdit );
737 
738  // key usage
739  QStringList usage;
740  if ( pubakey.canVerify() )
741  {
742  usage.append( tr( "Verify" ) );
743  }
744 
745  // TODO: these two seem to always show up, why?
746  if ( pubakey.canEncrypt() )
747  {
748  usage.append( tr( "Encrypt" ) );
749  }
750 #if QCA_VERSION >= 0x020100
751  if ( pubakey.canDecrypt() )
752  {
753  usage.append( tr( "Decrypt" ) );
754  }
755 #endif
756  if ( pubakey.canKeyAgree() )
757  {
758  usage.append( tr( "Key agreement" ) );
759  }
760  if ( pubakey.canExport() )
761  {
762  usage.append( tr( "Export" ) );
763  }
764  if ( !usage.isEmpty() )
765  {
766  addFieldItem( mGrpPkey, tr( "Key usage" ),
767  usage.join( QLatin1String( ", " ) ),
768  LineEdit );
769  }
770  }
771 
772  // Extensions
773  QStringList basicconst;
774  basicconst << tr( "Certificate Authority: %1" ).arg( mCurrentACert.isCA() ? tr( "Yes" ) : tr( "No" ) )
775  << tr( "Chain Path Limit: %1" ).arg( mCurrentACert.pathLimit() );
776  addFieldItem( mGrpExts, tr( "Basic constraints" ),
777  basicconst.join( QLatin1Char( '\n' ) ),
778  TextEdit );
779 
780  QStringList keyusage;
781  QStringList extkeyusage;
782  const QList<QCA::ConstraintType> certconsts = mCurrentACert.constraints();
783  const auto constCertconsts = certconsts;
784  for ( const QCA::ConstraintType &certconst : constCertconsts )
785  {
786  if ( certconst.section() == QCA::ConstraintType::KeyUsage )
787  {
788  keyusage.append( QgsAuthCertUtils::qcaKnownConstraint( certconst.known() ) );
789  }
790  else if ( certconst.section() == QCA::ConstraintType::ExtendedKeyUsage )
791  {
792  extkeyusage.append( QgsAuthCertUtils::qcaKnownConstraint( certconst.known() ) );
793  }
794  }
795  if ( !keyusage.isEmpty() )
796  {
797  addFieldItem( mGrpExts, tr( "Key usage" ),
798  keyusage.join( QLatin1Char( '\n' ) ),
799  TextEdit );
800  }
801  if ( !extkeyusage.isEmpty() )
802  {
803  addFieldItem( mGrpExts, tr( "Extended key usage" ),
804  extkeyusage.join( QLatin1Char( '\n' ) ),
805  TextEdit );
806  }
807 
808  addFieldItem( mGrpExts, tr( "Subject key ID" ),
809  QgsAuthCertUtils::getColonDelimited( mCurrentACert.subjectKeyId().toHex() ).toUpper(),
810  LineEdit );
811  addFieldItem( mGrpExts, tr( "Authority key ID" ),
812  QgsAuthCertUtils::getColonDelimited( mCurrentACert.issuerKeyId().toHex() ).toUpper(),
813  LineEdit );
814 }
815 
816 void QgsAuthCertInfo::populateInfoPemTextSection()
817 {
818  removeChildren_( mSecPemText );
819 
820  if ( mCurrentQCert.isNull() )
821  return;
822 
823  QTreeWidgetItem *item = new QTreeWidgetItem(
824  mSecPemText,
825  QStringList( QString() ),
826  static_cast<int>( DetailsField ) );
827 
828  item->setFirstColumnSpanned( true );
829 
830  QPlainTextEdit *pte = new QPlainTextEdit( mCurrentQCert.toPem(), treeDetails );
831  pte->setReadOnly( true );
832  pte->setMinimumHeight( 150 );
833  pte->setMaximumHeight( 150 );
834  pte->moveCursor( QTextCursor::Start );
835  item->treeWidget()->setItemWidget( item, 0, pte );
836 }
837 
838 void QgsAuthCertInfo::btnSaveTrust_clicked()
839 {
840  const QgsAuthCertUtils::CertTrustPolicy newpolicy( cmbbxTrust->trustPolicy() );
841  if ( !QgsApplication::authManager()->storeCertTrustPolicy( mCurrentQCert, newpolicy ) )
842  {
843  QgsDebugMsg( QStringLiteral( "Could not set trust policy for certificate" ) );
844  }
845  mCurrentTrustPolicy = newpolicy;
846  decorateCertTreeItem( mCurrentQCert, newpolicy, nullptr );
847  btnSaveTrust->setEnabled( false );
848 
849  // rebuild trust cache
851  mTrustCacheRebuilt = true;
853 }
854 
855 void QgsAuthCertInfo::currentPolicyIndexChanged( int indx )
856 {
857  const QgsAuthCertUtils::CertTrustPolicy newpolicy( cmbbxTrust->trustPolicyForIndex( indx ) );
858  btnSaveTrust->setEnabled( newpolicy != mCurrentTrustPolicy );
859 }
860 
861 void QgsAuthCertInfo::decorateCertTreeItem( const QSslCertificate &cert,
863  QTreeWidgetItem *item )
864 {
865  if ( !item )
866  {
867  item = treeHierarchy->currentItem();
868  }
869  if ( !item )
870  {
871  return;
872  }
873 
874  if ( cert.isNull() || trustpolicy == QgsAuthCertUtils::NoPolicy )
875  {
876  item->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/mIconCertificateMissing.svg" ) ) );
877  // missing CA, color gray and italicize
878  QBrush b( item->foreground( 0 ) );
879  b.setColor( QColor::fromRgb( 90, 90, 90 ) );
880  item->setForeground( 0, b );
881  QFont f( item->font( 0 ) );
882  f.setItalic( true );
883  item->setFont( 0, f );
884  return;
885  }
886 
887  if ( !QgsAuthCertUtils::certIsViable( cert ) )
888  {
889  item->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/mIconCertificateUntrusted.svg" ) ) );
890  return;
891  }
892 
893  if ( trustpolicy == QgsAuthCertUtils::Trusted )
894  {
895  item->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/mIconCertificateTrusted.svg" ) ) );
896  }
897  else if ( trustpolicy == QgsAuthCertUtils::Untrusted
898  || ( trustpolicy == QgsAuthCertUtils::DefaultTrust
899  && mDefaultTrustPolicy == QgsAuthCertUtils::Untrusted ) )
900  {
901  item->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/mIconCertificateUntrusted.svg" ) ) );
902  }
903  else
904  {
905  item->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/mIconCertificate.svg" ) ) );
906  }
907 }
908 
910 
911 QgsAuthCertInfoDialog::QgsAuthCertInfoDialog( const QSslCertificate &cert,
912  bool manageCertTrust,
913  QWidget *parent,
914  const QList<QSslCertificate> &connectionCAs )
915  : QDialog( parent )
916 
917 {
918  setWindowTitle( tr( "Certificate Information" ) );
919  QVBoxLayout *layout = new QVBoxLayout( this );
920  layout->setContentsMargins( 6, 6, 6, 6 );
921 
922  mCertInfoWdgt = new QgsAuthCertInfo( cert, manageCertTrust, this, connectionCAs );
923  layout->addWidget( mCertInfoWdgt );
924 
925  QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Close,
926  Qt::Horizontal, this );
927  buttonBox->button( QDialogButtonBox::Close )->setDefault( true );
928  connect( buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close );
929  layout->addWidget( buttonBox );
930 
931  setLayout( layout );
932 }
933 
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QgsAuthManager * authManager()
Returns the application's authentication manager instance.
QgsAuthCertInfoDialog(const QSslCertificate &cert, bool manageCertTrust, QWidget *parent=nullptr, const QList< QSslCertificate > &connectionCAs=QList< QSslCertificate >())
Construct a dialog displaying detailed info on a certificate and its hierarchical trust chain.
Widget for viewing detailed info on a certificate and its hierarchical trust chain.
QgsAuthCertInfo(const QSslCertificate &cert, bool manageCertTrust=false, QWidget *parent=nullptr, const QList< QSslCertificate > &connectionCAs=QList< QSslCertificate >())
Constructor for QgsAuthCertInfo.
Utilities for working with certificates and keys.
static QString qcaValidityMessage(QCA::Validity validity)
Certificate validity check messages per enum.
static QList< QgsAuthCertUtils::CertUsageType > certificateUsageTypes(const QSslCertificate &cert)
Try to determine the certificates usage types.
static QString qcaSignatureAlgorithm(QCA::SignatureAlgorithm algorithm)
Certificate signature algorithm strings per enum.
static QString resolvedCertName(const QSslCertificate &cert, bool issuer=false)
Gets the general name via RFC 5280 resolution.
static QString certificateUsageTypeString(QgsAuthCertUtils::CertUsageType usagetype)
Certificate usage type strings per enum.
static QString shaHexForCert(const QSslCertificate &cert, bool formatted=false)
Gets the sha1 hash for certificate.
CertTrustPolicy
Type of certificate trust policy.
static QString qcaKnownConstraint(QCA::ConstraintTypeKnown constraint)
Certificate well-known constraint strings per enum.
static bool certIsViable(const QSslCertificate &cert)
certIsViable checks for viability errors of cert and whether it is NULL
static QString getColonDelimited(const QString &txt)
Gets string with colon delimiters every 2 characters.
static QString getCaSourceName(QgsAuthCertUtils::CaCertSource source, bool single=false)
Gets the general name for CA source enum type.
static QString getCertDistinguishedName(const QSslCertificate &qcert, const QCA::Certificate &acert=QCA::Certificate(), bool issuer=false)
Gets combined distinguished name for certificate.
static QString redTextStyleSheet(const QString &selector="*")
Red text stylesheet representing invalid, untrusted, etc. certificate.
static QColor redColor()
Red color representing invalid, untrusted, etc. certificate.
QgsAuthCertUtils::CertTrustPolicy defaultCertTrustPolicy()
Gets the default certificate trust policy preferred by user.
bool rebuildCertTrustCache()
Rebuild certificate authority cache.
const QMap< QString, QPair< QgsAuthCertUtils::CaCertSource, QSslCertificate > > caCertsCache()
caCertsCache get all CA certs mapped to their sha1 from cache.
bool rebuildTrustedCaCertsCache()
Rebuild trusted certificate authorities cache.
#define SSL_SUBJECT_INFO(var, prop)
#define SSL_ISSUER_INFO(var, prop)
const QgsField & field
Definition: qgsfield.h:463
#define QgsDebugMsg(str)
Definition: qgslogger.h:38