1 /***************************************************************************
2     qgsauthconfig.cpp
3     ---------------------
4     begin                : October 5, 2014
5     copyright            : (C) 2014 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 "qgsauthconfig.h"
18 
19 #include <QtCrypto>
20 
21 #include <QFile>
22 #include <QObject>
23 #include <QCryptographicHash>
24 #include <QUrl>
25 
26 #include "qgsauthcertutils.h"
27 
28 
29 //////////////////////////////////////////////
30 // QgsAuthMethodConfig
31 //////////////////////////////////////////////
32 
33 const QString QgsAuthMethodConfig::CONFIG_SEP = QStringLiteral( "|||" );
34 const QString QgsAuthMethodConfig::CONFIG_KEY_SEP = QStringLiteral( ":::" );
35 const QString QgsAuthMethodConfig::CONFIG_LIST_SEP = QStringLiteral( "```" );
36 
37 const int QgsAuthMethodConfig::CONFIG_VERSION = 1;
38 
39 // get uniqueConfigId only on save
QgsAuthMethodConfig(const QString & method,int version)40 QgsAuthMethodConfig::QgsAuthMethodConfig( const QString &method, int version )
41   : mId( QString() )
42   , mName( QString() )
43   , mUri( QString() )
44   , mMethod( method )
45   , mVersion( version )
46   , mConfigMap( QgsStringMap() )
47 {
48 }
49 
operator ==(const QgsAuthMethodConfig & other) const50 bool QgsAuthMethodConfig::operator==( const QgsAuthMethodConfig &other ) const
51 {
52   return ( other.id() == id()
53            && other.name() == name()
54            && other.uri() == uri()
55            && other.method() == method()
56            && other.version() == version()
57            && other.configMap() == configMap() );
58 }
59 
operator !=(const QgsAuthMethodConfig & other) const60 bool QgsAuthMethodConfig::operator!=( const QgsAuthMethodConfig &other ) const
61 {
62   return  !( *this == other );
63 }
64 
isValid(bool validateid) const65 bool QgsAuthMethodConfig::isValid( bool validateid ) const
66 {
67   bool idvalid = validateid ? !mId.isEmpty() : true;
68 
69   return (
70            idvalid
71            && !mName.isEmpty()
72            && !mMethod.isEmpty()
73          );
74 }
75 
configString() const76 const QString QgsAuthMethodConfig::configString() const
77 {
78   QStringList confstrs;
79   QgsStringMap::const_iterator i = mConfigMap.constBegin();
80   while ( i != mConfigMap.constEnd() )
81   {
82     confstrs << i.key() + CONFIG_KEY_SEP + i.value();
83     ++i;
84   }
85   return confstrs.join( CONFIG_SEP );
86 }
87 
loadConfigString(const QString & configstr)88 void QgsAuthMethodConfig::loadConfigString( const QString &configstr )
89 {
90   clearConfigMap();
91   if ( configstr.isEmpty() )
92   {
93     return;
94   }
95 
96   const QStringList confs( configstr.split( CONFIG_SEP ) );
97 
98   for ( const auto &conf : confs )
99   {
100     if ( conf.contains( CONFIG_KEY_SEP ) )
101     {
102       QStringList keyval( conf.split( CONFIG_KEY_SEP ) );
103       setConfig( keyval.at( 0 ), keyval.at( 1 ) );
104     }
105   }
106 
107   if ( configMap().empty() )
108   {
109     setConfig( QStringLiteral( "oldconfigstyle" ), configstr );
110   }
111 }
112 
setConfig(const QString & key,const QString & value)113 void QgsAuthMethodConfig::setConfig( const QString &key, const QString &value )
114 {
115   mConfigMap.insert( key, value );
116 }
117 
setConfigList(const QString & key,const QStringList & value)118 void QgsAuthMethodConfig::setConfigList( const QString &key, const QStringList &value )
119 {
120   setConfig( key, value.join( CONFIG_LIST_SEP ) );
121 }
122 
removeConfig(const QString & key)123 int QgsAuthMethodConfig::removeConfig( const QString &key )
124 {
125   return mConfigMap.remove( key );
126 }
127 
config(const QString & key,const QString & defaultvalue) const128 QString QgsAuthMethodConfig::config( const QString &key, const QString &defaultvalue ) const
129 {
130   return mConfigMap.value( key, defaultvalue );
131 }
132 
configList(const QString & key) const133 QStringList QgsAuthMethodConfig::configList( const QString &key ) const
134 {
135   return config( key ).split( CONFIG_LIST_SEP );
136 }
137 
hasConfig(const QString & key) const138 bool QgsAuthMethodConfig::hasConfig( const QString &key ) const
139 {
140   return mConfigMap.contains( key );
141 }
142 
uriToResource(const QString & accessurl,QString * resource,bool withpath)143 bool QgsAuthMethodConfig::uriToResource( const QString &accessurl, QString *resource, bool withpath )
144 {
145   QString res = QString();
146   if ( !accessurl.isEmpty() )
147   {
148     QUrl url( accessurl );
149     if ( url.isValid() )
150     {
151       res = QStringLiteral( "%1://%2:%3%4" ).arg( url.scheme(), url.host() )
152             .arg( url.port() ).arg( withpath ? url.path() : QString() );
153     }
154   }
155   *resource = res;
156   return ( !res.isEmpty() );
157 }
158 
159 
160 #ifndef QT_NO_SSL
161 
162 //////////////////////////////////////////////////////
163 // QgsPkiBundle
164 //////////////////////////////////////////////////////
165 
QgsPkiBundle(const QSslCertificate & clientCert,const QSslKey & clientKey,const QList<QSslCertificate> & caChain)166 QgsPkiBundle::QgsPkiBundle( const QSslCertificate &clientCert,
167                             const QSslKey &clientKey,
168                             const QList<QSslCertificate> &caChain )
169   : mCert( QSslCertificate() )
170   , mCertKey( QSslKey() )
171   , mCaChain( caChain )
172 {
173   setClientCert( clientCert );
174   setClientKey( clientKey );
175 }
176 
fromPemPaths(const QString & certPath,const QString & keyPath,const QString & keyPass,const QList<QSslCertificate> & caChain)177 const QgsPkiBundle QgsPkiBundle::fromPemPaths( const QString &certPath,
178     const QString &keyPath,
179     const QString &keyPass,
180     const QList<QSslCertificate> &caChain )
181 {
182   QgsPkiBundle pkibundle;
183   if ( !certPath.isEmpty() && !keyPath.isEmpty()
184        && ( certPath.endsWith( QLatin1String( ".pem" ), Qt::CaseInsensitive )
185             || certPath.endsWith( QLatin1String( ".der" ), Qt::CaseInsensitive ) )
186        && QFile::exists( certPath ) && QFile::exists( keyPath )
187      )
188   {
189     // client cert
190     bool pem = certPath.endsWith( QLatin1String( ".pem" ), Qt::CaseInsensitive );
191     QSslCertificate clientcert( QgsAuthCertUtils::fileData( certPath ), pem ? QSsl::Pem : QSsl::Der );
192     pkibundle.setClientCert( clientcert );
193 
194     QSslKey clientkey;
195     clientkey = QgsAuthCertUtils::keyFromFile( keyPath, keyPass );
196     pkibundle.setClientKey( clientkey );
197     if ( !caChain.isEmpty() )
198     {
199       pkibundle.setCaChain( caChain );
200     }
201   }
202   return pkibundle;
203 }
204 
fromPkcs12Paths(const QString & bundlepath,const QString & bundlepass)205 const QgsPkiBundle QgsPkiBundle::fromPkcs12Paths( const QString &bundlepath,
206     const QString &bundlepass )
207 {
208   QgsPkiBundle pkibundle;
209   if ( QCA::isSupported( "pkcs12" )
210        && !bundlepath.isEmpty()
211        && ( bundlepath.endsWith( QLatin1String( ".p12" ), Qt::CaseInsensitive )
212             || bundlepath.endsWith( QLatin1String( ".pfx" ), Qt::CaseInsensitive ) )
213        && QFile::exists( bundlepath ) )
214   {
215     QCA::SecureArray passarray;
216     if ( !bundlepass.isNull() )
217       passarray = QCA::SecureArray( bundlepass.toUtf8() );
218     QCA::ConvertResult res;
219     QCA::KeyBundle bundle( QCA::KeyBundle::fromFile( bundlepath, passarray, &res, QStringLiteral( "qca-ossl" ) ) );
220     if ( res == QCA::ConvertGood && !bundle.isNull() )
221     {
222       const QCA::CertificateChain cert_chain( bundle.certificateChain() );
223       QSslCertificate cert( cert_chain.primary().toPEM().toLatin1() );
224       if ( !cert.isNull() )
225       {
226         pkibundle.setClientCert( cert );
227       }
228       QSslKey cert_key( bundle.privateKey().toPEM().toLatin1(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, QByteArray() );
229       if ( !cert_key.isNull() )
230       {
231         pkibundle.setClientKey( cert_key );
232       }
233 
234       if ( cert_chain.size() > 1 )
235       {
236         QList<QSslCertificate> ca_chain;
237         for ( const auto &ca_cert : cert_chain )
238         {
239           if ( ca_cert != cert_chain.primary() )
240           {
241             ca_chain << QSslCertificate( ca_cert.toPEM().toLatin1() );
242           }
243         }
244         pkibundle.setCaChain( ca_chain );
245       }
246 
247     }
248   }
249   return pkibundle;
250 }
251 
isNull() const252 bool QgsPkiBundle::isNull() const
253 {
254   return ( mCert.isNull() || mCertKey.isNull() );
255 }
256 
isValid() const257 bool QgsPkiBundle::isValid() const
258 {
259   return ( !isNull() && QgsAuthCertUtils::certIsViable( mCert ) );
260 }
261 
certId() const262 const QString QgsPkiBundle::certId() const
263 {
264   if ( mCert.isNull() )
265   {
266     return QString();
267   }
268   return QString( mCert.digest( QCryptographicHash::Sha1 ).toHex() );
269 }
270 
setClientCert(const QSslCertificate & cert)271 void QgsPkiBundle::setClientCert( const QSslCertificate &cert )
272 {
273   mCert.clear();
274   if ( !cert.isNull() )
275   {
276     mCert = cert;
277   }
278 }
279 
setClientKey(const QSslKey & certkey)280 void QgsPkiBundle::setClientKey( const QSslKey &certkey )
281 {
282   mCertKey.clear();
283   if ( !certkey.isNull() && certkey.type() == QSsl::PrivateKey )
284   {
285     mCertKey = certkey;
286   }
287 }
288 
289 
290 //////////////////////////////////////////////////////
291 // QgsPkiConfigBundle
292 //////////////////////////////////////////////////////
293 
QgsPkiConfigBundle(const QgsAuthMethodConfig & config,const QSslCertificate & cert,const QSslKey & certkey,const QList<QSslCertificate> & cachain)294 QgsPkiConfigBundle::QgsPkiConfigBundle( const QgsAuthMethodConfig &config,
295                                         const QSslCertificate &cert,
296                                         const QSslKey &certkey,
297                                         const QList<QSslCertificate> &cachain )
298   : mConfig( config )
299   , mCert( cert )
300   , mCertKey( certkey )
301   , mCaChain( cachain )
302 {
303 }
304 
isValid()305 bool QgsPkiConfigBundle::isValid()
306 {
307   return ( !mCert.isNull() && !mCertKey.isNull() );
308 }
309 
310 
311 //////////////////////////////////////////////
312 // QgsAuthConfigSslServer
313 //////////////////////////////////////////////
314 
315 const QString QgsAuthConfigSslServer::CONF_SEP = QStringLiteral( "|||" );
316 
QgsAuthConfigSslServer()317 QgsAuthConfigSslServer::QgsAuthConfigSslServer()
318   : mSslHostPort( QString() )
319   , mSslCert( QSslCertificate() )
320   , mSslIgnoredErrors( QList<QSslError::SslError>() )
321 {
322   // TODO: figure out if Qt 5 has changed yet again, e.g. TLS-only
323   mQtVersion = 480;
324   // Qt 4.8 defaults to SecureProtocols, i.e. TlsV1SslV3
325   // http://qt-project.org/doc/qt-4.8/qssl.html#SslProtocol-enum
326   mSslProtocol = QSsl::SecureProtocols;
327 }
328 
sslIgnoredErrors() const329 const QList<QSslError> QgsAuthConfigSslServer::sslIgnoredErrors() const
330 {
331   QList<QSslError> errors;
332   const QList<QSslError::SslError> ignoredErrors = sslIgnoredErrorEnums();
333   for ( QSslError::SslError errenum : ignoredErrors )
334   {
335     errors << QSslError( errenum );
336   }
337   return errors;
338 }
339 
configString() const340 const QString QgsAuthConfigSslServer::configString() const
341 {
342   QStringList configlist;
343   configlist << QString::number( mVersion ) << QString::number( mQtVersion );
344 
345   configlist << QString::number( static_cast< int >( mSslProtocol ) );
346 
347   QStringList errs;
348   for ( auto err : mSslIgnoredErrors )
349   {
350     errs << QString::number( static_cast< int >( err ) );
351   }
352   configlist << errs.join( QLatin1String( "~~" ) );
353 
354   configlist << QStringLiteral( "%1~~%2" ).arg( static_cast< int >( mSslPeerVerifyMode ) ).arg( mSslPeerVerifyDepth );
355 
356   return configlist.join( CONF_SEP );
357 }
358 
loadConfigString(const QString & config)359 void QgsAuthConfigSslServer::loadConfigString( const QString &config )
360 {
361   if ( config.isEmpty() )
362   {
363     return;
364   }
365   QStringList configlist( config.split( CONF_SEP ) );
366 
367   mVersion = configlist.at( 0 ).toInt();
368   mQtVersion = configlist.at( 1 ).toInt();
369 
370   // TODO: Conversion between 4.7 -> 4.8 protocol enum differences (and reverse?).
371   //       This is necessary for users upgrading from 4.7 to 4.8
372   mSslProtocol = static_cast< QSsl::SslProtocol >( configlist.at( 2 ).toInt() );
373 
374   mSslIgnoredErrors.clear();
375   const QStringList errs( configlist.at( 3 ).split( QStringLiteral( "~~" ) ) );
376   for ( const auto &err : errs )
377   {
378     mSslIgnoredErrors.append( static_cast< QSslError::SslError >( err.toInt() ) );
379   }
380 
381   QStringList peerverify( configlist.at( 4 ).split( QStringLiteral( "~~" ) ) );
382   mSslPeerVerifyMode = static_cast< QSslSocket::PeerVerifyMode >( peerverify.at( 0 ).toInt() );
383   mSslPeerVerifyDepth = peerverify.at( 1 ).toInt();
384 }
385 
isNull() const386 bool QgsAuthConfigSslServer::isNull() const
387 {
388   return mSslCert.isNull() && mSslHostPort.isEmpty();
389 }
390 
391 #endif
392