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