Skip to content
Permalink
Browse files

[auth] Add utility method to validate a PKI bundle

  • Loading branch information
elpaso committed Oct 26, 2017
1 parent f1eba3a commit 89166a0d2ef580b0c34cfd838ae2e1389baaecff
@@ -275,18 +275,27 @@ Get short strings describing an SSL error
%End


static QList<QSslError> validateCertChain( const QList<QSslCertificate> &certificateChain, const QString &hostName = QString(), bool addRootCa = false ) ;
static QList<QSslError> validateCertChain( const QList<QSslCertificate> &certificateChain,
const QString &hostName = QString(),
bool trustRootCa = false ) ;
%Docstring
validateCertChain validates the given ``certificateChain``
\param certificateChain list of certificates to be checked, with leaf first and with optional root CA last
\param hostName (optional) name of the host to be verified
\param addRootCa if true the CA will be added to the trusted CAs for this validation check
\param trustRootCa if true the CA will be added to the trusted CAs for this validation check
:return: list of QSslError, if the list is empty then the cert chain is valid
:rtype: list of QSslError
%End

static QStringList validatePKIBundle( QgsPkiBundle &bundle, bool useIntermediates = true, bool addRootCa = false );
static QStringList validatePKIBundle( QgsPkiBundle &bundle, bool useIntermediates = true, bool trustRootCa = false );
%Docstring
validatePKIBundle validate the PKI bundle by checking the certificate chain, the
expiration and effective dates, optionally trusts the root CA
\param bundle
\param useIntermediates if true the intermediate certs are also checked
\param trustRootCa if true the CA will be added to the trusted CAs for this validation check (if useIntermediates is false)
this option is ignored and set to false
:return: a list of error strings, if the list is empty then the PKI bundle is valid
:rtype: list of str
%End

@@ -221,7 +221,6 @@ class QgsPkiBundle
const QString &keyPass = QString(),
const QList<QSslCertificate> &caChain = QList<QSslCertificate>() );
%Docstring
TORM
Construct a bundle of PKI components from PEM-formatted file paths
\param certPath Certificate file path
\param keyPath Private key path
@@ -233,7 +232,6 @@ class QgsPkiBundle
static const QgsPkiBundle fromPkcs12Paths( const QString &bundlepath,
const QString &bundlepass = QString() );
%Docstring
TORM
Construct a bundle of PKI components from a PKCS#12 file path
\param bundlepath Bundle file path
\param bundlepass Optional bundle passphrase
@@ -295,7 +293,6 @@ class QgsPkiConfigBundle
{
%Docstring
Storage set for constructed SSL certificate, key, associated with an authentication config
TODO: inherit from PKIBundle
%End

%TypeHeaderCode
@@ -1019,7 +1019,9 @@ QList<QPair<QSslError::SslError, QString> > QgsAuthCertUtils::sslErrorEnumString
return errenums;
}

QList<QSslError> QgsAuthCertUtils::validateCertChain( const QList<QSslCertificate> &certificateChain, const QString &hostName, bool addRootCa )
QList<QSslError> QgsAuthCertUtils::validateCertChain( const QList<QSslCertificate> &certificateChain,
const QString &hostName,
bool trustRootCa )
{
QList<QSslError> sslErrors;
QList<QSslCertificate> trustedChain;
@@ -1041,8 +1043,28 @@ QList<QSslError> QgsAuthCertUtils::validateCertChain( const QList<QSslCertificat
}
}

// Check that no certs in the chain are expired or not yet valid or blacklisted
const QList<QSslCertificate> constTrustedChain( trustedChain );
for ( const auto &cert : constTrustedChain )
{
// TODO: move all the checks to QgsAuthCertUtils::certIsViable( )
const QDateTime currentTime = QDateTime::currentDateTime();
if ( cert.expiryDate() <= currentTime )
{
sslErrors << QSslError( QSslError::SslError::CertificateExpired, cert );
}
if ( cert.effectiveDate() >= QDateTime::currentDateTime() )
{
sslErrors << QSslError( QSslError::SslError::CertificateNotYetValid, cert );
}
if ( cert.isBlacklisted() )
{
sslErrors << QSslError( QSslError::SslError::CertificateBlacklisted, cert );
}
}

