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