QGIS API Documentation 3.41.0-Master (3440c17df1d)
Loading...
Searching...
No Matches
qgsauthimportidentitydialog.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsauthimportidentitydialog.cpp
3 ---------------------
4 begin : May 9, 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
18#include "moc_qgsauthimportidentitydialog.cpp"
19#include "ui_qgsauthimportidentitydialog.h"
20
21#include <QFile>
22#include <QFileDialog>
23#include <QPushButton>
24
25#include "qgssettings.h"
26#include "qgsauthcertutils.h"
27#include "qgsauthconfig.h"
28#include "qgsauthguiutils.h"
29#include "qgsauthmanager.h"
30#include "qgslogger.h"
31#include "qgsapplication.h"
32
33
35 QWidget *parent )
36 : QDialog( parent )
37 , mIdentityType( CertIdentity )
38 , mPkiBundle( QgsPkiBundle() )
39 , mDisabled( false )
40
41{
42 if ( QgsApplication::authManager()->isDisabled() )
43 {
44 mDisabled = true;
45 mAuthNotifyLayout = new QVBoxLayout;
46 this->setLayout( mAuthNotifyLayout );
47 mAuthNotify = new QLabel( QgsApplication::authManager()->disabledMessage(), this );
48 mAuthNotifyLayout->addWidget( mAuthNotify );
49 }
50 else
51 {
52 setupUi( this );
53 connect( lePkiPathsKeyPass, &QLineEdit::textChanged, this, &QgsAuthImportIdentityDialog::lePkiPathsKeyPass_textChanged );
54 connect( chkPkiPathsPassShow, &QCheckBox::stateChanged, this, &QgsAuthImportIdentityDialog::chkPkiPathsPassShow_stateChanged );
55 connect( btnPkiPathsCert, &QToolButton::clicked, this, &QgsAuthImportIdentityDialog::btnPkiPathsCert_clicked );
56 connect( btnPkiPathsKey, &QToolButton::clicked, this, &QgsAuthImportIdentityDialog::btnPkiPathsKey_clicked );
57 connect( lePkiPkcs12KeyPass, &QLineEdit::textChanged, this, &QgsAuthImportIdentityDialog::lePkiPkcs12KeyPass_textChanged );
58 connect( chkPkiPkcs12PassShow, &QCheckBox::stateChanged, this, &QgsAuthImportIdentityDialog::chkPkiPkcs12PassShow_stateChanged );
59 connect( btnPkiPkcs12Bundle, &QToolButton::clicked, this, &QgsAuthImportIdentityDialog::btnPkiPkcs12Bundle_clicked );
60 connect( buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close );
61 connect( buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
62
63 mIdentityType = identitytype;
64
65 populateIdentityType();
66 }
67}
68
77
78const QPair<QSslCertificate, QSslKey> QgsAuthImportIdentityDialog::certBundleToImport()
79{
80 if ( mDisabled )
81 {
82 return qMakePair( QSslCertificate(), QSslKey() );
83 }
84 return mCertBundle;
85}
86
87void QgsAuthImportIdentityDialog::populateIdentityType()
88{
89 if ( mIdentityType == CertIdentity )
90 {
91 stkwBundleType->setVisible( true );
92
93 cmbIdentityTypes->addItem( tr( "PKI PEM/DER Certificate Paths" ),
95 cmbIdentityTypes->addItem( tr( "PKI PKCS#12 Certificate Bundle" ),
97
98 connect( cmbIdentityTypes, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ),
99 stkwBundleType, &QStackedWidget::setCurrentIndex );
100 connect( stkwBundleType, &QStackedWidget::currentChanged,
101 cmbIdentityTypes, &QComboBox::setCurrentIndex );
102
103 connect( cmbIdentityTypes, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ),
104 this, [ = ] { validateIdentity(); } );
105 connect( stkwBundleType, &QStackedWidget::currentChanged,
106 this, &QgsAuthImportIdentityDialog::validateIdentity );
107
108 cmbIdentityTypes->setCurrentIndex( 0 );
109 stkwBundleType->setCurrentIndex( 0 );
110 }
111 // else switch stacked widget, and populate/connect according to that type and widget
112}
113
114void QgsAuthImportIdentityDialog::validateIdentity()
115{
116 bool ok = false;
117 if ( mIdentityType == CertIdentity )
118 {
119 ok = validateBundle();
120 }
121 okButton()->setEnabled( ok );
122}
123
124bool QgsAuthImportIdentityDialog::validateBundle()
125{
126
127 // clear out any previously set bundle
128 const QSslCertificate emptycert;
129 const QSslKey emptykey;
130 mCertBundle = qMakePair( emptycert, emptykey );
131 mPkiBundle = QgsPkiBundle();
132
133 QWidget *curpage = stkwBundleType->currentWidget();
134 if ( curpage == pagePkiPaths )
135 {
136 return validatePkiPaths();
137 }
138 else if ( curpage == pagePkiPkcs12 )
139 {
140 return validatePkiPkcs12();
141 }
142
143 return false;
144}
145
146void QgsAuthImportIdentityDialog::clearValidation()
147{
148 teValidation->clear();
149 teValidation->setStyleSheet( QString() );
150}
151
152void QgsAuthImportIdentityDialog::writeValidation( const QString &msg,
154 bool append )
155{
156 QString ss;
157 QString txt( msg );
158 switch ( valid )
159 {
160 case Valid:
161 ss = QgsAuthGuiUtils::greenTextStyleSheet( QStringLiteral( "QTextEdit" ) );
162 txt = tr( "Valid: %1" ).arg( msg );
163 break;
164 case Invalid:
165 ss = QgsAuthGuiUtils::redTextStyleSheet( QStringLiteral( "QTextEdit" ) );
166 txt = tr( "Invalid: %1" ).arg( msg );
167 break;
168 case Unknown:
169 break;
170 }
171 teValidation->setStyleSheet( ss );
172 if ( append )
173 {
174 teValidation->append( txt );
175 }
176 else
177 {
178 teValidation->setText( txt );
179 }
180 teValidation->moveCursor( QTextCursor::Start );
181}
182
183void QgsAuthImportIdentityDialog::lePkiPathsKeyPass_textChanged( const QString &pass )
184{
185 Q_UNUSED( pass )
186 validateIdentity();
187}
188
189void QgsAuthImportIdentityDialog::chkPkiPathsPassShow_stateChanged( int state )
190{
191 lePkiPathsKeyPass->setEchoMode( ( state > 0 ) ? QLineEdit::Normal : QLineEdit::Password );
192}
193
194void QgsAuthImportIdentityDialog::btnPkiPathsCert_clicked()
195{
196 const QString &fn = getOpenFileName( tr( "Open Client Certificate File" ), tr( "All files (*.*);;PEM (*.pem);;DER (*.der)" ) );
197 if ( !fn.isEmpty() )
198 {
199 lePkiPathsCert->setText( fn );
200 validateIdentity();
201 }
202}
203
204void QgsAuthImportIdentityDialog::btnPkiPathsKey_clicked()
205{
206 const QString &fn = getOpenFileName( tr( "Open Private Key File" ), tr( "All files (*.*);;PEM (*.pem);;DER (*.der)" ) );
207 if ( !fn.isEmpty() )
208 {
209 lePkiPathsKey->setText( fn );
210 validateIdentity();
211 }
212}
213
214void QgsAuthImportIdentityDialog::lePkiPkcs12KeyPass_textChanged( const QString &pass )
215{
216 Q_UNUSED( pass )
217 validateIdentity();
218}
219
220void QgsAuthImportIdentityDialog::chkPkiPkcs12PassShow_stateChanged( int state )
221{
222 lePkiPkcs12KeyPass->setEchoMode( ( state > 0 ) ? QLineEdit::Normal : QLineEdit::Password );
223}
224
225void QgsAuthImportIdentityDialog::btnPkiPkcs12Bundle_clicked()
226{
227 const QString &fn = getOpenFileName( tr( "Open PKCS#12 Certificate Bundle" ), tr( "PKCS#12 (*.p12 *.pfx)" ) );
228 if ( !fn.isEmpty() )
229 {
230 lePkiPkcs12Bundle->setText( fn );
231 validateIdentity();
232 }
233}
234
235bool QgsAuthImportIdentityDialog::validatePkiPaths()
236{
237 bool isvalid = false;
238
239 // required components
240 const QString certpath( lePkiPathsCert->text() );
241 const QString keypath( lePkiPathsKey->text() );
242
243 const bool certfound = QFile::exists( certpath );
244 const bool keyfound = QFile::exists( keypath );
245
246 fileFound( certpath.isEmpty() || certfound, lePkiPathsCert );
247 fileFound( keypath.isEmpty() || keyfound, lePkiPathsKey );
248
249 if ( !certfound || !keyfound )
250 {
251 writeValidation( tr( "Missing components" ), Invalid );
252 return false;
253 }
254
255 // check for issue date validity
256 QSslCertificate clientcert;
257 QList<QSslCertificate> certs( QgsAuthCertUtils::certsFromFile( certpath ) );
258 QList<QSslCertificate> ca_certs;
259 if ( !certs.isEmpty() )
260 {
261 clientcert = certs.takeFirst();
262 }
263 else
264 {
265 writeValidation( tr( "Failed to read client certificate from file" ), Invalid );
266 return false;
267 }
268
269 if ( clientcert.isNull() )
270 {
271 writeValidation( tr( "Failed to load client certificate from file" ), Invalid );
272 return false;
273 }
274
275 if ( !certs.isEmpty() ) // Multiple certificates in file
276 {
277 teValidation->append( tr( "Extra certificates found with identity" ) );
278 ca_certs = certs;
279 }
280
281 isvalid = QgsAuthCertUtils::certIsViable( clientcert );
282
283 const QDateTime startdate( clientcert.effectiveDate() );
284 const QDateTime enddate( clientcert.expiryDate() );
285
286 writeValidation( tr( "%1 thru %2" ).arg( startdate.toString(), enddate.toString() ),
287 ( QgsAuthCertUtils::certIsCurrent( clientcert ) ? Valid : Invalid ) );
288 //TODO: set enabled on cert info button, relative to cert validity
289
290 // check for valid private key and that any supplied password works
291 const QString keypass( lePkiPathsKeyPass->text() );
292 const QSslKey clientkey( QgsAuthCertUtils::keyFromFile( keypath, keypass ) );
293 if ( clientkey.isNull() )
294 {
295 writeValidation( tr( "Failed to load client private key from file" ), Invalid, true );
296 if ( !keypass.isEmpty() )
297 {
298 writeValidation( tr( "Private key password may not match" ), Invalid, true );
299 }
300 return false;
301 }
302
303 if ( isvalid )
304 {
305 mCertBundle = qMakePair( clientcert, clientkey );
306 mPkiBundle = QgsPkiBundle( clientcert,
307 clientkey,
308 ca_certs );
309 }
310
311 return isvalid;
312}
313
314bool QgsAuthImportIdentityDialog::validatePkiPkcs12()
315{
316 // required components
317 const QString bundlepath( lePkiPkcs12Bundle->text() );
318 const bool bundlefound = QFile::exists( bundlepath );
319 fileFound( bundlepath.isEmpty() || bundlefound, lePkiPkcs12Bundle );
320
321 if ( !bundlefound )
322 {
323 writeValidation( tr( "Missing components" ), Invalid );
324 return false;
325 }
326
327 if ( !QCA::isSupported( "pkcs12" ) )
328 {
329 writeValidation( tr( "QCA library has no PKCS#12 support" ), Invalid );
330 return false;
331 }
332
333 // load the bundle
334 QCA::SecureArray passarray;
335 QString keypass = QString();
336 if ( !lePkiPkcs12KeyPass->text().isEmpty() )
337 {
338 passarray = QCA::SecureArray( lePkiPkcs12KeyPass->text().toUtf8() );
339 keypass = lePkiPkcs12KeyPass->text();
340 }
341
342 QCA::ConvertResult res;
343 const QCA::KeyBundle bundle( QCA::KeyBundle::fromFile( bundlepath, passarray, &res, QStringLiteral( "qca-ossl" ) ) );
344
345 if ( res == QCA::ErrorFile )
346 {
347 writeValidation( tr( "Failed to read bundle file" ), Invalid );
348 return false;
349 }
350 else if ( res == QCA::ErrorPassphrase )
351 {
352 writeValidation( tr( "Incorrect bundle password" ), Invalid );
353 lePkiPkcs12KeyPass->setPlaceholderText( QStringLiteral( "Required passphrase" ) );
354 return false;
355 }
356 else if ( res == QCA::ErrorDecode )
357 {
358 writeValidation( tr( "Failed to decode (try entering password)" ), Invalid );
359 return false;
360 }
361
362 if ( bundle.isNull() )
363 {
364 writeValidation( tr( "Bundle empty or can not be loaded" ), Invalid );
365 return false;
366 }
367
368 // check for primary cert and that it is valid
369 const QCA::Certificate cert( bundle.certificateChain().primary() );
370 if ( cert.isNull() )
371 {
372 writeValidation( tr( "Bundle client cert can not be loaded" ), Invalid );
373 return false;
374 }
375
376 // TODO: add more robust validation, including cert chain resolution
377 const QDateTime startdate( cert.notValidBefore() );
378 const QDateTime enddate( cert.notValidAfter() );
379 const QDateTime now( QDateTime::currentDateTime() );
380 const bool bundlevalid = ( now >= startdate && now <= enddate );
381
382 writeValidation( tr( "%1 thru %2" ).arg( startdate.toString(), enddate.toString() ),
383 ( bundlevalid ? Valid : Invalid ) );
384
385 if ( bundlevalid )
386 {
387 QSslCertificate clientcert;
388 QList<QSslCertificate> certs( QgsAuthCertUtils::certsFromString( cert.toPEM() ) );
389 if ( !certs.isEmpty() )
390 {
391 clientcert = certs.first();
392 }
393 if ( clientcert.isNull() )
394 {
395 writeValidation( tr( "Qt cert could not be created from QCA cert" ), Invalid, true );
396 return false;
397 }
398 QSslKey clientkey;
399 clientkey = QSslKey( bundle.privateKey().toRSA().toPEM().toLatin1(), QSsl::Rsa );
400 if ( clientkey.isNull() )
401 {
402 writeValidation( tr( "Qt private key could not be created from QCA key" ), Invalid, true );
403 return false;
404 }
405
406 const QCA::CertificateChain cert_chain( bundle.certificateChain() );
407 QList<QSslCertificate> ca_certs;
408 if ( cert_chain.size() > 1 )
409 {
410 const auto constCert_chain = cert_chain;
411 for ( const QCA::Certificate &ca_cert : constCert_chain )
412 {
413 if ( ca_cert != cert_chain.primary() )
414 {
415 ca_certs << QSslCertificate( ca_cert.toPEM().toLatin1() );
416 }
417 }
418 }
419
420 mCertBundle = qMakePair( clientcert, clientkey );
421 mPkiBundle = QgsPkiBundle( clientcert, clientkey, ca_certs );
422 }
423
424 return bundlevalid;
425}
426
427void QgsAuthImportIdentityDialog::fileFound( bool found, QWidget *widget )
428{
429 if ( !found )
430 {
431 widget->setStyleSheet( QgsAuthGuiUtils::redTextStyleSheet( QStringLiteral( "QLineEdit" ) ) );
432 widget->setToolTip( tr( "File not found" ) );
433 }
434 else
435 {
436 widget->setStyleSheet( QString() );
437 widget->setToolTip( QString() );
438 }
439}
440
441QString QgsAuthImportIdentityDialog::getOpenFileName( const QString &title, const QString &extfilter )
442{
443 QgsSettings settings;
444 const QString recentdir = settings.value( QStringLiteral( "UI/lastAuthImportBundleOpenFileDir" ), QDir::homePath() ).toString();
445 QString f = QFileDialog::getOpenFileName( this, title, recentdir, extfilter );
446
447 // return dialog focus on Mac
448 this->raise();
449 this->activateWindow();
450
451 if ( !f.isEmpty() )
452 {
453 settings.setValue( QStringLiteral( "UI/lastAuthImportBundleOpenFileDir" ), QFileInfo( f ).absoluteDir().path() );
454 }
455 return f;
456}
457
458QPushButton *QgsAuthImportIdentityDialog::okButton()
459{
460 return buttonBox->button( QDialogButtonBox::Ok );
461}
static QgsAuthManager * authManager()
Returns the application's authentication manager instance.
static bool certIsViable(const QSslCertificate &cert)
certIsViable checks for viability errors of cert and whether it is NULL
static QList< QSslCertificate > certsFromFile(const QString &certspath)
Returns a list of concatenated certs from a PEM or DER formatted file.
static QSslKey keyFromFile(const QString &keypath, const QString &keypass=QString(), QString *algtype=nullptr)
Returns non-encrypted key from a PEM or DER formatted file.
static QList< QSslCertificate > certsFromString(const QString &pemtext)
Returns a list of concatenated certs from a PEM Base64 text block.
static bool certIsCurrent(const QSslCertificate &cert)
certIsCurrent checks if cert is viable for its not before and not after dates
static QString greenTextStyleSheet(const QString &selector="*")
Green text stylesheet representing valid, trusted, etc. certificate.
static QString redTextStyleSheet(const QString &selector="*")
Red text stylesheet representing invalid, untrusted, etc. certificate.
QgsAuthImportIdentityDialog::IdentityType identityType()
Gets identity type.
const QPair< QSslCertificate, QSslKey > certBundleToImport()
Gets certificate/key bundle to be imported.
Validity
Type of certificate/bundle validity output.
IdentityType
Type of identity being imported.
QgsAuthImportIdentityDialog(QgsAuthImportIdentityDialog::IdentityType identitytype, QWidget *parent=nullptr)
Construct a dialog for importing identities.
Storage set for PKI bundle: SSL certificate, key, optional CA cert chain.
This class is a composition of two QSettings instances:
Definition qgssettings.h:64
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.