// Merge in the root CA if present and asked for
if ( addRootCa && trustedChain.count() > 1 && trustedChain.last().isSelfSigned() )
if ( trustRootCa && trustedChain.count() > 1 && trustedChain.last().isSelfSigned() )
{
static QMutex sMutex;
QMutexLocker lock( &sMutex );
@@ -1060,15 +1082,15 @@ QList<QSslError> QgsAuthCertUtils::validateCertChain( const QList<QSslCertificat
return sslErrors;
}

QStringList QgsAuthCertUtils::validatePKIBundle( QgsPkiBundle &bundle, bool useIntermediates, bool addRootCa )
QStringList QgsAuthCertUtils::validatePKIBundle( QgsPkiBundle &bundle, bool useIntermediates, bool trustRootCa )
{
QStringList errors;
QList<QSslError> sslErrors;
if ( useIntermediates )
{
QList<QSslCertificate> certsList( bundle.caChain() );
certsList.insert( 0, bundle.clientCert( ) );
sslErrors = QgsAuthCertUtils::validateCertChain( certsList, QString(), addRootCa );
sslErrors = QgsAuthCertUtils::validateCertChain( certsList, QString(), trustRootCa );
}
else
{
@@ -1101,7 +1123,7 @@ QStringList QgsAuthCertUtils::validatePKIBundle( QgsPkiBundle &bundle, bool useI
}
else
{
// Log? Error? Note that elliptical curve is not supported by QCA
QgsDebugMsg( "Key is not DSA, RSA or DH: validation is not supported by Qt" );
}
if ( ! keyValid )
{
@@ -301,12 +301,23 @@ class CORE_EXPORT QgsAuthCertUtils
* \brief validateCertChain validates the given \a certificateChain
* \param certificateChain list of certificates to be checked, with leaf first and with optional root CA last
* \param hostName (optional) name of the host to be verified
* \param addRootCa if true the CA will be added to the trusted CAs for this validation check
* \param trustRootCa if true the CA will be added to the trusted CAs for this validation check
* \return list of QSslError, if the list is empty then the cert chain is valid
*/
static QList<QSslError> validateCertChain( const QList<QSslCertificate> &certificateChain, const QString &hostName = QString(), bool addRootCa = false ) ;
static QList<QSslError> validateCertChain( const QList<QSslCertificate> &certificateChain,
const QString &hostName = QString(),
bool trustRootCa = false ) ;

static QStringList validatePKIBundle( QgsPkiBundle &bundle, bool useIntermediates = true, bool addRootCa = false );
/**
* \brief validatePKIBundle validate the PKI bundle by checking the certificate chain, the
* expiration and effective dates, optionally trusts the root CA
* \param bundle
* \param useIntermediates if true the intermediate certs are also checked
* \param trustRootCa if true the CA will be added to the trusted CAs for this validation check (if useIntermediates is false)
* this option is ignored and set to false
* \return a list of error strings, if the list is empty then the PKI bundle is valid
*/
static QStringList validatePKIBundle( QgsPkiBundle &bundle, bool useIntermediates = true, bool trustRootCa = false );

private:
static void appendDirSegment_( QStringList &dirname, const QString &segment, QString value );
@@ -201,7 +201,6 @@ class CORE_EXPORT QgsPkiBundle
const QList<QSslCertificate> &caChain = QList<QSslCertificate>() );

/**
* TORM
* Construct a bundle of PKI components from PEM-formatted file paths
* \param certPath Certificate file path
* \param keyPath Private key path
@@ -214,7 +213,6 @@ class CORE_EXPORT QgsPkiBundle
const QList<QSslCertificate> &caChain = QList<QSslCertificate>() );

/**
* TORM
* Construct a bundle of PKI components from a PKCS#12 file path
* \param bundlepath Bundle file path
* \param bundlepass Optional bundle passphrase
@@ -256,7 +254,6 @@ class CORE_EXPORT QgsPkiBundle
/**
* \ingroup core
* \brief Storage set for constructed SSL certificate, key, associated with an authentication config
* TODO: inherit from PKIBundle
*/
class CORE_EXPORT QgsPkiConfigBundle
{
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for bindings to core authentication system classes
From build dir: ctest -R PyQgsAuthenticationSystem -V
From build dir: LC_ALL=en_US.UTF-8 ctest -R PyQgsAuthenticationSystem -V
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -17,19 +17,13 @@
import os
import tempfile

from qgis.core import QgsAuthManager, QgsAuthCertUtils, QgsPkiBundle, QgsAuthMethodConfig, QgsAuthMethod, QgsAuthConfigSslServer, QgsApplication
from qgis.core import QgsAuthCertUtils, QgsPkiBundle, QgsAuthMethodConfig, QgsAuthMethod, QgsAuthConfigSslServer, QgsApplication
from qgis.gui import QgsAuthEditorWidgets


from qgis.PyQt.QtCore import QFileInfo, qDebug
from qgis.PyQt.QtWidgets import QDialog, QVBoxLayout, QDialogButtonBox
from qgis.PyQt.QtTest import QTest
from qgis.PyQt.QtNetwork import QSsl, QSslError, QSslSocket

from qgis.testing import (
start_app,
unittest,
)
from qgis.PyQt.QtTest import QTest
from qgis.PyQt.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout
from qgis.testing import start_app, unittest

from utilities import unitTestDataPath

@@ -635,6 +629,7 @@ def testChain(path):

testChain(PKIDATA + '/chain_subissuer-issuer-root.pem')
testChain(PKIDATA + '/localhost_ssl_w-chain.pem')
testChain(PKIDATA + '/fra_w-chain.pem')

path = PKIDATA + '/localhost_ssl_w-chain.pem'

@@ -650,7 +645,48 @@ def testChain(path):
# and a right domain is set
self.assertTrue(len(QgsAuthCertUtils.validateCertChain(QgsAuthCertUtils.certsFromFile(path), 'localhost', False)) > 0)

testChain(PKIDATA + '/fra_w-chain.pem')
def test_validate_pki_bundle(self):
"""Text the pki bundle validation"""

def mkPEMBundle(client_cert, client_key, password, chain):
return QgsPkiBundle.fromPemPaths(PKIDATA + '/' + client_cert,
PKIDATA + '/' + client_key,
password,
QgsAuthCertUtils.certsFromFile(
PKIDATA + '/' + chain
))

# Valid bundle:
bundle = mkPEMBundle('fra_cert.pem', 'fra_key.pem', 'password', 'chain_subissuer-issuer-root.pem')

# Test valid bundle with intermediates and without trusted root
self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle), ['The root certificate of the certificate chain is self-signed, and untrusted'])
# Test valid without intermediates
self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle, False), ['The issuer certificate of a locally looked up certificate could not be found', 'No certificates could be verified'])
# Test valid with intermediates and trusted root
self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle, True, True), [])

