Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] SSL PKI integration for OWS connections #1588

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions python/core/core.sip
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
%Include qgssimplifymethod.sip
%Include qgssnapper.sip
%Include qgsspatialindex.sip
%Include qgssslutils.sip
%Include qgstolerance.sip
%Include qgsvectordataprovider.sip
%Include qgsvectorfilewriter.sip
Expand Down
7 changes: 7 additions & 0 deletions python/core/qgscredentials.sip
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ class QgsCredentials
//! request a password
virtual bool request( QString realm, QString &username /In,Out/, QString &password /In,Out/, QString message = QString::null ) = 0;

//! request a password for an SSL key, and optionally its filepath
virtual bool requestSslKey( QString &password /In,Out/, QString &keypath /In,Out/, bool needskeypath,
QString resource, QString message = QString::null ) = 0;

//! register instance
void setInstance( QgsCredentials *theInstance );

Expand Down Expand Up @@ -70,4 +74,7 @@ class QgsCredentialsConsole : QObject, QgsCredentials

protected:
virtual bool request( QString realm, QString &username /In,Out/, QString &password /In,Out/, QString message = QString::null );

virtual bool requestSslKey( QString &password /In,Out/, QString &keypath /In,Out/, bool needskeypath,
QString resource, QString message = QString::null );
};
2 changes: 2 additions & 0 deletions python/core/qgsnetworkaccessmanager.sip
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ class QgsNetworkAccessManager : QNetworkAccessManager

bool useSystemProxy();

//! DO NOT ADD BINDING for access/set functions to the cache for hash/passphrases for private keys of PKI certificates

signals:
void requestAboutToBeCreated( QNetworkAccessManager::Operation, const QNetworkRequest &, QIODevice * );
void requestCreated( QNetworkReply * );
Expand Down
137 changes: 137 additions & 0 deletions python/core/qgssslutils.sip
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/**
* @class QgsSslPkiSettings
* @ingroup core
* @brief Settings for working with a SSL certificate, key and optional certificate issuer
* @since since 2.5
*/

class QgsSslPkiSettings
{
public:

enum SslStoreType
{
QGISStore = 0
};

QgsSslPkiSettings();

QByteArray clientCertData() const;

QByteArray clientKeyData() const;

QByteArray issuerCertData() const;

// TODO: add protocol option, e.g. QSsl::TlsV1SslV3, etc.?

bool certIsReady() const;
void setCertReady( bool ready );

QgsSslPkiSettings::SslStoreType storeType() const;
void setStoreType( QgsSslPkiSettings::SslStoreType storetype );

QString certId() const;
void setCertId( const QString& txtid );

QString keyId() const;
void setKeyId( const QString& txtid );

bool needsKeyPath() const;
void setNeedsKeyPath( bool needs );

bool needsKeyPassphrase() const;
void setNeedsKeyPassphrase( bool has );

QString keyPassphrase() const;
void setKeyPassphrase( const QString& passphrase );

QString issuerId() const;
void setIssuerId( const QString& txtid );

bool issuerSelfSigned() const;
void setIssuerSelfSigned( bool seflsigned );

QString accessUrl() const;
void setAccessUrl( const QString& url );
};

/**
* @class QgsSslPkiGroup
* @ingroup core
* @brief Storage set for constructed SSL certificate, key and optional certificate issuer
* @since 2.6
*/

class QgsSslPkiGroup
{
%TypeHeaderCode
#include <qgssslutils.h>
%End
public:
QgsSslPkiGroup( const QSslCertificate& cert = QSslCertificate(), const QSslKey& certkey = QSslKey(),
const QSslCertificate& issuer = QSslCertificate(), bool issuerselfsigned = false );
~QgsSslPkiGroup();

bool isNull();

QSslCertificate clientCert();
void setClientCert( const QSslCertificate& cert );

QSslKey clientCertKey();
void setClientCertKey( const QSslKey& certkey );

QSslCertificate issuerCert();
void setIssuerCert( const QSslCertificate& issuer );

bool issuerSelfSigned();
void setIssuerSelfSigned( bool selfsigned );
};

/**
\class QgsSslPkiUtility
\ingroup core
\brief Utilites functions for working with SSL certificates, keys and connections
\since 2.5
*/

