1 /***************************************************************************
2     qgsauthpkcs12edit.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 "qgsauthpkcs12edit.h"
18 #include "ui_qgsauthpkcs12edit.h"
19 
20 #include <QDateTime>
21 #include <QFile>
22 #include <QFileInfo>
23 #include <QSslCertificate>
24 #include <QSslKey>
25 
26 #include "qgsapplication.h"
27 #include "qgsauthmanager.h"
28 #include "qgsauthguiutils.h"
29 #include "qgslogger.h"
30 
31 
QgsAuthPkcs12Edit(QWidget * parent)32 QgsAuthPkcs12Edit::QgsAuthPkcs12Edit( QWidget *parent )
33   : QgsAuthMethodEdit( parent )
34 {
35   setupUi( this );
36   connect( lePkcs12KeyPass, &QLineEdit::textChanged, this, &QgsAuthPkcs12Edit::lePkcs12KeyPass_textChanged );
37   connect( chkPkcs12PassShow, &QCheckBox::stateChanged, this, &QgsAuthPkcs12Edit::chkPkcs12PassShow_stateChanged );
38   connect( btnPkcs12Bundle, &QToolButton::clicked, this, &QgsAuthPkcs12Edit::btnPkcs12Bundle_clicked );
39   connect( cbAddCas, &QCheckBox::stateChanged, this, [ = ]( int state ) {  cbAddRootCa->setEnabled( state == Qt::Checked ); } );
40   lblCas->hide();
41   twCas->hide();
42   cbAddCas->hide();
43   cbAddRootCa->hide();
44 }
45 
validateConfig()46 bool QgsAuthPkcs12Edit::validateConfig()
47 {
48   // required components
49   const QString bundlepath( lePkcs12Bundle->text() );
50 
51   const bool bundlefound = QFile::exists( bundlepath );
52 
53   QgsAuthGuiUtils::fileFound( bundlepath.isEmpty() || bundlefound, lePkcs12Bundle );
54 
55   if ( !bundlefound )
56   {
57     writePkiMessage( lePkcs12Msg, tr( "Missing components" ), Invalid );
58     return validityChange( false );
59   }
60 
61   if ( !QCA::isSupported( "pkcs12" ) )
62   {
63     writePkiMessage( lePkcs12Msg, tr( "QCA library has no PKCS#12 support" ), Invalid );
64     return validityChange( false );
65   }
66 
67   // load the bundle
68   QCA::SecureArray passarray;
69   if ( !lePkcs12KeyPass->text().isEmpty() )
70     passarray = QCA::SecureArray( lePkcs12KeyPass->text().toUtf8() );
71 
72   QCA::ConvertResult res;
73   const QCA::KeyBundle bundle( QCA::KeyBundle::fromFile( bundlepath, passarray, &res, QStringLiteral( "qca-ossl" ) ) );
74 
75   if ( res == QCA::ErrorFile )
76   {
77     writePkiMessage( lePkcs12Msg, tr( "Failed to read bundle file" ), Invalid );
78     return validityChange( false );
79   }
80   else if ( res == QCA::ErrorPassphrase )
81   {
82     writePkiMessage( lePkcs12Msg, tr( "Incorrect bundle password" ), Invalid );
83     lePkcs12KeyPass->setPlaceholderText( QStringLiteral( "Required passphrase" ) );
84     return validityChange( false );
85   }
86   else if ( res == QCA::ErrorDecode )
87   {
88     writePkiMessage( lePkcs12Msg, tr( "Failed to decode (try entering password)" ), Invalid );
89     return validityChange( false );
90   }
91 
92   if ( bundle.isNull() )
93   {
94     writePkiMessage( lePkcs12Msg, tr( "Bundle empty or can not be loaded" ), Invalid );
95     return validityChange( false );
96   }
97 
98   // check for primary cert and that it is valid
99   const QCA::Certificate cert( bundle.certificateChain().primary() );
100   if ( cert.isNull() )
101   {
102     writePkiMessage( lePkcs12Msg, tr( "Bundle client cert can not be loaded" ), Invalid );
103     return validityChange( false );
104   }
105 
106   // TODO: add more robust validation, including cert chain resolution
107   const QDateTime startdate( cert.notValidBefore() );
108   const QDateTime enddate( cert.notValidAfter() );
109   const QDateTime now( QDateTime::currentDateTime() );
110   const bool bundlevalid = ( now >= startdate && now <= enddate );
111 
112   writePkiMessage( lePkcs12Msg,
113                    tr( "%1 thru %2" ).arg( startdate.toString(), enddate.toString() ),
114                    ( bundlevalid ? Valid : Invalid ) );
115 
116   const bool showCas( bundlevalid && populateCas() );
117   lblCas->setVisible( showCas );
118   twCas->setVisible( showCas );
119   cbAddCas->setVisible( showCas );
120   cbAddRootCa->setVisible( showCas );
121 
122   return validityChange( bundlevalid );
123 }
124 
125 
126 
configMap() const127 QgsStringMap QgsAuthPkcs12Edit::configMap() const
128 {
129   QgsStringMap config;
130   config.insert( QStringLiteral( "bundlepath" ), lePkcs12Bundle->text() );
131   config.insert( QStringLiteral( "bundlepass" ), lePkcs12KeyPass->text() );
132   config.insert( QStringLiteral( "addcas" ), cbAddCas->isChecked() ? QStringLiteral( "true" ) :  QStringLiteral( "false" ) );
133   config.insert( QStringLiteral( "addrootca" ), cbAddRootCa->isChecked() ? QStringLiteral( "true" ) :  QStringLiteral( "false" ) );
134 
135   return config;
136 }
137 
loadConfig(const QgsStringMap & configmap)138 void QgsAuthPkcs12Edit::loadConfig( const QgsStringMap &configmap )
139 {
140   clearConfig();
141 
142   mConfigMap = configmap;
143   lePkcs12Bundle->setText( configmap.value( QStringLiteral( "bundlepath" ) ) );
144   lePkcs12KeyPass->setText( configmap.value( QStringLiteral( "bundlepass" ) ) );
145   cbAddCas->setChecked( configmap.value( QStringLiteral( "addcas" ), QStringLiteral( "false " ) ) == QLatin1String( "true" ) );
146   cbAddRootCa->setChecked( configmap.value( QStringLiteral( "addrootca" ), QStringLiteral( "false " ) ) == QLatin1String( "true" ) );
147 
148   validateConfig();
149 }
150 
resetConfig()151 void QgsAuthPkcs12Edit::resetConfig()
152 {
153   loadConfig( mConfigMap );
154 }
155 
clearConfig()156 void QgsAuthPkcs12Edit::clearConfig()
157 {
158   clearPkcs12BundlePath();
159   clearPkcs12BundlePass();
160 
161   clearPkiMessage( lePkcs12Msg );
162   validateConfig();
163 }
164 
clearPkiMessage(QLineEdit * lineedit)165 void QgsAuthPkcs12Edit::clearPkiMessage( QLineEdit *lineedit )
166 {
167   lineedit->clear();
168   lineedit->setStyleSheet( QString() );
169 }
170 
writePkiMessage(QLineEdit * lineedit,const QString & msg,Validity valid)171 void QgsAuthPkcs12Edit::writePkiMessage( QLineEdit *lineedit, const QString &msg, Validity valid )
172 {
173   QString ss;
174   QString txt( msg );
175   switch ( valid )
176   {
177     case Valid:
178       ss = QgsAuthGuiUtils::greenTextStyleSheet( QStringLiteral( "QLineEdit" ) );
179       txt = tr( "Valid: %1" ).arg( msg );
180       break;
181     case Invalid:
182       ss = QgsAuthGuiUtils::redTextStyleSheet( QStringLiteral( "QLineEdit" ) );
183       txt = tr( "Invalid: %1" ).arg( msg );
184       break;
185     case Unknown:
186       break;
187   }
188   lineedit->setStyleSheet( ss );
189   lineedit->setText( txt );
190   lineedit->setCursorPosition( 0 );
191 }
192 
clearPkcs12BundlePath()193 void QgsAuthPkcs12Edit::clearPkcs12BundlePath()
194 {
195   lePkcs12Bundle->clear();
196   lePkcs12Bundle->setStyleSheet( QString() );
197 }
198 
clearPkcs12BundlePass()199 void QgsAuthPkcs12Edit::clearPkcs12BundlePass()
200 {
201   lePkcs12KeyPass->clear();
202   lePkcs12KeyPass->setStyleSheet( QString() );
203   lePkcs12KeyPass->setPlaceholderText( QStringLiteral( "Optional passphrase" ) );
204   chkPkcs12PassShow->setChecked( false );
205 }
206 
lePkcs12KeyPass_textChanged(const QString & pass)207 void QgsAuthPkcs12Edit::lePkcs12KeyPass_textChanged( const QString &pass )
208 {
209   Q_UNUSED( pass )
210   validateConfig();
211 }
212 
chkPkcs12PassShow_stateChanged(int state)213 void QgsAuthPkcs12Edit::chkPkcs12PassShow_stateChanged( int state )
214 {
215   lePkcs12KeyPass->setEchoMode( ( state > 0 ) ? QLineEdit::Normal : QLineEdit::Password );
216 }
217 
btnPkcs12Bundle_clicked()218 void QgsAuthPkcs12Edit::btnPkcs12Bundle_clicked()
219 {
220   const QString &fn = QgsAuthGuiUtils::getOpenFileName( this, tr( "Open PKCS#12 Certificate Bundle" ),
221                       tr( "PKCS#12 (*.p12 *.pfx)" ) );
222   if ( !fn.isEmpty() )
223   {
224     lePkcs12Bundle->setText( fn );
225     validateConfig();
226   }
227 }
228 
validityChange(bool curvalid)229 bool QgsAuthPkcs12Edit::validityChange( bool curvalid )
230 {
231   if ( mValid != curvalid )
232   {
233     mValid = curvalid;
234     emit validityChanged( curvalid );
235   }
236   return curvalid;
237 }
238 
populateCas()239 bool QgsAuthPkcs12Edit::populateCas()
240 {
241   twCas->clear();
242   const QList<QSslCertificate> cas( QgsAuthCertUtils::pkcs12BundleCas( lePkcs12Bundle->text(), lePkcs12KeyPass->text() ) );
243   if ( cas.isEmpty() )
244   {
245     return false;
246   }
247 
248   QTreeWidgetItem *prevItem( nullptr );
249   QList<QSslCertificate>::const_iterator it( cas.constEnd() );
250   while ( it != cas.constBegin() )
251   {
252     --it;
253     const QSslCertificate cert = static_cast<QSslCertificate>( *it );
254     QTreeWidgetItem *item;
255 
256     if ( prevItem && cert.issuerInfo( QSslCertificate::SubjectInfo::CommonName ).contains( prevItem->text( 0 ) ) )
257     {
258       item = new QTreeWidgetItem( QStringList( cert.subjectInfo( QSslCertificate::SubjectInfo::CommonName ) ) );
259       prevItem->addChild( item );
260     }
261     else
262     {
263       item = new QTreeWidgetItem( twCas, QStringList( cert.subjectInfo( QSslCertificate::SubjectInfo::CommonName ) ) );
264     }
265     item->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/mIconCertificate.svg" ) ) );
266     item->setToolTip( 0, tr( "<ul><li>Serial #: %1</li><li>Expiry date: %2</li></ul>" ).arg( cert.serialNumber( ), cert.expiryDate().toString( Qt::TextDate ) ) );
267     prevItem = item;
268   }
269   twCas->expandAll();
270 
271   return true;
272 }
273