# Wrong chain
bundle = mkPEMBundle('fra_cert.pem', 'fra_key.pem', 'password', 'chain_issuer2-root2.pem')
# Test invalid bundle with intermediates and without trusted root
self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle), ['The issuer certificate of a locally looked up certificate could not be found', 'No certificates could be verified'])
# Test valid without intermediates
self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle, False), ['The issuer certificate of a locally looked up certificate could not be found', 'No certificates could be verified'])
# Test valid with intermediates and trusted root
self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle, True, True), ['The issuer certificate of a locally looked up certificate could not be found', 'No certificates could be verified'])

# Wrong key
bundle = mkPEMBundle('fra_cert.pem', 'ptolemy_key.pem', 'password', 'chain_subissuer-issuer-root.pem')
# Test invalid bundle with intermediates and without trusted root
self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle), ['The root certificate of the certificate chain is self-signed, and untrusted', 'Private key does not match client certificate public key.'])
# Test invalid without intermediates
self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle, False), ['The issuer certificate of a locally looked up certificate could not be found', 'No certificates could be verified', 'Private key does not match client certificate public key.'])
# Test invalid with intermediates and trusted root
self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle, True, True), ['Private key does not match client certificate public key.'])

# TODO: Wrong root CA
# TODO: expired/not-yet-valid cert
# TODO: expired/not-yet-valid intermediate (is it possible to build a cert from one of those?)


if __name__ == '__main__':

0 comments on commit 89166a0

Please sign in to comment.
You can’t perform that action at this time.