[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.
This commit is contained in:
Larry Shaffer 2017-10-18 16:03:40 -06:00
parent 8107f91037
commit c66de14055
16 changed files with 480 additions and 2 deletions

View File

@ -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

45
cmake/FindLibtasn1.cmake Normal file
View File

@ -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 <lshaffer (at) boundlessgeo (dot) com>
#
# 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)

View File

@ -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)

63
resources/pkcs8.asn Normal file
View File

@ -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

View File

@ -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)

View File

@ -23,9 +23,16 @@
#include <QSslCertificate>
#include <QUuid>
#include "qgsapplication.h"
#include "qgsauthmanager.h"
#include "qgslogger.h"
#ifdef Q_OS_MAC
#include <string.h>
#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<unsigned char *>( 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<int>( 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<QSslCertificate> QgsAuthCertUtils::pkcs12BundleCas( const QString &bundlepath, const QString &bundlepass )

View File

@ -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

View File

@ -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 )

Binary file not shown.

View File

@ -0,0 +1,16 @@
-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAOOA/yLAj0TOL6Z3
OGY+2JxaSmStwl5veQjp+VoAOoxDVjQDOxuBNihZ1gGUVPc2cQm8HS+nMizw9SiC
l0ZiP23QqkL9Xgd1+scE1Hhxf8cvTp2Ek3QKbKlfol3wGZgGElwkrVed77+l7PjX
VxLd2UbnZEF8TURntOyMKeYwi53vAgMBAAECgYA9/tIH41dnVZSQlV5uJmQav1QU
eXFFELV342KKzxMlU9gy1kqOJTjf6BM0XPqGX3SQRY3ihXpb2tHD10pn6LAFtiOR
ymfPJ+fs3TiPUn+Ut7TkedKkTxu5IT5C5Nu0FllcTo9mpi5ytfu6D+gkrB8fX/fZ
5+jGdevrd9WWU+v5wQJBAP8AerrTiFLCJRocP/jIdwg+gmEdcPYg5cmeNVpAUuAN
CSa5QYIQ9xB3ERUVo4ODCEGQFdYDZaPPvGp5wICo9U8CQQDkZPaaj4UegQZp/Vkf
7fQBmRzVhccxewV/HlEqJR1iQydjN3SfTU3cI0QmZL805emSN0f2sgT4lV4tdLbJ
ueVhAkEAk2C+jf21u0bz1IxhOLL7gKtIBULTx5yp0gX7BedJPq6qDFRjlP2jHUQD
fnEcKOTxP5s7043xD2T/m3Y0mOeNpwJAaFDI5Y05otYRhOVnCJNZSEWTit7APRRQ
TWAeeB5djlzXp5RTmtLnBe3BmbuYLWP5S4QeRUnHxXYLfr15IyfZ4QJBAMxGWwet
yoR03gyOwSagP53hcV5wGWu1ThKlmzrLl6ulJYb/3lwbYeNCaI5ZzGaSiycWC/8K
9zIREiwz1u/iupk=
-----END PRIVATE KEY-----

Binary file not shown.

View File

@ -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-----

Binary file not shown.

View File

@ -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-----

Binary file not shown.

View File

@ -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-----