class QgsSslPkiUtility
{
%TypeHeaderCode
#include <qgssslutils.h>
%End
public:

static QgsSslPkiUtility *instance();

static bool urlToResource( const QString& accessurl, QString *resource );

QgsSslPkiGroup * getSslPkiGroup( const QgsSslPkiSettings& pki );
void putSslPkiGroup( const QgsSslPkiSettings& pki, QgsSslPkiGroup * pkigroup );
void removeSslPkiGroup( const QgsSslPkiSettings& pki );

static const QString qgisCertStoreDirPath();

static bool createQgisCertStoreDir();

static const QString qgisCertsDirPath();
static const QString qgisKeysDirPath();
static const QString qgisIssuersDirPath();

/**
* @return NULL QString if file does not exist
*/
static const QString qgisCertPath( const QString& file );
static const QString qgisKeyPath( const QString& file );
static const QString qgisIssuerPath( const QString& file );

static const QStringList storeCerts( QgsSslPkiSettings::SslStoreType store );
static const QStringList storeKeys( QgsSslPkiSettings::SslStoreType store );
static const QStringList storeIssuers( QgsSslPkiSettings::SslStoreType store );

static QSslCertificate certFromPath( const QString& path, QSsl::EncodingFormat format = QSsl::Pem );

static QSsl::KeyAlgorithm keyAlgorithm( const QByteArray& keydata );

void updateRequestSslConfiguration( QNetworkRequest &request /In,Out/, const QgsSslPkiSettings& pki );
void updateReplyExpectedSslErrors( QNetworkReply *reply, const QgsSslPkiSettings& pki );
};
1 change: 1 addition & 0 deletions python/gui/gui.sip
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
%Include qgsscalerangewidget.sip
%Include qgsscalevisibilitydialog.sip
%Include qgssearchquerybuilder.sip
%Include qgssslcertificatewidget.sip
%Include qgstextannotationitem.sip
%Include qgsvertexmarker.sip
%Include qgsvectorlayertools.sip
Expand Down
3 changes: 3 additions & 0 deletions python/gui/qgscredentialdialog.sip
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ class QgsCredentialDialog : QDialog, QgsCredentials

protected:
virtual bool request( QString realm, QString &username /In,Out/, QString &password /In,Out/, QString message = QString::null );

virtual bool requestSslKey( QString &password /In,Out/, QString &keypath /In,Out/, bool needskeypath,
QString resource = QString::null, QString message = QString::null );
};
55 changes: 55 additions & 0 deletions python/gui/qgssslcertificatewidget.sip
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@

class QgsSslCertificateWidget : QWidget
{
%TypeHeaderCode
#include "qgssslcertificatewidget.h"
%End

public:
enum Validity
{
Valid,
Invalid,
Unknown
};

/** Widget for choosing/inspecting SSL certificate, using different store methods, and testing validity
*/
explicit QgsSslCertificateWidget( QWidget *parent /TransferThis/ = 0, const QString& accessUrl = QString( "" ) );
~QgsSslCertificateWidget();

void loadSettings( QgsSslPkiSettings::SslStoreType storeType = QgsSslPkiSettings::QGISStore,
const QString& certId = "",
const QString& keyId = "",
bool needsKeyPath = false,
bool needsKeyPhrase = false,
const QString& issuerId = "",
bool issuerSelf = false );

void loadSslCertSettings( const QgsSslPkiSettings& pki );

QgsSslPkiSettings toSslCertSettings();

bool certIsValid();

QgsSslPkiSettings::SslStoreType ssLStoreType();
QString certId() const;
QString keyId() const;
bool needsKeyPath();
bool needsKeyPassphrase();
QString issuerId() const;
bool issuerSelfSigned();
QString accessUrl() const;

public slots:
void setSslStoreType( QgsSslPkiSettings::SslStoreType storeType );
void setCertId( const QString& id );
void setKeyId( const QString& id );
void setNeedsKeyPath( bool needsPath );
void setNeedsKeyPassphrase( bool needsPass );
void setIssuerId( const QString& id );
void setIssuerSelfSigned( bool selfSigned );
void setAccessUrl( const QString& url );

void validateCert();
};
7 changes: 7 additions & 0 deletions src/app/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include "qgspluginregistry.h"
#include "qgsmessagelog.h"
#include "qgspythonrunner.h"
#include "qgssslutils.h"

#include <cstdio>
#include <stdio.h>
Expand Down Expand Up @@ -849,6 +850,12 @@ int main( int argc, char *argv[] )
QCoreApplication::addLibraryPath( myPath );
#endif

#ifndef QT_NO_OPENSSL
//set up user-local SSL cert store
bool storeSetup = QgsSslPkiUtility::createQgisCertStoreDir();
QgsDebugMsg( QString( "Setting up QGIS local cert store: %1" ).arg( storeSetup ? "succeeded" : "failed" ) );
#endif

