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 #include "qgsauthcertutils.h"
19 #include "qgsxmlutils.h"
20 
21 #include <QtCrypto>
22 
23 #include <QFile>
24 #include <QObject>
25 #include <QCryptographicHash>
26 #include <QUrl>
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   const 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       const 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     const 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 
writeXml(QDomElement & parentElement,QDomDocument & document)160 bool QgsAuthMethodConfig::writeXml( QDomElement &parentElement, QDomDocument &document )
161 {
162   QDomElement element = document.createElement( QStringLiteral( "AuthMethodConfig" ) );
163   element.setAttribute( QStringLiteral( "method" ), mMethod );
164   element.setAttribute( QStringLiteral( "id" ), mId );
165   element.setAttribute( QStringLiteral( "name" ), mName );
166   element.setAttribute( QStringLiteral( "version" ), QString::number( mVersion ) );
167   element.setAttribute( QStringLiteral( "uri" ), mUri );
168 
169   QDomElement configElements = document.createElement( QStringLiteral( "Config" ) );
170   QgsStringMap::const_iterator i = mConfigMap.constBegin();
171   while ( i != mConfigMap.constEnd() )
172   {
173     configElements.setAttribute( i.key(), i.value() );
174     ++i;
175   }
176   element.appendChild( configElements );
177 
178   parentElement.appendChild( element );
179   return true;
180 }
181 
readXml(const QDomElement & element)182 bool QgsAuthMethodConfig::readXml( const QDomElement &element )
183 {
184   if ( element.nodeName() != QLatin1String( "AuthMethodConfig" ) )
185     return false;
186 
187   mMethod = element.attribute( QStringLiteral( "method" ) );
188   mId = element.attribute( QStringLiteral( "id" ) );
189   mName = element.attribute( QStringLiteral( "name" ) );
190   mVersion = element.attribute( QStringLiteral( "version" ) ).toInt();
191   mUri = element.attribute( QStringLiteral( "uri" ) );
192 
193   clearConfigMap();
194   const QDomNamedNodeMap configAttributes = element.firstChildElement().attributes();
195   for ( int i = 0; i < configAttributes.length(); i++ )
196   {
197     const QDomAttr configAttribute = configAttributes.item( i ).toAttr();
198     setConfig( configAttribute.name(), configAttribute.value() );
199   }
200 
201   return true;
202 }
203 
204 #ifndef QT_NO_SSL
205 
206 //////////////////////////////////////////////////////
207 // QgsPkiBundle
208 //////////////////////////////////////////////////////
209 
QgsPkiBundle(const QSslCertificate & clientCert,const QSslKey & clientKey,const QList<QSslCertificate> & caChain)210 QgsPkiBundle::QgsPkiBundle( const QSslCertificate &clientCert,
211                             const QSslKey &clientKey,
212                             const QList<QSslCertificate> &caChain )
213   : mCert( QSslCertificate() )
214   , mCertKey( QSslKey() )
215   , mCaChain( caChain )
216 {
217   setClientCert( clientCert );
218   setClientKey( clientKey );
219 }
220 
fromPemPaths(const QString & certPath,const QString & keyPath,const QString & keyPass,const QList<QSslCertificate> & caChain)221 const QgsPkiBundle QgsPkiBundle::fromPemPaths( const QString &certPath,
222     const QString &keyPath,
223     const QString &keyPass,
224     const QList<QSslCertificate> &caChain )
225 {
226   QgsPkiBundle pkibundle;
227   if ( !certPath.isEmpty() && !keyPath.isEmpty()
228        && ( certPath.endsWith( QLatin1String( ".pem" ), Qt::CaseInsensitive )
229             || certPath.endsWith( QLatin1String( ".der" ), Qt::CaseInsensitive ) )
230        && QFile::exists( certPath ) && QFile::exists( keyPath )
231      )
232   {
233     // client cert
234     const bool pem = certPath.endsWith( QLatin1String( ".pem" ), Qt::CaseInsensitive );
235     const QSslCertificate clientcert( QgsAuthCertUtils::fileData( certPath ), pem ? QSsl::Pem : QSsl::Der );
236     pkibundle.setClientCert( clientcert );
237 
238     QSslKey clientkey;
239     clientkey = QgsAuthCertUtils::keyFromFile( keyPath, keyPass );
240     pkibundle.setClientKey( clientkey );
241     if ( !caChain.isEmpty() )
242     {
243       pkibundle.setCaChain( caChain );
244     }
245   }
246   return pkibundle;
247 }
248 
fromPkcs12Paths(const QString & bundlepath,const QString & bundlepass)249 const QgsPkiBundle QgsPkiBundle::fromPkcs12Paths( const QString &bundlepath,
250     const QString &bundlepass )
251 {
252   QgsPkiBundle pkibundle;
253   if ( QCA::isSupported( "pkcs12" )
254        && !bundlepath.isEmpty()
255        && ( bundlepath.endsWith( QLatin1String( ".p12" ), Qt::CaseInsensitive )
256             || bundlepath.endsWith( QLatin1String( ".pfx" ), Qt::CaseInsensitive ) )
257        && QFile::exists( bundlepath ) )
258   {
259     QCA::SecureArray passarray;
260     if ( !bundlepass.isNull() )
261       passarray = QCA::SecureArray( bundlepass.toUtf8() );
262     QCA::ConvertResult res;
263     const QCA::KeyBundle bundle( QCA::KeyBundle::fromFile( bundlepath, passarray, &res, QStringLiteral( "qca-ossl" ) ) );
264     if ( res == QCA::ConvertGood && !bundle.isNull() )
265     {
266       const QCA::CertificateChain cert_chain( bundle.certificateChain() );
267       const QSslCertificate cert( cert_chain.primary().toPEM().toLatin1() );
268       if ( !cert.isNull() )
269       {
270         pkibundle.setClientCert( cert );
271       }
272       const QSslKey cert_key( bundle.privateKey().toPEM().toLatin1(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, QByteArray() );
273       if ( !cert_key.isNull() )
274       {
275         pkibundle.setClientKey( cert_key );
276       }
277 
278       if ( cert_chain.size() > 1 )
279       {
280         QList<QSslCertificate> ca_chain;
281         for ( const auto &ca_cert : cert_chain )
282         {
283           if ( ca_cert != cert_chain.primary() )
284           {
285             ca_chain << QSslCertificate( ca_cert.toPEM().toLatin1() );
286           }
287         }
288         pkibundle.setCaChain( ca_chain );
289       }
290 
291     }
292   }
293   return pkibundle;
294 }
295 
isNull() const296 bool QgsPkiBundle::isNull() const
297 {
298   return ( mCert.isNull() || mCertKey.isNull() );
299 }
300 
isValid() const301 bool QgsPkiBundle::isValid() const
302 {
303   return ( !isNull() && QgsAuthCertUtils::certIsViable( mCert ) );
304 }
305 
certId() const306 const QString QgsPkiBundle::certId() const
307 {
308   if ( mCert.isNull() )
309   {
310     return QString();
311   }
312   return QString( mCert.digest( QCryptographicHash::Sha1 ).toHex() );
313 }
314 
setClientCert(const QSslCertificate & cert)315 void QgsPkiBundle::setClientCert( const QSslCertificate &cert )
316 {
317   mCert.clear();
318   if ( !cert.isNull() )
319   {
320     mCert = cert;
321   }
322 }
323 
setClientKey(const QSslKey & certkey)324 void QgsPkiBundle::setClientKey( const QSslKey &certkey )
325 {
326   mCertKey.clear();
327   if ( !certkey.isNull() && certkey.type() == QSsl::PrivateKey )
328   {
329     mCertKey = certkey;
330   }
331 }
332 
333 
334 //////////////////////////////////////////////////////
335 // QgsPkiConfigBundle
336 //////////////////////////////////////////////////////
337 
QgsPkiConfigBundle(const QgsAuthMethodConfig & config,const QSslCertificate & cert,const QSslKey & certkey,const QList<QSslCertificate> & cachain)338 QgsPkiConfigBundle::QgsPkiConfigBundle( const QgsAuthMethodConfig &config,
339                                         const QSslCertificate &cert,
340                                         const QSslKey &certkey,
341                                         const QList<QSslCertificate> &cachain )
342   : mConfig( config )
343   , mCert( cert )
344   , mCertKey( certkey )
345   , mCaChain( cachain )
346 {
347 }
348 
isValid()349 bool QgsPkiConfigBundle::isValid()
350 {
351   return ( !mCert.isNull() && !mCertKey.isNull() );
352 }
353 
354 
355 //////////////////////////////////////////////
356 // QgsAuthConfigSslServer
357 //////////////////////////////////////////////
358 
359 const QString QgsAuthConfigSslServer::CONF_SEP = QStringLiteral( "|||" );
360 
QgsAuthConfigSslServer()361 QgsAuthConfigSslServer::QgsAuthConfigSslServer()
362   : mSslHostPort( QString() )
363   , mSslCert( QSslCertificate() )
364   , mSslIgnoredErrors( QList<QSslError::SslError>() )
365 {
366   // TODO: figure out if Qt 5 has changed yet again, e.g. TLS-only
367   mQtVersion = 480;
368   // Qt 4.8 defaults to SecureProtocols, i.e. TlsV1SslV3
369   // http://qt-project.org/doc/qt-4.8/qssl.html#SslProtocol-enum
370   mSslProtocol = QSsl::SecureProtocols;
371 }
372 
sslIgnoredErrors() const373 const QList<QSslError> QgsAuthConfigSslServer::sslIgnoredErrors() const
374 {
375   QList<QSslError> errors;
376   const QList<QSslError::SslError> ignoredErrors = sslIgnoredErrorEnums();
377   for ( const QSslError::SslError errenum : ignoredErrors )
378   {
379     errors << QSslError( errenum );
380   }
381   return errors;
382 }
383 
configString() const384 const QString QgsAuthConfigSslServer::configString() const
385 {
386   QStringList configlist;
387   configlist << QString::number( mVersion ) << QString::number( mQtVersion );
388 
389   configlist << QString::number( static_cast< int >( mSslProtocol ) );
390 
391   QStringList errs;
392   for ( const auto err : mSslIgnoredErrors )
393   {
394     errs << QString::number( static_cast< int >( err ) );
395   }
396   configlist << errs.join( QLatin1String( "~~" ) );
397 
398   configlist << QStringLiteral( "%1~~%2" ).arg( static_cast< int >( mSslPeerVerifyMode ) ).arg( mSslPeerVerifyDepth );
399 
400   return configlist.join( CONF_SEP );
401 }
402 
loadConfigString(const QString & config)403 void QgsAuthConfigSslServer::loadConfigString( const QString &config )
404 {
405   if ( config.isEmpty() )
406   {
407     return;
408   }
409   const QStringList configlist( config.split( CONF_SEP ) );
410 
411   mVersion = configlist.at( 0 ).toInt();
412   mQtVersion = configlist.at( 1 ).toInt();
413 
414   // TODO: Conversion between 4.7 -> 4.8 protocol enum differences (and reverse?).
415   //       This is necessary for users upgrading from 4.7 to 4.8
416   mSslProtocol = static_cast< QSsl::SslProtocol >( configlist.at( 2 ).toInt() );
417 
418   mSslIgnoredErrors.clear();
419   const QStringList errs( configlist.at( 3 ).split( QStringLiteral( "~~" ) ) );
420   for ( const auto &err : errs )
421   {
422     mSslIgnoredErrors.append( static_cast< QSslError::SslError >( err.toInt() ) );
423   }
424 
425   const QStringList peerverify( configlist.at( 4 ).split( QStringLiteral( "~~" ) ) );
426   mSslPeerVerifyMode = static_cast< QSslSocket::PeerVerifyMode >( peerverify.at( 0 ).toInt() );
427   mSslPeerVerifyDepth = peerverify.at( 1 ).toInt();
428 }
429 
isNull() const430 bool QgsAuthConfigSslServer::isNull() const
431 {
432   return mSslCert.isNull() && mSslHostPort.isEmpty();
433 }
434 
435 #endif
436