QGIS API Documentation  2.14.0-Essen
qgsauthconfig.cpp
Go to the documentation of this file.
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 <QUrl>
24 
25 #include "qgsauthcertutils.h"
26 
27 
29 // QgsAuthMethodConfig
31 
32 const QString QgsAuthMethodConfig::mConfigSep = "|||";
33 const QString QgsAuthMethodConfig::mConfigKeySep = ":::";
34 const QString QgsAuthMethodConfig::mConfigListSep = "```";
35 
36 const int QgsAuthMethodConfig::mConfigVersion = 1;
37 
38 // get uniqueConfigId only on save
40  : mId( QString() )
41  , mName( QString() )
42  , mUri( QString() )
43  , mMethod( method )
44  , mVersion( version )
45  , mConfigMap( QgsStringMap() )
46 {
47 }
48 
50 {
51  return ( other.id() == id()
52  && other.name() == name()
53  && other.uri() == uri()
54  && other.method() == method()
55  && other.version() == version()
56  && other.configMap() == configMap() );
57 }
58 
60 {
61  return !( *this == other );
62 }
63 
64 bool QgsAuthMethodConfig::isValid( bool validateid ) const
65 {
66  bool idvalid = validateid ? !mId.isEmpty() : true;
67 
68  return (
69  idvalid
70  && !mName.isEmpty()
71  && !mMethod.isEmpty()
72  );
73 }
74 
76 {
77  QStringList confstrs;
79  while ( i != mConfigMap.constEnd() )
80  {
81  confstrs << i.key() + mConfigKeySep + i.value();
82  ++i;
83  }
84  return confstrs.join( mConfigSep );
85 }
86 
88 {
90  if ( configstr.isEmpty() )
91  {
92  return;
93  }
94 
95  QStringList confs( configstr.split( mConfigSep ) );
96 
97  Q_FOREACH ( const QString& conf, confs )
98  {
99  if ( conf.contains( mConfigKeySep ) )
100  {
101  QStringList keyval( conf.split( mConfigKeySep ) );
102  setConfig( keyval.at( 0 ), keyval.at( 1 ) );
103  }
104  }
105 
106  if ( configMap().empty() )
107  {
108  setConfig( "oldconfigstyle", configstr );
109  }
110 }
111 
112 void QgsAuthMethodConfig::setConfig( const QString &key, const QString &value )
113 {
114  mConfigMap.insert( key, value );
115 }
116 
118 {
119  setConfig( key, value.join( mConfigListSep ) );
120 }
121 
123 {
124  return mConfigMap.remove( key );
125 }
126 
127 QString QgsAuthMethodConfig::config( const QString &key, const QString& defaultvalue ) const
128 {
129  return mConfigMap.value( key, defaultvalue );
130 }
131 
133 {
134  return config( key ).split( mConfigListSep );
135 }
136 
137 bool QgsAuthMethodConfig::hasConfig( const QString &key ) const
138 {
139  return mConfigMap.contains( key );
140 }
141 
142 bool QgsAuthMethodConfig::uriToResource( const QString &accessurl, QString *resource, bool withpath )
143 {
144  QString res = QString();
145  if ( !accessurl.isEmpty() )
146  {
147  QUrl url( accessurl );
148  if ( url.isValid() )
149  {
150  res = QString( "%1://%2:%3%4" ).arg( url.scheme(), url.host() )
151  .arg( url.port() ).arg( withpath ? url.path() : "" );
152  }
153  }
154  *resource = res;
155  return ( !res.isEmpty() );
156 }
157 
158 
159 #ifndef QT_NO_OPENSSL
160 
162 // QgsPkiBundle
164 
166  const QSslKey &clientKey,
167  const QList<QSslCertificate> &caChain )
168  : mCert( QSslCertificate() )
169  , mCertKey( QSslKey() )
170  , mCaChain( caChain )
171 {
172  setClientCert( clientCert );
173  setClientKey( clientKey );
174 }
175 
176 static QByteArray fileData_( const QString& path, bool astext = false )
177 {
178  QByteArray data;
179  QFile file( path );
180  if ( file.exists() )
181  {
182  QFile::OpenMode openflags( QIODevice::ReadOnly );
183  if ( astext )
184  openflags |= QIODevice::Text;
185  bool ret = file.open( openflags );
186  if ( ret )
187  {
188  data = file.readAll();
189  }
190  file.close();
191  }
192  return data;
193 }
194 
196  const QString &keyPath,
197  const QString &keyPass,
199 {
200  QgsPkiBundle pkibundle;
201  if ( !certPath.isEmpty() && !keyPath.isEmpty()
202  && ( certPath.endsWith( ".pem", Qt::CaseInsensitive )
203  || certPath.endsWith( ".der", Qt::CaseInsensitive ) )
204  && ( keyPath.endsWith( ".pem", Qt::CaseInsensitive )
205  || keyPath.endsWith( ".der", Qt::CaseInsensitive ) )
206  && QFile::exists( certPath ) && QFile::exists( keyPath )
207  )
208  {
209  // client cert
210  bool pem = certPath.endsWith( ".pem", Qt::CaseInsensitive );
211  QSslCertificate clientcert( fileData_( certPath, pem ), pem ? QSsl::Pem : QSsl::Der );
212  pkibundle.setClientCert( clientcert );
213 
214  // client key
215  bool pem_key = keyPath.endsWith( ".pem", Qt::CaseInsensitive );
216  QByteArray keydata( fileData_( keyPath, pem_key ) );
217 
218  QSslKey clientkey;
219  clientkey = QSslKey( keydata,
220  QSsl::Rsa,
221  pem_key ? QSsl::Pem : QSsl::Der,
222  QSsl::PrivateKey,
223  !keyPass.isNull() ? keyPass.toUtf8() : QByteArray() );
224  if ( clientkey.isNull() )
225  {
226  // try DSA algorithm, since Qt can't seem to determine it otherwise
227  clientkey = QSslKey( keydata,
228  QSsl::Dsa,
229  pem_key ? QSsl::Pem : QSsl::Der,
230  QSsl::PrivateKey,
231  !keyPass.isNull() ? keyPass.toUtf8() : QByteArray() );
232  }
233  pkibundle.setClientKey( clientkey );
234  if ( !caChain.isEmpty() )
235  {
236  pkibundle.setCaChain( caChain );
237  }
238  }
239  return pkibundle;
240 }
241 
243  const QString &bundlepass )
244 {
245  QgsPkiBundle pkibundle;
246  if ( QCA::isSupported( "pkcs12" )
247  && !bundlepath.isEmpty()
248  && ( bundlepath.endsWith( ".p12", Qt::CaseInsensitive )
249  || bundlepath.endsWith( ".pfx", Qt::CaseInsensitive ) )
250  && QFile::exists( bundlepath ) )
251  {
252  QCA::SecureArray passarray;
253  if ( !bundlepass.isNull() )
254  passarray = QCA::SecureArray( bundlepass.toUtf8() );
255  QCA::ConvertResult res;
256  QCA::KeyBundle bundle( QCA::KeyBundle::fromFile( bundlepath, passarray, &res, QString( "qca-ossl" ) ) );
257  if ( res == QCA::ConvertGood && !bundle.isNull() )
258  {
259  QCA::CertificateChain cert_chain( bundle.certificateChain() );
260  QSslCertificate cert( cert_chain.primary().toPEM().toAscii() );
261  if ( !cert.isNull() )
262  {
263  pkibundle.setClientCert( cert );
264  }
265  QSslKey cert_key( bundle.privateKey().toPEM().toAscii(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, QByteArray() );
266  if ( !cert_key.isNull() )
267  {
268  pkibundle.setClientKey( cert_key );
269  }
270 
271  if ( cert_chain.size() > 1 )
272  {
273  QList<QSslCertificate> ca_chain;
274  Q_FOREACH ( const QCA::Certificate& ca_cert, cert_chain )
275  {
276  if ( ca_cert != cert_chain.primary() )
277  {
278  ca_chain << QSslCertificate( ca_cert.toPEM().toAscii() );
279  }
280  }
281  pkibundle.setCaChain( ca_chain );
282  }
283 
284  }
285  }
286  return pkibundle;
287 }
288 
290 {
291  return ( mCert.isNull() || mCertKey.isNull() );
292 }
293 
295 {
296  return ( !isNull() && mCert.isValid() );
297 }
298 
300 {
301  if ( mCert.isNull() )
302  {
303  return QString::null;
304  }
305  return QgsAuthCertUtils::shaHexForCert( mCert );
306 }
307 
309 {
310  mCert.clear();
311  if ( !cert.isNull() )
312  {
313  mCert = cert;
314  }
315 }
316 
317 void QgsPkiBundle::setClientKey( const QSslKey &certkey )
318 {
319  mCertKey.clear();
320  if ( !certkey.isNull() && certkey.type() == QSsl::PrivateKey )
321  {
322  mCertKey = certkey;
323  }
324 }
325 
326 
328 // QgsPkiConfigBundle
330 
332  const QSslCertificate& cert,
333  const QSslKey& certkey )
334  : mConfig( config )
335  , mCert( cert )
336  , mCertKey( certkey )
337 {
338 }
339 
341 {
342  return ( !mCert.isNull() && !mCertKey.isNull() );
343 }
344 
345 
347 // QgsAuthConfigSslServer
349 
350 const QString QgsAuthConfigSslServer::mConfSep = "|||";
351 
353  : mSslHostPort( QString() )
354  , mSslCert( QSslCertificate() )
355  , mSslIgnoredErrors( QList<QSslError::SslError>() )
356  , mSslPeerVerifyMode( QSslSocket::VerifyPeer )
357  , mSslPeerVerifyDepth( 0 )
358  , mVersion( 1 )
359 {
360  // TODO: figure out if Qt 5 has changed yet again, e.g. TLS-only
361 #if QT_VERSION >= 0x040800
362  mQtVersion = 480;
363  // Qt 4.8 defaults to SecureProtocols, i.e. TlsV1SslV3
364  // http://qt-project.org/doc/qt-4.8/qssl.html#SslProtocol-enum
365  mSslProtocol = QSsl::SecureProtocols;
366 #else
367  mQtVersion = 470;
368  // older Qt 4.7 defaults to now-vulnerable SSLv3
369  // http://qt-project.org/doc/qt-4.7/qssl.html
370  // Default this to TlsV1 instead
371  mSslProtocol = QSsl::TlsV1;
372 #endif
373 }
374 
376 {
377  QList<QSslError> errors;
378  Q_FOREACH ( QSslError::SslError errenum, sslIgnoredErrorEnums() )
379  {
380  errors << QSslError( errenum );
381  }
382  return errors;
383 }
384 
386 {
387  QStringList configlist;
388  configlist << QString::number( mVersion ) << QString::number( mQtVersion );
389 
390  configlist << QString::number( static_cast< int >( mSslProtocol ) );
391 
392  QStringList errs;
393  Q_FOREACH ( const QSslError::SslError& err, mSslIgnoredErrors )
394  {
395  errs << QString::number( static_cast< int >( err ) );
396  }
397  configlist << errs.join( "~~" );
398 
399  configlist << QString( "%1~~%2" ).arg( static_cast< int >( mSslPeerVerifyMode ) ).arg( mSslPeerVerifyDepth );
400 
401  return configlist.join( mConfSep );
402 }
403 
405 {
406  if ( config.isEmpty() )
407  {
408  return;
409  }
410  QStringList configlist( config.split( mConfSep ) );
411 
412  mVersion = configlist.at( 0 ).toInt();
413  mQtVersion = configlist.at( 1 ).toInt();
414 
415  // TODO: Conversion between 4.7 -> 4.8 protocol enum differences (and reverse?).
416  // This is necessary for users upgrading from 4.7 to 4.8
417  mSslProtocol = static_cast< QSsl::SslProtocol >( configlist.at( 2 ).toInt() );
418 
419  mSslIgnoredErrors.clear();
420  QStringList errs( configlist.at( 3 ).split( "~~" ) );
421  Q_FOREACH ( const QString& err, errs )
422  {
423  mSslIgnoredErrors.append( static_cast< QSslError::SslError >( err.toInt() ) );
424  }
425 
426  QStringList peerverify( configlist.at( 4 ).split( "~~" ) );
427  mSslPeerVerifyMode = static_cast< QSslSocket::PeerVerifyMode >( peerverify.at( 0 ).toInt() );
428  mSslPeerVerifyDepth = peerverify.at( 1 ).toInt();
429 }
430 
432 {
433  return mSslCert.isNull() && mSslHostPort.isEmpty();
434 }
435 
436 #endif
typedef OpenMode
QSsl::KeyType type() const
void clear()
void setConfig(const QString &key, const QString &value)
Set a single config value per key in the map.
bool contains(const Key &key) const
bool empty() const
bool isNull() const
const QString configString() const
The extended configuration, as stored and retrieved from the authentication database.
bool isValid() const
void setCaChain(const QList< QSslCertificate > &cachain)
Set chain of Certificate Authorities for client certificate.
static const QgsPkiBundle fromPkcs12Paths(const QString &bundlepath, const QString &bundlepass=QString::null)
Construct a bundle of PKI components from a PKCS#12 file path.
QStringList split(const QString &sep, SplitBehavior behavior, Qt::CaseSensitivity cs) const
const_iterator constBegin() const
QString host() const
bool isNull() const
Whether configuration is null (missing components)
bool isNull() const
Whether the bundle, either its certificate or private key, is null.
QString join(const QString &separator) const
void setConfigList(const QString &key, const QStringList &value)
Set a multiple config values per key in the map.
bool exists() const
int port() const
bool isNull() const
const QString name() const
Get name of configuration.
Definition: qgsauthconfig.h:62
Storage set for PKI bundle: SSL certificate, key, optional CA cert chain.
void clear()
static QByteArray fileData_(const QString &path, bool astext=false)
QString number(int n, int base)
const QString uri() const
A URI to auto-select a config when connecting to a resource.
Definition: qgsauthconfig.h:67
void setClientCert(const QSslCertificate &cert)
Set client certificate object.
void append(const T &value)
const QString configString() const
Configuration as a concatenated string.
const Key & key() const
QgsStringMap configMap() const
Get extended configuration, mapped to key/value pairs of QStrings.
Definition: qgsauthconfig.h:97
bool isNull() const
const QString certId() const
The sha hash of the client certificate.
bool operator!=(const QgsAuthMethodConfig &other) const
Operator used to compare configs&#39; inequality.
Configuration storage class for authentication method configurations.
Definition: qgsauthconfig.h:36
int toInt(bool *ok, int base) const
bool isEmpty() const
bool isEmpty() const
const_iterator constEnd() const
QByteArray readAll()
QgsAuthConfigSslServer()
Construct a default SSL server configuration.
void loadConfigString(const QString &configstr)
Load existing extended configuration.
QString path() const
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const
bool isValid()
Whether the bundle is valid.
const T & value() const
const QString id() const
Get &#39;authcfg&#39; 7-character alphanumeric ID of the config.
Definition: qgsauthconfig.h:57
virtual bool open(QFlags< QIODevice::OpenModeFlag > mode)
QString scheme() const
QStringList configList(const QString &key) const
Return a config&#39;s list of values.
QString config(const QString &key, const QString &defaultvalue=QString()) const
Return a config&#39;s value.
bool contains(QChar ch, Qt::CaseSensitivity cs) const
virtual void close()
void setClientKey(const QSslKey &certkey)
Set private key object.
static QString shaHexForCert(const QSslCertificate &cert, bool formatted=false)
Get the sha1 hash for certificate.
QgsAuthMethodConfig(const QString &method=QString(), int version=0)
Construct a configuration for an authentication method.
const QList< QSslCertificate > caChain() const
Chain of Certificate Authorities for client certificate.
bool isValid() const
bool isValid() const
Whether the bundle is valid.
static bool uriToResource(const QString &accessurl, QString *resource, bool withpath=false)
A utility function for generating a resource from a URL to be compared against the config&#39;s uri() for...
void loadConfigString(const QString &config=QString())
Load concatenated string into configuration, e.g.
int removeConfig(const QString &key)
Remove a config from map.
QgsPkiConfigBundle(const QgsAuthMethodConfig &config, const QSslCertificate &cert, const QSslKey &certkey)
Construct a bundle from existing PKI components and authentication method configuration.
iterator insert(const Key &key, const T &value)
bool hasConfig(const QString &key) const
Whether a config key exists in config map.
bool isValid(bool validateid=false) const
Whether the configuration is valid.
void clearConfigMap()
Clear all configs.
bool operator==(const QgsAuthMethodConfig &other) const
Operator used to compare configs&#39; equality.
int version() const
Get version of the configuration.
Definition: qgsauthconfig.h:75
static const QgsPkiBundle fromPemPaths(const QString &certPath, const QString &keyPath, const QString &keyPass=QString::null, const QList< QSslCertificate > &caChain=QList< QSslCertificate >())
Construct a bundle of PKI components from PEM-formatted file paths.
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
QString method() const
Textual key of the associated authentication method.
Definition: qgsauthconfig.h:71
QgsPkiBundle(const QSslCertificate &clientCert=QSslCertificate(), const QSslKey &clientKey=QSslKey(), const QList< QSslCertificate > &caChain=QList< QSslCertificate >())
Construct a bundle from existing PKI components.
const T value(const Key &key) const
int remove(const Key &key)
const QList< QSslError::SslError > sslIgnoredErrorEnums() const
SSL server errors (as enum list) to ignore in connections.
const QList< QSslError > sslIgnoredErrors() const
SSL server errors to ignore in connections.
QByteArray toUtf8() const