From 025c6f2e25321edc70ce5c23dcf197c2ba2422f8 Mon Sep 17 00:00:00 2001 From: Larry Shaffer Date: Tue, 17 Oct 2017 15:57:01 -0600 Subject: [PATCH 1/5] [auth] Add error trap and debug fixes for PKCS#12 auth method --- src/auth/pkipkcs12/qgsauthpkcs12method.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/auth/pkipkcs12/qgsauthpkcs12method.cpp b/src/auth/pkipkcs12/qgsauthpkcs12method.cpp index 53bd2956de0..57b5411f754 100644 --- a/src/auth/pkipkcs12/qgsauthpkcs12method.cpp +++ b/src/auth/pkipkcs12/qgsauthpkcs12method.cpp @@ -282,6 +282,12 @@ QgsPkiConfigBundle *QgsAuthPkcs12Method::getPkiConfigBundle( const QString &auth QStringList bundlelist = QgsAuthCertUtils::pkcs12BundleToPem( mconfig.config( QStringLiteral( "bundlepath" ) ), mconfig.config( QStringLiteral( "bundlepass" ) ), false ); + if ( bundlelist.isEmpty() || bundlelist.size() < 2 ) + { + QgsDebugMsg( QString( "PKI bundle for authcfg %1: insert FAILED, PKCS#12 bundle parsing failed" ).arg( authcfg ) ); + return bundle; + } + // init client cert // Note: if this is not valid, no sense continuing QSslCertificate clientcert( bundlelist.at( 0 ).toLatin1() ); @@ -291,6 +297,11 @@ QgsPkiConfigBundle *QgsAuthPkcs12Method::getPkiConfigBundle( const QString &auth return bundle; } + // !!! DON'T LEAVE THESE UNCOMMENTED !!! + // QgsDebugMsg( QString( "PKI bundle key for authcfg: \n%1" ).arg( bundlelist.at( 1 ) ) ); + // QgsDebugMsg( QString( "PKI bundle key pass for authcfg: \n%1" ) + // .arg( !mconfig.config( QStringLiteral( "bundlepass" ) ).isNull() ? mconfig.config( QStringLiteral( "bundlepass" ) ) : QStringLiteral() ) ); + // init key QSslKey clientkey( bundlelist.at( 1 ).toLatin1(), QSsl::Rsa, From 8107f910376678c196cc796ec28c8cde4046450a Mon Sep 17 00:00:00 2001 From: Larry Shaffer Date: Wed, 18 Oct 2017 15:09:18 -0600 Subject: [PATCH 2/5] [auth] Move inline file reads to static func; add PKCS#8 sniffing func --- python/core/auth/qgsauthcertutils.sip | 18 +++++ src/core/auth/qgsauthcertutils.cpp | 37 ++++++---- src/core/auth/qgsauthcertutils.h | 13 ++++ src/core/auth/qgsauthconfig.cpp | 25 +------ src/gui/auth/qgsauthimportidentitydialog.cpp | 22 +----- tests/src/core/CMakeLists.txt | 1 + tests/src/core/testqgsauthcertutils.cpp | 78 ++++++++++++++++++++ 7 files changed, 139 insertions(+), 55 deletions(-) create mode 100644 tests/src/core/testqgsauthcertutils.cpp diff --git a/python/core/auth/qgsauthcertutils.sip b/python/core/auth/qgsauthcertutils.sip index 1d2520068d3..1eb2a9a72b1 100644 --- a/python/core/auth/qgsauthcertutils.sip +++ b/python/core/auth/qgsauthcertutils.sip @@ -81,6 +81,14 @@ Map certificate sha1 to certificate as simple cache %End + static QByteArray fileData( const QString &path, bool astext = false ); +%Docstring + Return data from a local file via a read-only operation + \param astext Whether to open the file as text, otherwise as binary + :return: All data contained in file or empty contents if file does not exist + :rtype: QByteArray +%End + static QList certsFromFile( const QString &certspath ); %Docstring Return list of concatenated certs from a PEM or DER formatted file @@ -150,6 +158,16 @@ Return list of concatenated certs from a PEM Base64 text block :rtype: list of str %End + static bool pemIsPkcs8( const QString &keyPemTxt ); +%Docstring + Determine if the PEM-encoded text of a key is PKCS#8 format + \param keyPemTxt PEM-encoded text + :return: True if PKCS#8, otherwise false + :rtype: bool +%End + + + static QStringList pkcs12BundleToPem( const QString &bundlepath, const QString &bundlepass = QString(), bool reencrypt = true ); diff --git a/src/core/auth/qgsauthcertutils.cpp b/src/core/auth/qgsauthcertutils.cpp index fc9273a96d5..38b9dc5fd1d 100644 --- a/src/core/auth/qgsauthcertutils.cpp +++ b/src/core/auth/qgsauthcertutils.cpp @@ -94,22 +94,26 @@ QMap > QgsAuthCertUtils::sslConfigsGroupe return orgconfigs; } -static QByteArray fileData_( const QString &path, bool astext = false ) +QByteArray QgsAuthCertUtils::fileData( const QString &path, bool astext ) { QByteArray data; QFile file( path ); - if ( file.exists() ) + if ( !file.exists() ) { - QFile::OpenMode openflags( QIODevice::ReadOnly ); - if ( astext ) - openflags |= QIODevice::Text; - bool ret = file.open( openflags ); - if ( ret ) - { - data = file.readAll(); - } - file.close(); + QgsDebugMsg( QStringLiteral( "Read file error, file not found: %1" ).arg( path ) ); + return data; } + // TODO: add checks for locked file, etc., to ensure it can be read + QFile::OpenMode openflags( QIODevice::ReadOnly ); + if ( astext ) + openflags |= QIODevice::Text; + bool ret = file.open( openflags ); + if ( ret ) + { + data = file.readAll(); + } + file.close(); + return data; } @@ -117,7 +121,7 @@ QList QgsAuthCertUtils::certsFromFile( const QString &certspath { QList certs; bool pem = certspath.endsWith( QLatin1String( ".pem" ), Qt::CaseInsensitive ); - certs = QSslCertificate::fromData( fileData_( certspath, pem ), pem ? QSsl::Pem : QSsl::Der ); + certs = QSslCertificate::fromData( QgsAuthCertUtils::fileData( certspath, pem ), pem ? QSsl::Pem : QSsl::Der ); if ( certs.isEmpty() ) { QgsDebugMsg( QString( "Parsed cert(s) EMPTY for path: %1" ).arg( certspath ) ); @@ -181,7 +185,7 @@ QSslKey QgsAuthCertUtils::keyFromFile( const QString &keypath, QString *algtype ) { bool pem = keypath.endsWith( QLatin1String( ".pem" ), Qt::CaseInsensitive ); - QByteArray keydata( fileData_( keypath, pem ) ); + QByteArray keydata( QgsAuthCertUtils::fileData( keypath, pem ) ); QSslKey clientkey; clientkey = QSslKey( keydata, @@ -262,6 +266,13 @@ QStringList QgsAuthCertUtils::certKeyBundleToPem( const QString &certpath, return QStringList() << certpem << keypem << algtype; } +bool QgsAuthCertUtils::pemIsPkcs8( const QString &keyPemTxt ) +{ + QString pkcs8Header = QStringLiteral( "-----BEGIN PRIVATE KEY-----" ); + QString pkcs8Footer = QStringLiteral( "-----END PRIVATE KEY-----" ); + return keyPemTxt.contains( pkcs8Header ) && keyPemTxt.contains( pkcs8Footer ); +} + QStringList QgsAuthCertUtils::pkcs12BundleToPem( const QString &bundlepath, const QString &bundlepass, bool reencrypt ) diff --git a/src/core/auth/qgsauthcertutils.h b/src/core/auth/qgsauthcertutils.h index 5b2932a2170..064366de3ca 100644 --- a/src/core/auth/qgsauthcertutils.h +++ b/src/core/auth/qgsauthcertutils.h @@ -104,6 +104,13 @@ class CORE_EXPORT QgsAuthCertUtils */ static QMap< QString, QList > sslConfigsGroupedByOrg( const QList &configs ) SIP_SKIP; + /** + * Return data from a local file via a read-only operation + * \param astext Whether to open the file as text, otherwise as binary + * \returns All data contained in file or empty contents if file does not exist + */ + static QByteArray fileData( const QString &path, bool astext = false ); + //! Return list of concatenated certs from a PEM or DER formatted file static QList certsFromFile( const QString &certspath ); @@ -158,6 +165,12 @@ class CORE_EXPORT QgsAuthCertUtils bool reencrypt = true ); /** + * Determine if the PEM-encoded text of a key is PKCS#8 format + * \param keyPemTxt PEM-encoded text + * \returns True if PKCS#8, otherwise false + */ + static bool pemIsPkcs8( const QString &keyPemTxt ); + * Return list of certificate, private key and algorithm (as PEM text) for a PKCS#12 bundle * \param bundlepath File path to the PKCS bundle * \param bundlepass Passphrase for bundle diff --git a/src/core/auth/qgsauthconfig.cpp b/src/core/auth/qgsauthconfig.cpp index 177422ad30f..e1f8b4df274 100644 --- a/src/core/auth/qgsauthconfig.cpp +++ b/src/core/auth/qgsauthconfig.cpp @@ -23,6 +23,8 @@ #include #include +#include "qgsauthcertutils.h" + ////////////////////////////////////////////// // QgsAuthMethodConfig @@ -172,25 +174,6 @@ QgsPkiBundle::QgsPkiBundle( const QSslCertificate &clientCert, setClientKey( clientKey ); } -static QByteArray fileData_( const QString &path, bool astext = false ) -{ - QByteArray data; - QFile file( path ); - if ( file.exists() ) - { - QFile::OpenMode openflags( QIODevice::ReadOnly ); - if ( astext ) - openflags |= QIODevice::Text; - bool ret = file.open( openflags ); - if ( ret ) - { - data = file.readAll(); - } - file.close(); - } - return data; -} - const QgsPkiBundle QgsPkiBundle::fromPemPaths( const QString &certPath, const QString &keyPath, const QString &keyPass, @@ -207,12 +190,12 @@ const QgsPkiBundle QgsPkiBundle::fromPemPaths( const QString &certPath, { // client cert bool pem = certPath.endsWith( QLatin1String( ".pem" ), Qt::CaseInsensitive ); - QSslCertificate clientcert( fileData_( certPath, pem ), pem ? QSsl::Pem : QSsl::Der ); + QSslCertificate clientcert( QgsAuthCertUtils::fileData( certPath, pem ), pem ? QSsl::Pem : QSsl::Der ); pkibundle.setClientCert( clientcert ); // client key bool pem_key = keyPath.endsWith( QLatin1String( ".pem" ), Qt::CaseInsensitive ); - QByteArray keydata( fileData_( keyPath, pem_key ) ); + QByteArray keydata( QgsAuthCertUtils::fileData( keyPath, pem_key ) ); QSslKey clientkey; clientkey = QSslKey( keydata, diff --git a/src/gui/auth/qgsauthimportidentitydialog.cpp b/src/gui/auth/qgsauthimportidentitydialog.cpp index c819ff9b095..481cda0eaf2 100644 --- a/src/gui/auth/qgsauthimportidentitydialog.cpp +++ b/src/gui/auth/qgsauthimportidentitydialog.cpp @@ -29,26 +29,6 @@ #include "qgslogger.h" -static QByteArray fileData_( const QString &path, bool astext = false ) -{ - QByteArray data; - QFile file( path ); - if ( file.exists() ) - { - QFile::OpenMode openflags( QIODevice::ReadOnly ); - if ( astext ) - openflags |= QIODevice::Text; - bool ret = file.open( openflags ); - if ( ret ) - { - data = file.readAll(); - } - file.close(); - } - return data; -} - - QgsAuthImportIdentityDialog::QgsAuthImportIdentityDialog( QgsAuthImportIdentityDialog::IdentityType identitytype, QWidget *parent ) : QDialog( parent ) @@ -306,7 +286,7 @@ bool QgsAuthImportIdentityDialog::validatePkiPaths() // check for valid private key and that any supplied password works bool keypem = keypath.endsWith( QLatin1String( ".pem" ), Qt::CaseInsensitive ); - QByteArray keydata( fileData_( keypath, keypem ) ); + QByteArray keydata( QgsAuthCertUtils::fileData( keypath, keypem ) ); QSslKey clientkey; QString keypass = lePkiPathsKeyPass->text(); diff --git a/tests/src/core/CMakeLists.txt b/tests/src/core/CMakeLists.txt index 5c47b388249..7a463a1561e 100755 --- a/tests/src/core/CMakeLists.txt +++ b/tests/src/core/CMakeLists.txt @@ -74,6 +74,7 @@ SET(TESTS testqgsapplication.cpp testqgsatlascomposition.cpp testqgsauthcrypto.cpp + testqgsauthcertutils.cpp testqgsauthconfig.cpp testqgsauthmanager.cpp testqgsblendmodes.cpp diff --git a/tests/src/core/testqgsauthcertutils.cpp b/tests/src/core/testqgsauthcertutils.cpp new file mode 100644 index 00000000000..28953180e0a --- /dev/null +++ b/tests/src/core/testqgsauthcertutils.cpp @@ -0,0 +1,78 @@ +/*************************************************************************** + TestQgsAuthCertUtils.cpp + ---------------------- + Date : October 2017 + Copyright : (C) 2017 by Boundless Spatial, Inc. USA + Author : Larry Shaffer + Email : lshaffer at boundlessgeo dot com + *************************************************************************** + * * + * 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 * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgstest.h" +#include +#include +#include +#include + +#include "qgsapplication.h" +#include "qgsauthcrypto.h" +#include "qgsauthcertutils.h" +#include "qgslogger.h" + +/** + * \ingroup UnitTests + * Unit tests for QgsAuthCertUtils static functions + */ +class TestQgsAuthCertUtils: public QObject +{ + Q_OBJECT + + private slots: + void initTestCase(); + void cleanupTestCase(); + void init() {} + void cleanup() {} + + void testPkcsUtils(); + + private: + static QString sPkiData; +}; + +QString TestQgsAuthCertUtils::sPkiData = QStringLiteral( TEST_DATA_DIR ) + "/auth_system/certs_keys"; + +void TestQgsAuthCertUtils::initTestCase() +{ + QgsApplication::init(); + QgsApplication::initQgis(); + if ( QgsAuthCrypto::isDisabled() ) + QSKIP( "QCA's qca-ossl plugin is missing, skipping test case", SkipAll ); +} + +void TestQgsAuthCertUtils::cleanupTestCase() +{ + QgsApplication::exitQgis(); +} + +void TestQgsAuthCertUtils::testPkcsUtils() +{ + QByteArray pkcs; + + pkcs = QgsAuthCertUtils::fileData( sPkiData + "/gerardus_key.pem", false ); + QVERIFY( !pkcs.isEmpty() ); + QVERIFY( !QgsAuthCertUtils::pemIsPkcs8( QString( pkcs ) ) ); + + pkcs.clear(); + pkcs = QgsAuthCertUtils::fileData( sPkiData + "/gerardus_key-pkcs8-rsa.pem", false ); + QVERIFY( !pkcs.isEmpty() ); + QVERIFY( QgsAuthCertUtils::pemIsPkcs8( QString( pkcs ) ) ); +} + +QGSTEST_MAIN( TestQgsAuthCertUtils ) +#include "testqgsauthcertutils.moc" From c66de140551eb7b3aa1ac04fa2e772a374f47453 Mon Sep 17 00:00:00 2001 From: Larry Shaffer Date: Wed, 18 Oct 2017 16:03:40 -0600 Subject: [PATCH 3/5] [auth] Add PKCS8 to PKCS1 key conversion for macOS (deps on libtasn1) See description of QgsAuthCertUtils::pkcs8PrivateKey. This fix may be needed on other platforms (untested at this point), because Qt5 QSslkey class *still* does not directly support creation using non-PKCS1 PEM- or DER-encoded data, though QCA, whose qca-ossl plugin is linked to OpenSSL, does support PKCS1 and PKCS8. --- CMakeLists.txt | 5 + cmake/FindLibtasn1.cmake | 45 ++++ resources/CMakeLists.txt | 6 + resources/pkcs8.asn | 63 ++++++ src/core/CMakeLists.txt | 9 +- src/core/auth/qgsauthcertutils.cpp | 214 +++++++++++++++++- src/core/auth/qgsauthcertutils.h | 19 ++ tests/src/core/testqgsauthcertutils.cpp | 57 +++++ .../certs_keys/fra_key-pkcs8-rsa.der | Bin 0 -> 635 bytes .../certs_keys/fra_key-pkcs8-rsa.pem | 16 ++ .../certs_keys/gerardus_key-pkcs8-rsa.der | Bin 0 -> 636 bytes .../certs_keys/gerardus_key-pkcs8-rsa.pem | 16 ++ .../certs_keys/nicholas_key-pkcs8-rsa.der | Bin 0 -> 633 bytes .../certs_keys/nicholas_key-pkcs8-rsa.pem | 16 ++ .../certs_keys/ptolemy_key-pkcs8-rsa.der | Bin 0 -> 634 bytes .../certs_keys/ptolemy_key-pkcs8-rsa.pem | 16 ++ 16 files changed, 480 insertions(+), 2 deletions(-) create mode 100644 cmake/FindLibtasn1.cmake create mode 100644 resources/pkcs8.asn create mode 100644 tests/testdata/auth_system/certs_keys/fra_key-pkcs8-rsa.der create mode 100644 tests/testdata/auth_system/certs_keys/fra_key-pkcs8-rsa.pem create mode 100644 tests/testdata/auth_system/certs_keys/gerardus_key-pkcs8-rsa.der create mode 100644 tests/testdata/auth_system/certs_keys/gerardus_key-pkcs8-rsa.pem create mode 100644 tests/testdata/auth_system/certs_keys/nicholas_key-pkcs8-rsa.der create mode 100644 tests/testdata/auth_system/certs_keys/nicholas_key-pkcs8-rsa.pem create mode 100644 tests/testdata/auth_system/certs_keys/ptolemy_key-pkcs8-rsa.der create mode 100644 tests/testdata/auth_system/certs_keys/ptolemy_key-pkcs8-rsa.pem diff --git a/CMakeLists.txt b/CMakeLists.txt index d7d812933d9..9e161950379 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -320,6 +320,11 @@ IF(WITH_CORE) FIND_QCAOSSL_PLUGIN_CPP(ENABLE_TESTS) ENDIF(NOT MSVC) + IF (APPLE) + # Libtasn1 is for DER-encoded PKI ASN.1 parsing/extracting workarounds + FIND_PACKAGE(Libtasn1 REQUIRED) + ENDIF (APPLE) + IF (SUPPRESS_QT_WARNINGS) # Newer versions of UseQt4.cmake include Qt with -isystem automatically # This can be used to force this behavior on older systems diff --git a/cmake/FindLibtasn1.cmake b/cmake/FindLibtasn1.cmake new file mode 100644 index 00000000000..c416e874760 --- /dev/null +++ b/cmake/FindLibtasn1.cmake @@ -0,0 +1,45 @@ +# Find Libtasn1 +# ~~~~~~~~~~~~~~~ +# CMake module to search for Libtasn1 ASN.1 library and header(s) from: +# https://www.gnu.org/software/libtasn1/ +# +# If it's found it sets LIBTASN1_FOUND to TRUE +# and following variables are set: +# LIBTASN1_INCLUDE_DIR +# LIBTASN1_LIBRARY +# +# Copyright (c) 2017, Boundless Spatial +# Author: Larry Shaffer +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + + +find_path(LIBTASN1_INCLUDE_DIR + NAMES libtasn1.h + PATHS + ${LIB_DIR}/include + "$ENV{LIB_DIR}/include" + $ENV{INCLUDE} + /usr/local/include + /usr/include +) + +find_library(LIBTASN1_LIBRARY + NAMES tasn1 + PATHS + ${LIB_DIR} + "$ENV{LIB_DIR}" + $ENV{LIB} + /usr/local/lib + /usr/lib +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + Libtasn1 + REQUIRED_VARS LIBTASN1_INCLUDE_DIR LIBTASN1_LIBRARY + FOUND_VAR LIBTASN1_FOUND +) + +mark_as_advanced(LIBTASN1_INCLUDE_DIR LIBTASN1_LIBRARY) diff --git a/resources/CMakeLists.txt b/resources/CMakeLists.txt index b2ed42bb823..fe9bbc4d1f9 100644 --- a/resources/CMakeLists.txt +++ b/resources/CMakeLists.txt @@ -9,3 +9,9 @@ INSTALL(DIRECTORY data DESTINATION ${QGIS_DATA_DIR}/resources) IF (WITH_SERVER) INSTALL(DIRECTORY server DESTINATION ${QGIS_DATA_DIR}/resources) ENDIF (WITH_SERVER) + +IF (APPLE) + # ASN.1 definition files of PKIX elements + INSTALL(FILES pkcs8.asn + DESTINATION ${QGIS_DATA_DIR}/resources) +ENDIF (APPLE) diff --git a/resources/pkcs8.asn b/resources/pkcs8.asn new file mode 100644 index 00000000000..2aee10089c8 --- /dev/null +++ b/resources/pkcs8.asn @@ -0,0 +1,63 @@ +PKCS-8 {iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-8(8) + modules(1) pkcs-8(1)} + +-- $Revision: 1.5 $ + +-- This module has been checked for conformance with the ASN.1 +-- standard by the OSS ASN.1 Tools + +DEFINITIONS EXPLICIT TAGS ::= + +BEGIN + +-- EXPORTS All -- +-- All types and values defined in this module is exported for use in +-- other ASN.1 modules. + +-- attribute data types -- + +Attribute ::= SEQUENCE { + type AttributeType, + values SET OF AttributeValue + -- at least one value is required -- +} + +AttributeType ::= OBJECT IDENTIFIER + +AttributeValue ::= ANY DEFINED BY type + +AttributeTypeAndValue ::= SEQUENCE { + type AttributeType, + value AttributeValue } + +AlgorithmIdentifier ::= SEQUENCE { + algorithm OBJECT IDENTIFIER, + parameters ANY DEFINED BY algorithm OPTIONAL } + -- contains a value of the type + -- registered for use with the + -- algorithm object identifier value + +-- Private-key information syntax + +PrivateKeyInfo ::= SEQUENCE { + version Version, + privateKeyAlgorithm AlgorithmIdentifier, + privateKey PrivateKey, + attributes [0] Attributes OPTIONAL } + +Version ::= INTEGER {v1(0)} + +PrivateKey ::= OCTET STRING + +Attributes ::= SET OF Attribute + +-- Encrypted private-key information syntax + +EncryptedPrivateKeyInfo ::= SEQUENCE { + encryptionAlgorithm AlgorithmIdentifier, + encryptedData EncryptedData +} + +EncryptedData ::= OCTET STRING + +END diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 6dcce0d9ea6..d898a12a6e7 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1179,6 +1179,13 @@ INCLUDE_DIRECTORIES(SYSTEM ${QTKEYCHAIN_INCLUDE_DIR} ) +IF (APPLE) + # Libtasn1 is for DER-encoded PKI ASN.1 parsing/extracting workarounds + INCLUDE_DIRECTORIES(SYSTEM + ${LIBTASN1_INCLUDE_DIR} + ) +ENDIF (APPLE) + #for PAL classes IF (WIN32) @@ -1249,7 +1256,7 @@ IF (WIN32) ENDIF (WIN32) IF (APPLE) - TARGET_LINK_LIBRARIES(qgis_core qgis_native) + TARGET_LINK_LIBRARIES(qgis_core qgis_native ${LIBTASN1_LIBRARY}) ENDIF (APPLE) IF (NOT WITH_INTERNAL_QEXTSERIALPORT) diff --git a/src/core/auth/qgsauthcertutils.cpp b/src/core/auth/qgsauthcertutils.cpp index 38b9dc5fd1d..e6bc1fd1137 100644 --- a/src/core/auth/qgsauthcertutils.cpp +++ b/src/core/auth/qgsauthcertutils.cpp @@ -23,9 +23,16 @@ #include #include +#include "qgsapplication.h" #include "qgsauthmanager.h" #include "qgslogger.h" +#ifdef Q_OS_MAC +#include +#include "libtasn1.h" +#endif + + QString QgsAuthCertUtils::getSslProtocolName( QSsl::SslProtocol protocol ) { switch ( protocol ) @@ -273,37 +280,242 @@ bool QgsAuthCertUtils::pemIsPkcs8( const QString &keyPemTxt ) return keyPemTxt.contains( pkcs8Header ) && keyPemTxt.contains( pkcs8Footer ); } +#ifdef Q_OS_MAC +QByteArray QgsAuthCertUtils::pkcs8PrivateKey( QByteArray &pkcs8Der ) +{ + QByteArray pkcs1; + + if ( pkcs8Der.isEmpty() ) + { + QgsDebugMsg( QStringLiteral( "ERROR, passed DER is empty" ) ); + return pkcs1; + } + // Dump as unarmored PEM format, e.g. missing '-----BEGIN|END...' wrapper + //QgsDebugMsg ( QStringLiteral( "pkcs8Der: %1" ).arg( QString( pkcs8Der.toBase64() ) ) ); + + QFileInfo asnDefsRsrc( QgsApplication::pkgDataPath() + QStringLiteral( "/resources/pkcs8.asn" ) ); + if ( ! asnDefsRsrc.exists() ) + { + QgsDebugMsg( QStringLiteral( "ERROR, pkcs.asn resource file not found: %1" ).arg( asnDefsRsrc.filePath() ) ); + return pkcs1; + } + const char *asnDefsFile = asnDefsRsrc.absoluteFilePath().toLocal8Bit().constData(); + + int asn1_result = ASN1_SUCCESS, der_len = 0, oct_len = 0; + asn1_node definitions = NULL, structure = NULL; + char errorDescription[ASN1_MAX_ERROR_DESCRIPTION_SIZE], oct_data[1024]; + unsigned char *der = NULL; + unsigned int flags = 0; //TODO: see if any or all ASN1_DECODE_FLAG_* flags can be set + unsigned oct_etype; + + // Base PKCS#8 element to decode + QString typeName( QStringLiteral( "PKCS-8.PrivateKeyInfo" ) ); + + asn1_result = asn1_parser2tree( asnDefsFile, &definitions, errorDescription ); + + switch ( asn1_result ) + { + case ASN1_SUCCESS: + QgsDebugMsgLevel( QStringLiteral( "Parse: done.\n" ), 4 ); + break; + case ASN1_FILE_NOT_FOUND: + QgsDebugMsg( QStringLiteral( "ERROR, file not found: %1" ).arg( asnDefsFile ) ); + return pkcs1; + case ASN1_SYNTAX_ERROR: + case ASN1_IDENTIFIER_NOT_FOUND: + case ASN1_NAME_TOO_LONG: + QgsDebugMsg( QStringLiteral( "ERROR, asn1 parsing: %1" ).arg( errorDescription ) ); + return pkcs1; + default: + QgsDebugMsg( QStringLiteral( "ERROR, libtasn1: %1" ).arg( asn1_strerror( asn1_result ) ) ); + return pkcs1; + } + + // Generate the ASN.1 structure + asn1_result = asn1_create_element( definitions, typeName.toLatin1().constData(), &structure ); + + //asn1_print_structure( stdout, structure, "", ASN1_PRINT_ALL); + + if ( asn1_result != ASN1_SUCCESS ) + { + QgsDebugMsg( QStringLiteral( "ERROR, structure creation: %1" ).arg( asn1_strerror( asn1_result ) ) ); + goto PKCS1DONE; + } + + // Populate the ASN.1 structure with decoded DER data + der = reinterpret_cast( pkcs8Der.data() ); + der_len = pkcs8Der.size(); + + if ( flags != 0 ) + { + asn1_result = asn1_der_decoding2( &structure, der, &der_len, flags, errorDescription ); + } + else + { + asn1_result = asn1_der_decoding( &structure, der, der_len, errorDescription ); + } + + if ( asn1_result != ASN1_SUCCESS ) + { + QgsDebugMsg( QStringLiteral( "ERROR, decoding: %1" ).arg( errorDescription ) ); + goto PKCS1DONE; + } + else + { + QgsDebugMsgLevel( QStringLiteral( "Decoding: %1" ).arg( asn1_strerror( asn1_result ) ), 4 ); + } + + if ( QgsLogger::debugLevel() >= 4 ) + { + QgsDebugMsg( QStringLiteral( "DECODING RESULT:" ) ); + asn1_print_structure( stdout, structure, "", ASN1_PRINT_NAME_TYPE_VALUE ); + } + + // Validate and extract privateKey value + QgsDebugMsgLevel( QStringLiteral( "Validating privateKey type..." ), 4 ); + typeName.append( QStringLiteral( ".privateKey" ) ); + QgsDebugMsgLevel( QStringLiteral( "privateKey element name: %1" ).arg( typeName ), 4 ); + + asn1_result = asn1_read_value_type( structure, "privateKey", NULL, &oct_len, &oct_etype ); + + if ( asn1_result != ASN1_MEM_ERROR ) // not sure why ASN1_MEM_ERROR = success, but it does + { + QgsDebugMsg( QStringLiteral( "ERROR, asn1 read privateKey value type: %1" ).arg( asn1_strerror( asn1_result ) ) ); + goto PKCS1DONE; + } + + if ( oct_etype != ASN1_ETYPE_OCTET_STRING ) + { + QgsDebugMsg( QStringLiteral( "ERROR, asn1 privateKey value not octet string, but type: %1" ).arg( static_cast( oct_etype ) ) ); + goto PKCS1DONE; + } + + if ( oct_len == 0 ) + { + QgsDebugMsg( QStringLiteral( "ERROR, asn1 privateKey octet string empty" ) ); + goto PKCS1DONE; + } + + QgsDebugMsgLevel( QStringLiteral( "Reading privateKey value..." ), 4 ); + asn1_result = asn1_read_value( structure, "privateKey", oct_data, &oct_len ); + + if ( asn1_result != ASN1_SUCCESS ) + { + QgsDebugMsg( QStringLiteral( "ERROR, asn1 read privateKey value: %1" ).arg( asn1_strerror( asn1_result ) ) ); + goto PKCS1DONE; + } + + if ( oct_len == 0 ) + { + QgsDebugMsg( QStringLiteral( "ERROR, asn1 privateKey value octet string empty" ) ); + goto PKCS1DONE; + } + + pkcs1 = QByteArray( oct_data, oct_len ); + + // !!! SENSITIVE DATA !!! + QgsDebugMsgLevel( QStringLiteral( "privateKey octet data as PEM: %1" ).arg( QString( pkcs1.toBase64() ) ), 9 ); + +PKCS1DONE: + + asn1_delete_structure( &structure ); + return pkcs1; +} +#endif + QStringList QgsAuthCertUtils::pkcs12BundleToPem( const QString &bundlepath, const QString &bundlepass, bool reencrypt ) { QStringList empty; if ( !QCA::isSupported( "pkcs12" ) ) + { + QgsDebugMsg( QString( "QCA does not support PKCS#12" ) ); return empty; + } QCA::KeyBundle bundle( QgsAuthCertUtils::qcaKeyBundle( bundlepath, bundlepass ) ); if ( bundle.isNull() ) + { + QgsDebugMsg( QString( "FAILED to convert PKCS#12 file to QCA key bundle: %1" ).arg( bundlepath ) ); return empty; + } QCA::SecureArray passarray; if ( reencrypt && !bundlepass.isEmpty() ) + { passarray = QCA::SecureArray( bundlepass.toUtf8() ); + } QString algtype; + QSsl::KeyAlgorithm keyalg = QSsl::Opaque; if ( bundle.privateKey().isRSA() ) { algtype = QStringLiteral( "rsa" ); + keyalg = QSsl::Rsa; } else if ( bundle.privateKey().isDSA() ) { algtype = QStringLiteral( "dsa" ); + keyalg = QSsl::Dsa; } else if ( bundle.privateKey().isDH() ) { algtype = QStringLiteral( "dh" ); } + // TODO: add support for EC keys, once QCA supports them - return QStringList() << bundle.certificateChain().primary().toPEM() << bundle.privateKey().toPEM( passarray ) << algtype; + // can currently only support RSA and DSA between QCA and Qt + if ( keyalg == QSsl::Opaque ) + { + QgsDebugMsg( QString( "FAILED to read PKCS#12 key (only RSA and DSA algorithms supported): %1" ).arg( bundlepath ) ); + return empty; + } + + QString keyPem; +#ifdef Q_OS_MAC + if ( keyalg == QSsl::Rsa && QgsAuthCertUtils::pemIsPkcs8( bundle.privateKey().toPEM() ) ) + { + QgsDebugMsgLevel( QString( "Private key is PKCS#8: attempting conversion to PKCS#1..." ), 4 ); + // if RSA, convert from PKCS#8 key to 'traditional' OpenSSL RSA format, which Qt prefers + // note: QCA uses OpenSSL, regardless of the Qt SSL backend, and 1.0.2+ OpenSSL versions return + // RSA private keys as PKCS#8, which choke Qt upon QSslKey creation + + QByteArray pkcs8Der = bundle.privateKey().toDER().toByteArray(); + if ( pkcs8Der.isEmpty() ) + { + QgsDebugMsg( QString( "FAILED to convert PKCS#12 key to DER-encoded format: %1" ).arg( bundlepath ) ); + return empty; + } + + QByteArray pkcs1Der = QgsAuthCertUtils::pkcs8PrivateKey( pkcs8Der ); + if ( pkcs1Der.isEmpty() ) + { + QgsDebugMsg( QString( "FAILED to convert PKCS#12 key from PKCS#8 to PKCS#1: %1" ).arg( bundlepath ) ); + return empty; + } + + QSslKey pkcs1Key( pkcs1Der, QSsl::Rsa, QSsl::Der, QSsl::PrivateKey ); + if ( pkcs1Key.isNull() ) + { + QgsDebugMsg( QString( "FAILED to convert PKCS#12 key from PKCS#8 to PKCS#1 QSslKey: %1" ).arg( bundlepath ) ); + return empty; + } + keyPem = QString( pkcs1Key.toPem( passarray.toByteArray() ) ); + } + else + { + keyPem = bundle.privateKey().toPEM( passarray ); + } +#else + keyPem = bundle.privateKey().toPEM( passarray ); +#endif + + QgsDebugMsgLevel( QString( "PKCS#12 cert as PEM:\n%1" ).arg( QString( bundle.certificateChain().primary().toPEM() ) ), 4 ); + // !!! SENSITIVE DATA !!! + QgsDebugMsgLevel( QString( "PKCS#12 key as PEM:\n%1" ).arg( QString( keyPem ) ), 9 ); + + return QStringList() << bundle.certificateChain().primary().toPEM() << keyPem << algtype; } QList QgsAuthCertUtils::pkcs12BundleCas( const QString &bundlepath, const QString &bundlepass ) diff --git a/src/core/auth/qgsauthcertutils.h b/src/core/auth/qgsauthcertutils.h index 064366de3ca..14f1abcd41f 100644 --- a/src/core/auth/qgsauthcertutils.h +++ b/src/core/auth/qgsauthcertutils.h @@ -171,6 +171,25 @@ class CORE_EXPORT QgsAuthCertUtils */ static bool pemIsPkcs8( const QString &keyPemTxt ); +#ifdef Q_OS_MAC + + /** + * Extract the PrivateKey ASN.1 element of a DER-encoded PKCS#8 private key + * \param pkcs8Der PKCS#8 DER-encoded private key data + * \returns DER-encoded private key on success or an empty QByteArray upon failure + * \note On some platforms, e.g. macOS, where the default SSL backend is not OpenSSL, a QSslKey + * can not be created using PKCS#8-formatted data. However, PKCS#8 private key ASN.1 structures + * contain the key data inside a wrapper describing the algorithm used, e.g. RSA, DSA, ECC etc. + * Extracted PrivateKey ASN.1 data can be used to create a compatible QSslKey, + * e.g. 'traditional' SSLeay RSA-specific PKCS#1. + * By default OpenSSL 1.0.0+ returns private keys as PKCS#8, previously it was PKCS#1. + * \note This function requires 'libtasn1' development files and library, which is used + * to parse and extract the PrivateKey element from an ASN.1 PKCS#8 structure. + */ + static QByteArray pkcs8PrivateKey( QByteArray &pkcs8Der ) SIP_SKIP; +#endif + + /** * Return list of certificate, private key and algorithm (as PEM text) for a PKCS#12 bundle * \param bundlepath File path to the PKCS bundle * \param bundlepass Passphrase for bundle diff --git a/tests/src/core/testqgsauthcertutils.cpp b/tests/src/core/testqgsauthcertutils.cpp index 28953180e0a..68ef765fb46 100644 --- a/tests/src/core/testqgsauthcertutils.cpp +++ b/tests/src/core/testqgsauthcertutils.cpp @@ -72,6 +72,63 @@ void TestQgsAuthCertUtils::testPkcsUtils() pkcs = QgsAuthCertUtils::fileData( sPkiData + "/gerardus_key-pkcs8-rsa.pem", false ); QVERIFY( !pkcs.isEmpty() ); QVERIFY( QgsAuthCertUtils::pemIsPkcs8( QString( pkcs ) ) ); + + +#ifdef Q_OS_MAC + QByteArray pkcs1; + pkcs.clear(); + + // Nothing should return nothing + pkcs1 = QgsAuthCertUtils::pkcs8PrivateKey( pkcs ); + QVERIFY( pkcs1.isEmpty() ); + + pkcs.clear(); + pkcs1.clear(); + // Is actually a PKCS#1 key, not #8 + pkcs = QgsAuthCertUtils::fileData( sPkiData + "/gerardus_key.der", false ); + QVERIFY( !pkcs.isEmpty() ); + pkcs1 = QgsAuthCertUtils::pkcs8PrivateKey( pkcs ); + QVERIFY( pkcs1.isEmpty() ); + + pkcs.clear(); + pkcs1.clear(); + // Is PKCS#1 PEM text, not DER + pkcs = QgsAuthCertUtils::fileData( sPkiData + "/gerardus_key.pem", false ); + QVERIFY( !pkcs.isEmpty() ); + pkcs1 = QgsAuthCertUtils::pkcs8PrivateKey( pkcs ); + QVERIFY( pkcs1.isEmpty() ); + + pkcs.clear(); + pkcs1.clear(); + // Is PKCS#8 PEM text, not DER + pkcs = QgsAuthCertUtils::fileData( sPkiData + "/gerardus_key-pkcs8-rsa.pem", false ); + QVERIFY( !pkcs.isEmpty() ); + pkcs1 = QgsAuthCertUtils::pkcs8PrivateKey( pkcs ); + QVERIFY( pkcs1.isEmpty() ); + + pkcs.clear(); + pkcs1.clear(); + // Correct PKCS#8 DER input + pkcs = QgsAuthCertUtils::fileData( sPkiData + "/gerardus_key-pkcs8-rsa.der", false ); + QVERIFY( !pkcs.isEmpty() ); + pkcs1 = QgsAuthCertUtils::pkcs8PrivateKey( pkcs ); + QVERIFY( !pkcs1.isEmpty() ); + + // PKCS#8 DER format should fail, and the reason for QgsAuthCertUtils::pkcs8PrivateKey + // (as of Qt5.9.0, and where macOS Qt5 SSL backend is not OpenSSL, and + // where PKCS#8 is *still* unsupported for macOS) + QSslKey pkcs8Key( pkcs, QSsl::Rsa, QSsl::Der, QSsl::PrivateKey ); + QVERIFY( pkcs8Key.isNull() ); + + // PKCS#1 DER format should work + QSslKey pkcs1Key( pkcs1, QSsl::Rsa, QSsl::Der, QSsl::PrivateKey ); + QVERIFY( !pkcs1Key.isNull() ); + + // Converted PKCS#8 DER should match PKCS#1 PEM + QByteArray pkcs1PemRef = QgsAuthCertUtils::fileData( sPkiData + "/gerardus_key.pem", true ); + QVERIFY( !pkcs1PemRef.isEmpty() ); + QCOMPARE( pkcs1Key.toPem(), pkcs1PemRef ); +#endif } QGSTEST_MAIN( TestQgsAuthCertUtils ) diff --git a/tests/testdata/auth_system/certs_keys/fra_key-pkcs8-rsa.der b/tests/testdata/auth_system/certs_keys/fra_key-pkcs8-rsa.der new file mode 100644 index 0000000000000000000000000000000000000000..cd0638a71ec8b008f87843577f4242a8b1491b25 GIT binary patch literal 635 zcmV->0)+iAf&zB}0RS)!1_>&LNQUpVK9OMT>=3B0)c@5h@E zFQ#`mWAO+;t3?2IYqFpHh<0s{d60Rn-5J^s=M<5y=@l#rEPZYE?Jzf=@?aYYcdcjID; z&l4q6*fQ2ijwLwX=o2(t`i5V0kVTE+g?d}s(ZknDXXvm6wj+_sXU`|+?ArY>@|`??S4B&;7FU-#MP=*D&H>vz?bQ|tM`0zm-( z0D8L9h*H8O8XQ0P$ae@nf?*wS_8{fSo;6xPQs4~a^5cd#w5fv4qgM$b`kQLSg zWuwo$YI(qbsP#_*K>*}r_L`4{9)SjF{aGLF^Z}V1)rH3~dj)?UQ7R=JVnZimH*}v( zP23|yCS<=f<>`_)NA|J=_?2EQbhgR4DWdECXG??d&rvgA|P{`$tIil7P zgym-llUYbblZxKJJrqz)U>*A~TL-Pmgmm5vj=?6NKb_%mUT_&}wN4VHn>x#vtEDA||K401VdFw*j#&LNQUpVlaXNUIGCC0)c@5y4PFKTby*M z_o)D#qf8dQ*1L>UcR}zzBIc1QYl#|?Fpoq)UcLq<`Op#vU}|OocWfVy1)x79+P`WG zn7Wt#8k}b<0s{d60Rn-60J!a=Ky3^}Bwns{pOG6RfGWjewaS_$#tJfBxDNh{qCQAp$`F z?yVwbuj~V$7Uibf#cb~it@-4<$y1dIo?!*%ap9mb++4~Dx1OLd{Wz+3lGGTu5I=N( zN{f7EG>^2op6V%60zm-CXA+25asA>L!D*>4Pzm5Psr1AM-gR-u3d&mys^iS@tTz&6 z#R*_jDTW8&%{(rpGbjw)F+L+*kxWfV)|bfwKp{l3$=h|SU$QY;o57wKhsBOkirNV? zdLnOSvLt7XyTy8qlH)8=DdF+=80oikrkiCf=-o^z57_;>^f0M)4+22|f(EX2oV`hR z8;sJMok15zOQb?ow^MIyMKbXmrde{hl+B`L<90ToGZZWgjD=lT{(|(Ub8gq%W@k~< zi{=_p0zm-7&mt4*4zuKpK^I)mBnK_ug&D}Seu6g>ojw8rH>a?6=@GRr2I7<8KN9%b WI^%l6!4xx^3*bsRdV*9gYG(3I7#*qr literal 0 HcmV?d00001 diff --git a/tests/testdata/auth_system/certs_keys/gerardus_key-pkcs8-rsa.pem b/tests/testdata/auth_system/certs_keys/gerardus_key-pkcs8-rsa.pem new file mode 100644 index 00000000000..27313ab3b80 --- /dev/null +++ b/tests/testdata/auth_system/certs_keys/gerardus_key-pkcs8-rsa.pem @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBALrXW9BbnHSp96kA +naNMFr7Wu4xUd0HwPiLmkSpriRqSMI9EQF6+Bib50BIHYGpmAXdsH44FoD8k2r9q +C5sjiJpQY7pba3IB/n0pGufQJe4rw2lKWBKvl3ADjVoqPfCBT5mnpyEppDmXxOJr +ANH+ojsnjZh44fkq6emgEfganGcrAgMBAAECgYEAuO2jQG0MRCRernWfkRskgCrF +YrXPfAIvXhfboqLhBt2fFo41MBDgwf8MRGvssCLaXLs12DoVS6pMoJxzdFANSQyl +Oqf2wLtiTMzjPslv7x8R2ho/0uLxeccP5xHpLSxypbcXF3PzCxp4gNpnZWDvwx9V +Ofgrsjx//toTiMcSYiECQQDurSJmr+wDoBblptvFbO8KrfnkvMlTlQqeYQXmceGg +Mdxcygm3nqAw/Tiqd5LUGLgQP3R/Sot8ZjSPtLme6ilTAkEAyGcSiFhx/eIYwWmp +L1AJ4DSp9MQI3nVxxwrKWwyq48zxrDcSZcUJYFMphgfgzTwupTMoDNwxPiNdkUxN +SdaXyQJAIUSyydt1q1+yMVqbwZ4Yh8WOUoraCTN6Im9lsiRnjbvFeo2S4yxSKeHx +9xjpt3Smm2Us6N1MKg/Y/br0MKl1DwJBAIIGrnWcvUl3G4zSm51BF0dLpEJVt1Nv +bEUy8RymWXK4lM2iZeN2NqEzFCwMjIVdWP6C9KdzbtfcZmdR1IvmGlECQQDDzyIT +6g6z5IxBF1zQJAct34UZyLR+gjcTnT4CAjensHbpEbUvBuKT4D8S+No643rCwRQz +mgvgSjp6glQuamby +-----END PRIVATE KEY----- diff --git a/tests/testdata/auth_system/certs_keys/nicholas_key-pkcs8-rsa.der b/tests/testdata/auth_system/certs_keys/nicholas_key-pkcs8-rsa.der new file mode 100644 index 0000000000000000000000000000000000000000..55cc41591c4145b8e358b16b3c65d6525964f246 GIT binary patch literal 633 zcmV-<0*3uCf&z5{0RS)!1_>&LNQUpUoe6KTLJ+90)c@5%b9nGa!|M~t#Prh4ZaM_G-d)?-R>Mu7 z9|^p+cwN__TxV{3(7cFr-Zd^=i}5|L09Zw#%Je@CP~LdO0_^P~_`jAv{~iC31RcL)^wi8j`t} zgv|>7We!oH)P&S1#HjD{bnJX|5(04gJ)nC1uTH{`(y7@=8GHAC>2I)ncJqJF2tVID z^Yk=Mi~rQL$%30<-fPtR+gKAi>);AY{ch-IuOgpR3X_R{v-igY&T!O-J_Ggv0zm-p zZ^_9Q1lK0C1hY9vq(V=M;;fJotn#rfN2cQK?=SGLzCj0T4Due_UMZL&RF6%w>u|;M zpbaYrsE4i^wXlf-K>*pW2;Kc4z03b!i{imE6K85K-}WD;Lo@bOV$C6`10Z$W9jcb6 zVWYhTzB8rA{*wvv4&oKH;@OK;FKB@4J{bZ)FJ1W{=`!qvV@M!^;~c;^|7-#yN@2y z*7*WJHsFn_=n*`2MYoE)r!aEv+bw+ysXsyHQ4<)k@zE(Pm~=4{@U)7aiZdF&<`T?u T&e|UA9uRjdG#0VJimfDWu`e=+ literal 0 HcmV?d00001 diff --git a/tests/testdata/auth_system/certs_keys/nicholas_key-pkcs8-rsa.pem b/tests/testdata/auth_system/certs_keys/nicholas_key-pkcs8-rsa.pem new file mode 100644 index 00000000000..b31733e44ad --- /dev/null +++ b/tests/testdata/auth_system/certs_keys/nicholas_key-pkcs8-rsa.pem @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAMuZd8GDiWvpMU7W +LJBSeDL/bXzdbnmL35RmF+gDOQOS5zZFFz6Nn2PD6xPJxPTSzG46BLjeXdvZVsNN +nx8JvLZ4XdehXGdue9C8iHTeNS5di/E9rwBYRaHK9D8NUN54xQLs7SL4v5Y//x3/ +1p2pRWzhMsJm4o4xi2A0pi4qQmthAgMBAAECgYBIMrvM26A3rBHYKwrSguws6Xch ++EPcxkUakrmXhM0K/2UOUaHUhNQoxKjv83TsfHQSAnD6PaB6/a9Owo/SqdlJGXv3 +f+lvsHx283/PCD/fO/P0NE6L/9S0yYKbYd5r1PvbWBM66+AKS/1u6GevIp9UCpOJ +f7P3xwTOcNSIPgP2AQJBAO9vyckXBNcmtASzOUikQk+K4qyQEqzysS1HpuLt7y/w +r75BB2sM8h7cXimYIlSPTbPrcMXzoA0rB6iHrhq1sIkCQQDZrwjd/SC9y/9fi+LB +MxNnai/f9h+nQzP2VWLNIakDIHXcHaqWp2GjvQW+M6XH/pMJ8g7iFbXi2YtVL2iA +6z4ZAkAvXfkYXAJsIc75Iw+RDFXF8J7ZLoNTTYu5fnRIbnOkE0RhKfIyvlPjwQqr +xdn8yoC/uDMOJh0incGdGIJb7FepAkB18c2XIdB8paw/Y7a/wWHRFYrNCTkLUnE0 +Ff2LcaJ2jD7vva8xI43WvtL+xFMdsoSOzfVccDD1sbM5u48e0tb5AkA24I2q6BE8 +dkW3irynMHLu2y19C6k/QeZRExix8dEpLJh0MRPwtIqeijMav+YSzHLO2h7sHhB3 +LDQWscGKrSRu +-----END PRIVATE KEY----- diff --git a/tests/testdata/auth_system/certs_keys/ptolemy_key-pkcs8-rsa.der b/tests/testdata/auth_system/certs_keys/ptolemy_key-pkcs8-rsa.der new file mode 100644 index 0000000000000000000000000000000000000000..4f170ec062d9d69dfb37df30821952e5735cb66c GIT binary patch literal 634 zcmV-=0)_oBf&z8|0RS)!1_>&LNQUpU@(FLTmk_A0)c@5uF503sKpo- zLx2c$3alBbKQmJx&@U;V#4f*yHaQw)ii69RcDa2yVFXq|h#z0`qzj^n@JmR9!W%c` zS4p;8#G<(Fcd_O{>lEoFr0ugoK{r-xlgWV2Jc{xoWhm9W828LT$uFC|*M#na08xnx@ca@Dl#%7|AnKW^-8PjwFqepQyv0_qNhub^+%L zPlAl+FhcG_Z)x)mME;UPi<_2$%mqzS32_4ON!r&WwEl% Uhj!L(75>vy$&#m*(@VAheb{#;&;S4c literal 0 HcmV?d00001 diff --git a/tests/testdata/auth_system/certs_keys/ptolemy_key-pkcs8-rsa.pem b/tests/testdata/auth_system/certs_keys/ptolemy_key-pkcs8-rsa.pem new file mode 100644 index 00000000000..9ca8ca3c421 --- /dev/null +++ b/tests/testdata/auth_system/certs_keys/ptolemy_key-pkcs8-rsa.pem @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAK7KI7uoxRgWQ4AI +dAqsGao/M1Mg0C8poMQuv4k2ORplioPLlna5fTlhBFZBiB9f86QLoonwS0iEwhs3 +5VdJtlvEorjud7HmQesU6SWk7bNBQTdWbJPJgM48ivIjZSjVvBj3zEDJL5u948uJ +67zP1gQ5qqOrXDCV5KpXGOkRDDubAgMBAAECgYAgNQcYkSSgJ5oQgX5AaS3hfPvM +GYPC7Py+qY6JjgA/qO45Es6K2esFI6dU7YZToa6XT72HhUuZ9Tx/H3GW//Il8MJh +WEmiy6hB+yX3yEgq+CuUCgxnZd6BhX6H3O4dRBFxHaTEUjQJpZWrIS0vAzbdgJuM +vDbNDYuYgF/ZAMwGYQJBANbxDJzrYQxIelamwiEO9uPGvOoHRopLoIaFZPneSnpp +kIyLoCqikAjQuqeqVEOFXlYFfR8wVT9aq/RXqaw0A00CQQDQLZnGwhiaptBn8BL+ +6RjJM1Rmc1jmjiSNkp+ow573ttJhdgHnC0+CjOcwQu5Db2nzDkT+kkOLm5aCzOuZ +/XaHAkEAgAUOWCAxq1k31Ih6M6pwDnZ+an1u3EvzDmxBGjn17jcV6z/2Y65zT2zS +364phhXXfDDEt2DYRWXB6USVQIWyOQJAdEkEnQHOvJRx1Z1E/x81uS3y90d3YVIF +GQ/OH3cmVTjKS6afaW/n+gS7HzpD3Wdex2YxJAKPuGwwpt/QuzPaAQJAGP8nj5g9 +oYxzD+x018fxSf/BsTjXU2S6SrbIg5D4B5s5kYFXOvLUS4rfjGWxssyHdtZuFf7T +VMmSp5bTS7YAfQ== +-----END PRIVATE KEY----- From a1efff793a2e3f717e813b33f690faa15a8e9d6f Mon Sep 17 00:00:00 2001 From: Larry Shaffer Date: Wed, 18 Oct 2017 17:40:44 -0600 Subject: [PATCH 4/5] [auth] Fix documentation error in QgsAuthCertUtils --- python/core/auth/qgsauthcertutils.sip | 1 + src/core/auth/qgsauthcertutils.h | 1 + 2 files changed, 2 insertions(+) diff --git a/python/core/auth/qgsauthcertutils.sip b/python/core/auth/qgsauthcertutils.sip index 1eb2a9a72b1..560ce193b00 100644 --- a/python/core/auth/qgsauthcertutils.sip +++ b/python/core/auth/qgsauthcertutils.sip @@ -84,6 +84,7 @@ Map certificate sha1 to certificate as simple cache static QByteArray fileData( const QString &path, bool astext = false ); %Docstring Return data from a local file via a read-only operation + \param path Path to file to read \param astext Whether to open the file as text, otherwise as binary :return: All data contained in file or empty contents if file does not exist :rtype: QByteArray diff --git a/src/core/auth/qgsauthcertutils.h b/src/core/auth/qgsauthcertutils.h index 14f1abcd41f..719d579c994 100644 --- a/src/core/auth/qgsauthcertutils.h +++ b/src/core/auth/qgsauthcertutils.h @@ -106,6 +106,7 @@ class CORE_EXPORT QgsAuthCertUtils /** * Return data from a local file via a read-only operation + * \param path Path to file to read * \param astext Whether to open the file as text, otherwise as binary * \returns All data contained in file or empty contents if file does not exist */ From ef7a29dc53f634f699ffcc467366d80198c8dc44 Mon Sep 17 00:00:00 2001 From: Larry Shaffer Date: Thu, 26 Oct 2017 11:13:04 -0600 Subject: [PATCH 5/5] [auth] Keep sensitive debug data commented out --- src/core/auth/qgsauthcertutils.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/auth/qgsauthcertutils.cpp b/src/core/auth/qgsauthcertutils.cpp index e6bc1fd1137..0e378bb197f 100644 --- a/src/core/auth/qgsauthcertutils.cpp +++ b/src/core/auth/qgsauthcertutils.cpp @@ -413,8 +413,8 @@ QByteArray QgsAuthCertUtils::pkcs8PrivateKey( QByteArray &pkcs8Der ) pkcs1 = QByteArray( oct_data, oct_len ); - // !!! SENSITIVE DATA !!! - QgsDebugMsgLevel( QStringLiteral( "privateKey octet data as PEM: %1" ).arg( QString( pkcs1.toBase64() ) ), 9 ); + // !!! SENSITIVE DATA - DO NOT LEAVE UNCOMMENTED !!! + //QgsDebugMsgLevel( QStringLiteral( "privateKey octet data as PEM: %1" ).arg( QString( pkcs1.toBase64() ) ), 4 ); PKCS1DONE: @@ -512,8 +512,8 @@ QStringList QgsAuthCertUtils::pkcs12BundleToPem( const QString &bundlepath, #endif QgsDebugMsgLevel( QString( "PKCS#12 cert as PEM:\n%1" ).arg( QString( bundle.certificateChain().primary().toPEM() ) ), 4 ); - // !!! SENSITIVE DATA !!! - QgsDebugMsgLevel( QString( "PKCS#12 key as PEM:\n%1" ).arg( QString( keyPem ) ), 9 ); + // !!! SENSITIVE DATA - DO NOT LEAVE UNCOMMENTED !!! + //QgsDebugMsgLevel( QString( "PKCS#12 key as PEM:\n%1" ).arg( QString( keyPem ) ), 4 ); return QStringList() << bundle.certificateChain().primary().toPEM() << keyPem << algtype; }