1 /***************************************************************************
2     qgsauthpkipathsedit.cpp
3     ---------------------
4     begin                : September 1, 2015
5     copyright            : (C) 2015 by Boundless Spatial, Inc. USA
6     author               : Larry Shaffer
7     email                : lshaffer at boundlessgeo dot com
8  ***************************************************************************
9  *                                                                         *
10  *   This program is free software; you can redistribute it and/or modify  *
11  *   it under the terms of the GNU General Public License as published by  *
12  *   the Free Software Foundation; either version 2 of the License, or     *
13  *   (at your option) any later version.                                   *
14  *                                                                         *
15  ***************************************************************************/
16 
17 #include "qgsauthpkipathsedit.h"
18 #include "ui_qgsauthpkipathsedit.h"
19 
20 #include <QDateTime>
21 #include <QFile>
22 #include <QFileInfo>
23 #include <QSslCertificate>
24 #include <QSslKey>
25 
26 #include "qgsapplication.h"
27 #include "qgsauthcertutils.h"
28 #include "qgsauthmanager.h"
29 #include "qgsauthguiutils.h"
30 #include "qgslogger.h"
31 
32 
QgsAuthPkiPathsEdit(QWidget * parent)33 QgsAuthPkiPathsEdit::QgsAuthPkiPathsEdit( QWidget *parent )
34   : QgsAuthMethodEdit( parent )
35 {
36   setupUi( this );
37   connect( chkPkiPathsPassShow, &QCheckBox::stateChanged, this, &QgsAuthPkiPathsEdit::chkPkiPathsPassShow_stateChanged );
38   connect( btnPkiPathsCert, &QToolButton::clicked, this, &QgsAuthPkiPathsEdit::btnPkiPathsCert_clicked );
39   connect( btnPkiPathsKey, &QToolButton::clicked, this, &QgsAuthPkiPathsEdit::btnPkiPathsKey_clicked );
40   connect( cbAddCas, &QCheckBox::stateChanged, this, [ = ]( int state ) {  cbAddRootCa->setEnabled( state == Qt::Checked ); } );
41   lblCas->hide();
42   twCas->hide();
43   cbAddCas->hide();
44   cbAddRootCa->hide();
45 }
46 
validateConfig()47 bool QgsAuthPkiPathsEdit::validateConfig()
48 {
49   // required components
50   const QString certpath( lePkiPathsCert->text() );
51   const QString keypath( lePkiPathsKey->text() );
52 
53   const bool certfound = QFile::exists( certpath );
54   const bool keyfound = QFile::exists( keypath );
55 
56   QgsAuthGuiUtils::fileFound( certpath.isEmpty() || certfound, lePkiPathsCert );
57   QgsAuthGuiUtils::fileFound( keypath.isEmpty() || keyfound, lePkiPathsKey );
58 
59   if ( !certfound || !keyfound )
60   {
61     writePkiMessage( lePkiPathsMsg, tr( "Missing components" ), Invalid );
62     return validityChange( false );
63   }
64 
65   // check for issue date validity, then notify status
66   const QSslCertificate cert( QgsAuthCertUtils::certFromFile( certpath ) );
67 
68   if ( cert.isNull() )
69   {
70     writePkiMessage( lePkiPathsMsg, tr( "Failed to load certificate from file" ), Invalid );
71     return validityChange( false );
72   }
73 
74   const QDateTime startdate( cert.effectiveDate() );
75   const QDateTime enddate( cert.expiryDate() );
76 
77   writePkiMessage( lePkiPathsMsg,
78                    tr( "%1 thru %2" ).arg( startdate.toString(), enddate.toString() ),
79                    ( QgsAuthCertUtils::certIsCurrent( cert ) ? Valid : Invalid ) );
80 
81   const bool certviable = QgsAuthCertUtils::certIsViable( cert );
82   const bool showCas( certviable && populateCas() );
83   lblCas->setVisible( showCas );
84   twCas->setVisible( showCas );
85   cbAddCas->setVisible( showCas );
86   cbAddRootCa->setVisible( showCas );
87 
88   return validityChange( certviable );
89 }
90 
configMap() const91 QgsStringMap QgsAuthPkiPathsEdit::configMap() const
92 {
93   QgsStringMap config;
94   config.insert( QStringLiteral( "certpath" ), lePkiPathsCert->text() );
95   config.insert( QStringLiteral( "keypath" ), lePkiPathsKey->text() );
96   config.insert( QStringLiteral( "keypass" ), lePkiPathsKeyPass->text() );
97   config.insert( QStringLiteral( "addcas" ), cbAddCas->isChecked() ? QStringLiteral( "true" ) :  QStringLiteral( "false" ) );
98   config.insert( QStringLiteral( "addrootca" ), cbAddRootCa->isChecked() ? QStringLiteral( "true" ) :  QStringLiteral( "false" ) );
99 
100   return config;
101 }
102 
loadConfig(const QgsStringMap & configmap)103 void QgsAuthPkiPathsEdit::loadConfig( const QgsStringMap &configmap )
104 {
105   clearConfig();
106 
107   mConfigMap = configmap;
108   lePkiPathsCert->setText( configmap.value( QStringLiteral( "certpath" ) ) );
109   lePkiPathsKey->setText( configmap.value( QStringLiteral( "keypath" ) ) );
110   lePkiPathsKeyPass->setText( configmap.value( QStringLiteral( "keypass" ) ) );
111   cbAddCas->setChecked( configmap.value( QStringLiteral( "addcas" ), QStringLiteral( "false " ) ) == QLatin1String( "true" ) );
112   cbAddRootCa->setChecked( configmap.value( QStringLiteral( "addrootca" ), QStringLiteral( "false " ) ) == QLatin1String( "true" ) );
113 
114   validateConfig();
115 }
116 
resetConfig()117 void QgsAuthPkiPathsEdit::resetConfig()
118 {
119   loadConfig( mConfigMap );
120 }
121 
clearConfig()122 void QgsAuthPkiPathsEdit::clearConfig()
123 {
124   clearPkiPathsCertPath();
125   clearPkiPathsKeyPath();
126   clearPkiPathsKeyPass();
127 
128   clearPkiMessage( lePkiPathsMsg );
129   validateConfig();
130 }
131 
clearPkiMessage(QLineEdit * lineedit)132 void QgsAuthPkiPathsEdit::clearPkiMessage( QLineEdit *lineedit )
133 {
134   lineedit->clear();
135   lineedit->setStyleSheet( QString() );
136 }
137 
writePkiMessage(QLineEdit * lineedit,const QString & msg,Validity valid)138 void QgsAuthPkiPathsEdit::writePkiMessage( QLineEdit *lineedit, const QString &msg, Validity valid )
139 {
140   QString ss;
141   QString txt( msg );
142   switch ( valid )
143   {
144     case Valid:
145       ss = QgsAuthGuiUtils::greenTextStyleSheet( QStringLiteral( "QLineEdit" ) );
146       txt = tr( "Valid: %1" ).arg( msg );
147       break;
148     case Invalid:
149       ss = QgsAuthGuiUtils::redTextStyleSheet( QStringLiteral( "QLineEdit" ) );
150       txt = tr( "Invalid: %1" ).arg( msg );
151       break;
152     case Unknown:
153       break;
154   }
155   lineedit->setStyleSheet( ss );
156   lineedit->setText( txt );
157   lineedit->setCursorPosition( 0 );
158 }
159 
clearPkiPathsCertPath()160 void QgsAuthPkiPathsEdit::clearPkiPathsCertPath()
161 {
162   lePkiPathsCert->clear();
163   lePkiPathsCert->setStyleSheet( QString() );
164 }
165 
clearPkiPathsKeyPath()166 void QgsAuthPkiPathsEdit::clearPkiPathsKeyPath()
167 {
168   lePkiPathsKey->clear();
169   lePkiPathsKey->setStyleSheet( QString() );
170 }
171 
172 
clearPkiPathsKeyPass()173 void QgsAuthPkiPathsEdit::clearPkiPathsKeyPass()
174 {
175   lePkiPathsKeyPass->clear();
176   lePkiPathsKeyPass->setStyleSheet( QString() );
177   chkPkiPathsPassShow->setChecked( false );
178 }
179 
chkPkiPathsPassShow_stateChanged(int state)180 void QgsAuthPkiPathsEdit::chkPkiPathsPassShow_stateChanged( int state )
181 {
182   lePkiPathsKeyPass->setEchoMode( ( state > 0 ) ? QLineEdit::Normal : QLineEdit::Password );
183 }
184 
btnPkiPathsCert_clicked()185 void QgsAuthPkiPathsEdit::btnPkiPathsCert_clicked()
186 {
187   const QString &fn = QgsAuthGuiUtils::getOpenFileName( this, tr( "Open Client Certificate File" ),
188                       tr( "All files (*.*);;PEM (*.pem);;DER (*.der)" ) );
189   if ( !fn.isEmpty() )
190   {
191     lePkiPathsCert->setText( fn );
192     validateConfig();
193   }
194 }
195 
btnPkiPathsKey_clicked()196 void QgsAuthPkiPathsEdit::btnPkiPathsKey_clicked()
197 {
198   const QString &fn = QgsAuthGuiUtils::getOpenFileName( this, tr( "Open Private Key File" ),
199                       tr( "All files (*.*);;PEM (*.pem);;DER (*.der)" ) );
200   if ( !fn.isEmpty() )
201   {
202     lePkiPathsKey->setText( fn );
203     validateConfig();
204   }
205 }
206 
validityChange(bool curvalid)207 bool QgsAuthPkiPathsEdit::validityChange( bool curvalid )
208 {
209   if ( mValid != curvalid )
210   {
211     mValid = curvalid;
212     emit validityChanged( curvalid );
213   }
214   return curvalid;
215 }
216 
217 
populateCas()218 bool QgsAuthPkiPathsEdit::populateCas()
219 {
220   twCas->clear();
221   const QList<QSslCertificate> cas( QgsAuthCertUtils::casFromFile( lePkiPathsCert->text() ) );
222   if ( cas.isEmpty() )
223   {
224     return false;
225   }
226 
227   QTreeWidgetItem *prevItem( nullptr );
228   QList<QSslCertificate>::const_iterator it( cas.constEnd() );
229   while ( it != cas.constBegin() )
230   {
231     --it;
232     const QSslCertificate cert = static_cast<QSslCertificate>( *it );
233     QTreeWidgetItem *item;
234 
235     if ( prevItem && cert.issuerInfo( QSslCertificate::SubjectInfo::CommonName ).contains( prevItem->text( 0 ) ) )
236     {
237       item = new QTreeWidgetItem( QStringList( cert.subjectInfo( QSslCertificate::SubjectInfo::CommonName ) ) );
238       prevItem->addChild( item );
239     }
240     else
241     {
242       item = new QTreeWidgetItem( twCas, QStringList( cert.subjectInfo( QSslCertificate::SubjectInfo::CommonName ) ) );
243     }
244     item->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/mIconCertificate.svg" ) ) );
245     item->setToolTip( 0, tr( "<ul><li>Serial #: %1</li><li>Expiry date: %2</li></ul>" ).arg( cert.serialNumber( ), cert.expiryDate().toString( Qt::TextDate ) ) );
246     prevItem = item;
247   }
248   twCas->expandAll();
249 
250   return true;
251 }
252