//set up splash screen
QString mySplashPath( QgsCustomization::instance()->splashPath() );
QPixmap myPixmap( mySplashPath + QString( "splash.png" ) );
Expand Down
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ SET(QGIS_CORE_SRCS
qgssimplifymethod.cpp
qgssnapper.cpp
qgsspatialindex.cpp
qgssslutils.cpp
qgstolerance.cpp
qgsvectordataprovider.cpp
qgsvectorfilewriter.cpp
Expand Down Expand Up @@ -542,6 +543,7 @@ SET(QGIS_CORE_HDRS
qgssimplifymethod.h
qgssnapper.h
qgsspatialindex.h
qgssslutils.h
qgstolerance.h
qgsvectordataprovider.h
qgsvectorlayercache.h
Expand Down
51 changes: 51 additions & 0 deletions src/core/qgscredentials.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "qgslogger.h"

#include <QTextStream>
#include <QUrl>

QgsCredentials *QgsCredentials::smInstance = 0;

Expand Down Expand Up @@ -77,6 +78,29 @@ void QgsCredentials::put( QString realm, QString username, QString password )
mCredentialCache.insert( realm, QPair<QString, QString>( username, password ) );
}

bool QgsCredentials::getSslKeyInfo( QString &password, QString &keypath, bool needskeypath,
QString accessurl, QString message )
{
// shorten url of resource to keep any dialog from being too wide
QString resource = QObject::tr( "Unknown" );
if ( !accessurl.isEmpty() )
{
QUrl url( accessurl );
resource = QString( "%1://%2:%3" ).arg( url.scheme() ).arg( url.host() ).arg( url.port() );
}

if ( requestSslKey( password, keypath, needskeypath, resource, message ) )
{
QgsDebugMsg( "Retrieve SSL PKI key password/path succeeded" );
return true;
}
else
{
QgsDebugMsg( "Retrieve SSL PKI key password/path failed" );
return false;
}
}

void QgsCredentials::lock()
{
mMutex.lock();
Expand Down Expand Up @@ -111,3 +135,30 @@ bool QgsCredentialsConsole::request( QString realm, QString &username, QString &

return true;
}

bool QgsCredentialsConsole::requestSslKey( QString &password, QString &keypath, bool needskeypath,
QString resource, QString message )
{
QTextStream in( stdin, QIODevice::ReadOnly );
QTextStream out( stdout, QIODevice::WriteOnly );

if ( !resource.isEmpty() )
out << "For resource: " << resource << endl;
if ( !message.isEmpty() )
out << " message: " << message << endl;
if ( needskeypath )
{
out << "Enter file path for certificate key";
out << "keypath: ";
in >> keypath;
out << "Enter password for certificate key (optional)";
}
else
{
out << "Enter password for certificate key";
}
out << "password: ";
in >> password;

return true;
}
17 changes: 17 additions & 0 deletions src/core/qgscredentials.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ class CORE_EXPORT QgsCredentials
bool get( QString realm, QString &username, QString &password, QString message = QString::null );
void put( QString realm, QString username, QString password );

/**
* @brief Get a passphrase for a private key for a client certificate to be used in an SSL PKI handshake.
* @param The passphrase for the key.
* @param URL shown in the private key's password input dialog, so user remembers what scheme://domain.tld:port the key was meant for.
* @param Optional short message to go below password field.
* @return
*/
bool getSslKeyInfo( QString &password, QString &keypath, bool needskeypath,
QString accessurl, QString message = QString::null );

//! retrieves instance
static QgsCredentials *instance();

Expand Down Expand Up @@ -72,6 +82,10 @@ class CORE_EXPORT QgsCredentials
//! request a password
virtual bool request( QString realm, QString &username, QString &password, QString message = QString::null ) = 0;

//! request a password for an SSL key, and optionally its filepath
virtual bool requestSslKey( QString &password, QString &keypath, bool needskeypath,
QString resource, QString message = QString::null ) = 0;

//! register instance
void setInstance( QgsCredentials *theInstance );

Expand Down Expand Up @@ -108,6 +122,9 @@ class CORE_EXPORT QgsCredentialsConsole : public QObject, public QgsCredentials

protected:
virtual bool request( QString realm, QString &username, QString &password, QString message = QString::null );

virtual bool requestSslKey( QString &password, QString &keypath, bool needskeypath,
QString resource, QString message = QString::null );
};

#endif
Loading