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