QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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
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
35static 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
43static void removeChildren_( QTreeWidgetItem *item )
44{
45 const auto constTakeChildren = item->takeChildren();
46 for ( QTreeWidgetItem *child : constTakeChildren )
47 {
48 delete child;
49 }
50}
51
52QgsAuthCertInfo::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
108void 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
117void QgsAuthCertInfo::currentCertItemChanged( QTreeWidgetItem *current, QTreeWidgetItem *previous )
118{
119 Q_UNUSED( previous )
120 updateCurrentCert( current );
121}
122
123void 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
134bool 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
168bool 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
180bool 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 QgsDebugError( 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
219void 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
283void 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
310void 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
362void QgsAuthCertInfo::populateCertInfo()
363{
364 mSecDetails->setHidden( false );
365 mSecPemText->setHidden( false );
366
367 populateInfoGeneralSection();
368 populateInfoDetailsSection();
369 populateInfoPemTextSection();
370}
371
372QTreeWidgetItem *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
393void 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
448void 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(
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
522void 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
816void 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
838void QgsAuthCertInfo::btnSaveTrust_clicked()
839{
840 const QgsAuthCertUtils::CertTrustPolicy newpolicy( cmbbxTrust->trustPolicy() );
841 if ( !QgsApplication::authManager()->storeCertTrustPolicy( mCurrentQCert, newpolicy ) )
842 {
843 QgsDebugError( 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
855void QgsAuthCertInfo::currentPolicyIndexChanged( int indx )
856{
857 const QgsAuthCertUtils::CertTrustPolicy newpolicy( cmbbxTrust->trustPolicyForIndex( indx ) );
858 btnSaveTrust->setEnabled( newpolicy != mCurrentTrustPolicy );
859}
860
861void 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
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)
#define QgsDebugError(str)
Definition: qgslogger.h:38