ssl: common key parser support for encrypted keys

This adds the infrastructure for reading and writing encrypted private keys
when using non-OpenSSL backends. Each platform must provide its cryptographic
encrypt / decrypt functions.

As WinRT already uses the common parser, this commit includes an
implementation for that platform.

Done-with: Andrew Knight <andrew.knight@digia.com>
Task-number: QTBUG-40688
Change-Id: I0d153425ce63601ff03b784a111e13962061025f
Reviewed-by: Richard J. Moore <rich@kde.org>
bb10
Jeremy Lainé 2014-08-31 14:55:06 +03:00 committed by Richard J. Moore
parent b17365cda9
commit cd3dece750
8 changed files with 367 additions and 24 deletions

View File

@ -111,7 +111,8 @@ bool QSslKeyPrivate::fromEVP_PKEY(EVP_PKEY *pkey)
void QSslKeyPrivate::decodeDer(const QByteArray &der, bool deepClear)
{
decodePem(pemFromDer(der), QByteArray(), deepClear);
QMap<QByteArray, QByteArray> headers;
decodePem(pemFromDer(der, headers), QByteArray(), deepClear);
}
void QSslKeyPrivate::decodePem(const QByteArray &pem, const QByteArray &passPhrase,

View File

@ -63,6 +63,7 @@
#include <QtCore/qatomic.h>
#include <QtCore/qbytearray.h>
#include <QtCore/qbytearraymatcher.h>
#include <QtCore/qiodevice.h>
#ifndef QT_NO_DEBUG_STREAM
#include <QtCore/qdebug.h>
@ -130,7 +131,7 @@ QByteArray QSslKeyPrivate::pemFooter() const
Returns a DER key formatted as PEM.
*/
QByteArray QSslKeyPrivate::pemFromDer(const QByteArray &der) const
QByteArray QSslKeyPrivate::pemFromDer(const QByteArray &der, const QMap<QByteArray, QByteArray> &headers) const
{
QByteArray pem(der.toBase64());
@ -144,7 +145,16 @@ QByteArray QSslKeyPrivate::pemFromDer(const QByteArray &der) const
if (rem)
pem.append('\n'); // ###
pem.prepend(pemHeader() + '\n');
QByteArray extra;
if (!headers.isEmpty()) {
QMap<QByteArray, QByteArray>::const_iterator it = headers.constEnd();
do {
it--;
extra += it.key() + ": " + it.value() + '\n';
} while (it != headers.constBegin());
extra += '\n';
}
pem.prepend(pemHeader() + '\n' + extra);
pem.append(pemFooter() + '\n');
return pem;
@ -155,7 +165,7 @@ QByteArray QSslKeyPrivate::pemFromDer(const QByteArray &der) const
Returns a PEM key formatted as DER.
*/
QByteArray QSslKeyPrivate::derFromPem(const QByteArray &pem) const
QByteArray QSslKeyPrivate::derFromPem(const QByteArray &pem, QMap<QByteArray, QByteArray> *headers) const
{
const QByteArray header = pemHeader();
const QByteArray footer = pemFooter();
@ -169,6 +179,39 @@ QByteArray QSslKeyPrivate::derFromPem(const QByteArray &pem) const
der = der.mid(headerIndex + header.size(), footerIndex - (headerIndex + header.size()));
if (der.contains("Proc-Type:")) {
// taken from QHttpNetworkReplyPrivate::parseHeader
const QByteArrayMatcher lf("\n");
const QByteArrayMatcher colon(":");
int i = 0;
while (i < der.count()) {
int j = colon.indexIn(der, i); // field-name
if (j == -1)
break;
const QByteArray field = der.mid(i, j - i).trimmed();
j++;
// any number of LWS is allowed before and after the value
QByteArray value;
do {
i = lf.indexIn(der, j);
if (i == -1)
break;
if (!value.isEmpty())
value += ' ';
// check if we have CRLF or only LF
bool hasCR = (i && der[i-1] == '\r');
int length = i -(hasCR ? 1: 0) - j;
value += der.mid(j, length).trimmed();
j = ++i;
} while (i < der.count() && (der.at(i) == ' ' || der.at(i) == '\t'));
if (i == -1)
break; // something is wrong
headers->insert(field, value);
}
der = der.mid(i);
}
return QByteArray::fromBase64(der); // ignores newlines
}
@ -337,7 +380,8 @@ QByteArray QSslKey::toDer(const QByteArray &passPhrase) const
return QByteArray();
#ifndef QT_NO_OPENSSL
return d->derFromPem(toPem(passPhrase));
QMap<QByteArray, QByteArray> headers;
return d->derFromPem(toPem(passPhrase), &headers);
#else
return d->derData;
#endif

View File

@ -91,8 +91,8 @@ public:
bool deepClear = true);
QByteArray pemHeader() const;
QByteArray pemFooter() const;
QByteArray pemFromDer(const QByteArray &der) const;
QByteArray derFromPem(const QByteArray &pem) const;
QByteArray pemFromDer(const QByteArray &der, const QMap<QByteArray, QByteArray> &headers) const;
QByteArray derFromPem(const QByteArray &pem, QMap<QByteArray, QByteArray> *headers) const;
int length() const;
QByteArray toPem(const QByteArray &passPhrase) const;
@ -106,6 +106,15 @@ public:
RSA *rsa;
DSA *dsa;
#else
enum Cipher {
DesCbc,
DesEde3Cbc,
Rc2Cbc
};
Q_AUTOTEST_EXPORT static QByteArray decrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, const QByteArray &iv);
Q_AUTOTEST_EXPORT static QByteArray encrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, const QByteArray &iv);
Qt::HANDLE opaque;
QByteArray derData;
int keyLength;

View File

@ -43,6 +43,8 @@
#include "qsslkey_p.h"
#include "qasn1element_p.h"
#include <QtCore/qcryptographichash.h>
QT_USE_NAMESPACE
static const quint8 bits_table[256] = {
@ -78,6 +80,31 @@ static int numberOfBits(const QByteArray &modulus)
return bits;
}
static QByteArray deriveKey(QSslKeyPrivate::Cipher cipher, const QByteArray &passPhrase, const QByteArray &iv)
{
QByteArray key;
QCryptographicHash hash(QCryptographicHash::Md5);
hash.addData(passPhrase);
hash.addData(iv);
switch (cipher) {
case QSslKeyPrivate::DesCbc:
key = hash.result().left(8);
break;
case QSslKeyPrivate::DesEde3Cbc:
key = hash.result();
hash.reset();
hash.addData(key);
hash.addData(passPhrase);
hash.addData(iv);
key += hash.result().left(8);
break;
case QSslKeyPrivate::Rc2Cbc:
key = hash.result();
break;
}
return key;
}
void QSslKeyPrivate::clear(bool deep)
{
Q_UNUSED(deep);
@ -155,12 +182,32 @@ void QSslKeyPrivate::decodeDer(const QByteArray &der, bool deepClear)
void QSslKeyPrivate::decodePem(const QByteArray &pem, const QByteArray &passPhrase,
bool deepClear)
{
if (type == QSsl::PrivateKey && !passPhrase.isEmpty()) {
Q_UNIMPLEMENTED();
return;
}
QMap<QByteArray, QByteArray> headers;
QByteArray data = derFromPem(pem, &headers);
if (headers.value("Proc-Type") == "4,ENCRYPTED") {
QList<QByteArray> dekInfo = headers.value("DEK-Info").split(',');
if (dekInfo.size() != 2) {
clear(deepClear);
return;
}
decodeDer(derFromPem(pem), deepClear);
Cipher cipher;
if (dekInfo.first() == "DES-CBC") {
cipher = DesCbc;
} else if (dekInfo.first() == "DES-EDE3-CBC") {
cipher = DesEde3Cbc;
} else if (dekInfo.first() == "RC2-CBC") {
cipher = Rc2Cbc;
} else {
clear(deepClear);
return;
}
const QByteArray iv = QByteArray::fromHex(dekInfo.last());
const QByteArray key = deriveKey(cipher, passPhrase, iv);
data = decrypt(cipher, data, key, iv);
}
decodeDer(data, deepClear);
}
int QSslKeyPrivate::length() const
@ -170,12 +217,27 @@ int QSslKeyPrivate::length() const
QByteArray QSslKeyPrivate::toPem(const QByteArray &passPhrase) const
{
QByteArray data;
QMap<QByteArray, QByteArray> headers;
if (type == QSsl::PrivateKey && !passPhrase.isEmpty()) {
Q_UNIMPLEMENTED();
return QByteArray();
// ### use a cryptographically secure random number generator
QByteArray iv;
iv.resize(8);
for (int i = 0; i < iv.size(); ++i)
iv[i] = (qrand() & 0xff);
Cipher cipher = DesEde3Cbc;
const QByteArray key = deriveKey(cipher, passPhrase, iv);
data = encrypt(cipher, derData, key, iv);
headers.insert("Proc-Type", "4,ENCRYPTED");
headers.insert("DEK-Info", "DES-EDE3-CBC," + iv.toHex());
} else {
data = derData;
}
return pemFromDer(derData);
return pemFromDer(data, headers);
}
Qt::HANDLE QSslKeyPrivate::handle() const

View File

@ -61,3 +61,101 @@ using namespace ABI::Windows::Security::Cryptography::Core;
using namespace ABI::Windows::Storage::Streams;
QT_USE_NAMESPACE
struct SslKeyGlobal
{
SslKeyGlobal()
{
HRESULT hr;
hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Security_Cryptography_Core_CryptographicEngine).Get(),
&engine);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<ISymmetricKeyAlgorithmProviderStatics> keyProviderFactory;
hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Security_Cryptography_Core_SymmetricKeyAlgorithmProvider).Get(),
&keyProviderFactory);
Q_ASSERT_SUCCEEDED(hr);
hr = keyProviderFactory->OpenAlgorithm(HString::MakeReference(L"DES_CBC").Get(),
&keyProviders[QSslKeyPrivate::DesCbc]);
Q_ASSERT_SUCCEEDED(hr);
hr = keyProviderFactory->OpenAlgorithm(HString::MakeReference(L"3DES_CBC").Get(),
&keyProviders[QSslKeyPrivate::DesEde3Cbc]);
Q_ASSERT_SUCCEEDED(hr);
hr = keyProviderFactory->OpenAlgorithm(HString::MakeReference(L"RC2_CBC").Get(),
&keyProviders[QSslKeyPrivate::Rc2Cbc]);
Q_ASSERT_SUCCEEDED(hr);
hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Security_Cryptography_CryptographicBuffer).Get(),
&bufferFactory);
Q_ASSERT_SUCCEEDED(hr);
}
ComPtr<ICryptographicEngineStatics> engine;
QHash<QSslKeyPrivate::Cipher, ComPtr<ISymmetricKeyAlgorithmProvider>> keyProviders;
ComPtr<ICryptographicBufferStatics> bufferFactory;
};
Q_GLOBAL_STATIC(SslKeyGlobal, g)
static QByteArray doCrypt(QSslKeyPrivate::Cipher cipher, QByteArray data, const QByteArray &key, const QByteArray &iv, bool encrypt)
{
HRESULT hr;
ISymmetricKeyAlgorithmProvider *keyProvider = g->keyProviders[cipher].Get();
Q_ASSERT(keyProvider);
ComPtr<IBuffer> keyBuffer;
hr = g->bufferFactory->CreateFromByteArray(key.length(), (BYTE *)key.data(), &keyBuffer);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<ICryptographicKey> cryptographicKey;
hr = keyProvider->CreateSymmetricKey(keyBuffer.Get(), &cryptographicKey);
Q_ASSERT_SUCCEEDED(hr);
UINT32 blockLength;
hr = keyProvider->get_BlockLength(&blockLength);
Q_ASSERT_SUCCEEDED(hr);
if (encrypt) { // Add padding
const char padding = blockLength - data.length() % blockLength;
data += QByteArray(padding, padding);
}
ComPtr<IBuffer> dataBuffer;
hr = g->bufferFactory->CreateFromByteArray(data.length(), (BYTE *)data.data(), &dataBuffer);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IBuffer> ivBuffer;
hr = g->bufferFactory->CreateFromByteArray(iv.length(), (BYTE *)iv.data(), &ivBuffer);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<IBuffer> resultBuffer;
hr = encrypt ? g->engine->Encrypt(cryptographicKey.Get(), dataBuffer.Get(), ivBuffer.Get(), &resultBuffer)
: g->engine->Decrypt(cryptographicKey.Get(), dataBuffer.Get(), ivBuffer.Get(), &resultBuffer);
Q_ASSERT_SUCCEEDED(hr);
UINT32 resultLength;
hr = resultBuffer->get_Length(&resultLength);
Q_ASSERT_SUCCEEDED(hr);
ComPtr<Windows::Storage::Streams::IBufferByteAccess> bufferAccess;
hr = resultBuffer.As(&bufferAccess);
Q_ASSERT_SUCCEEDED(hr);
byte *resultData;
hr = bufferAccess->Buffer(&resultData);
Q_ASSERT_SUCCEEDED(hr);
if (!encrypt) { // Remove padding
const uchar padding = resultData[resultLength - 1];
if (padding > 0 && padding <= blockLength)
resultLength -= padding;
else
qWarning("Invalid padding length of %u; decryption likely failed.", padding);
}
return QByteArray(reinterpret_cast<const char *>(resultData), resultLength);
}
QByteArray QSslKeyPrivate::decrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, const QByteArray &iv)
{
return doCrypt(cipher, data, key, iv, false);
}
QByteArray QSslKeyPrivate::encrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, const QByteArray &iv)
{
return doCrypt(cipher, data, key, iv, true);
}

View File

@ -4,6 +4,9 @@ CONFIG += parallel_test
SOURCES += tst_qsslkey.cpp
!wince*:win32:LIBS += -lws2_32
QT = core network testlib
contains(QT_CONFIG, private_tests) {
QT += core-private network-private
}
TARGET = tst_qsslkey

View File

@ -0,0 +1,18 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: RC2-CBC,EAAF396E2CDD3680
G+kY27Vq0Bkw6dEGlpe4oyyXlilyKJgkX53NQHv0WtLsu+cfamGst9viSQbAluP0
Pk2m78Z05IZHBkxcl20tZZR3G3hFTVqf7OemEucT5Kb/Vx6V++ZXY5eI54bh1oEJ
f8klr2DnscyTYdT33cmMuaxUm1sWjHeBBdQuQlMnW11XoCiGQMgMyf2fwbzd/og+
vDPtORIMw+zedxaTsiOyNASLWB5ILDpUR9PTzz1tRIIOF5DnKttEe3SYPIqkLhxu
N7OtpVhor0QUulph8sS2uiilTVyaYVciOJK7Cqq2K015l9nlqGg/KI0GRIC9ty+k
wd+/Hdazp+YcLn3tL8jhekST/DAxK9VIb0DBvaboKr8UJw35nLOwA7smsij50l2S
kfpu9z/80gFbnSSQo7L8zD/kBzaPlup0H+h96rF3IjdCrnbveHlvJDo3GcPNxauw
rGUQXnnMzDVSTY179HfcgLZdsm3uOIBicEFaxXu2/L4Eof9yp8D/b6SeZeS41O7a
ICvLXxkiIfQHukHvqMLJ2SqKQ0J8zEXN0OBSLjaHUkBIOs3L4IFe2v8DMN798GrG
QzcxC0bmr9s6TvihlYFBbYkMZ3IYPT4SFZg92/pKTPlyD/Blc9oZm8QpQMGIMDKc
nWDLeqeCTXV6TL1mymqzwyzs9+4cXvEiM167FsLqk5tGRIyl4AR/dItELEtCWl3I
koIOUEk5rbJekOhTc85SFSQmCV0IebsUv0CpdWlmNeexNryLZu0r6kTUFWzpHcv/
0yEaBQFLVx9QAfRSIiNt+yAgGnpMxxMxeHs6shVmuscZ0fV50GpOpA==
-----END RSA PRIVATE KEY-----

View File

@ -47,6 +47,11 @@
#include <QtNetwork/qhostaddress.h>
#include <QtNetwork/qnetworkproxy.h>
#if !defined(QT_NO_SSL) && defined(QT_NO_OPENSSL) && defined(QT_BUILD_INTERNAL)
#include "private/qsslkey_p.h"
#define TEST_CRYPTO
#endif
class tst_QSslKey : public QObject
{
Q_OBJECT
@ -90,6 +95,11 @@ private slots:
void passphraseChecks_data();
void passphraseChecks();
void noPassphraseChecks();
#ifdef TEST_CRYPTO
void encrypt_data();
void encrypt();
#endif
#endif
private:
QString testDataDir;
@ -306,9 +316,6 @@ void tst_QSslKey::toEncryptedPemOrDer()
QByteArray pwBytes(password.toLatin1());
if (type == QSsl::PrivateKey) {
#ifdef QT_NO_OPENSSL
QSKIP("Encrypted keys require support from the SSL backend");
#endif
QByteArray encryptedPem = key.toPem(pwBytes);
QVERIFY(!encryptedPem.isEmpty());
QSslKey keyPem(encryptedPem, algorithm, QSsl::Pem, type, pwBytes);
@ -347,6 +354,7 @@ void tst_QSslKey::passphraseChecks_data()
QTest::newRow("DES") << QString(testDataDir + "/rsa-with-passphrase-des.pem");
QTest::newRow("3DES") << QString(testDataDir + "/rsa-with-passphrase-3des.pem");
QTest::newRow("RC2") << QString(testDataDir + "/rsa-with-passphrase-rc2.pem");
}
void tst_QSslKey::passphraseChecks()
@ -379,9 +387,6 @@ void tst_QSslKey::passphraseChecks()
QSslKey key(&keyFile,QSsl::Rsa,QSsl::Pem, QSsl::PrivateKey, "WRONG!");
QVERIFY(key.isNull()); // wrong passphrase => should not be able to decode key
}
#ifdef QT_NO_OPENSSL
QEXPECT_FAIL("", "Encrypted keys require support from the SSL backend", Abort);
#endif
{
if (!keyFile.isOpen())
keyFile.open(QIODevice::ReadOnly);
@ -413,9 +418,6 @@ void tst_QSslKey::noPassphraseChecks()
QSslKey key(&keyFile,QSsl::Rsa,QSsl::Pem, QSsl::PrivateKey, "");
QVERIFY(!key.isNull()); // empty passphrase => should be able to decode key
}
#ifdef QT_NO_OPENSSL
QEXPECT_FAIL("", "Encrypted keys require support from the SSL backend", Abort);
#endif
{
if (!keyFile.isOpen())
keyFile.open(QIODevice::ReadOnly);
@ -426,6 +428,112 @@ void tst_QSslKey::noPassphraseChecks()
}
}
#ifdef TEST_CRYPTO
Q_DECLARE_METATYPE(QSslKeyPrivate::Cipher)
void tst_QSslKey::encrypt_data()
{
QTest::addColumn<QSslKeyPrivate::Cipher>("cipher");
QTest::addColumn<QByteArray>("key");
QTest::addColumn<QByteArray>("plainText");
QTest::addColumn<QByteArray>("cipherText");
QTest::newRow("DES-CBC, length 0")
<< QSslKeyPrivate::DesCbc << QByteArray("01234567")
<< QByteArray()
<< QByteArray::fromHex("956585228BAF9B1F");
QTest::newRow("DES-CBC, length 1")
<< QSslKeyPrivate::DesCbc << QByteArray("01234567")
<< QByteArray(1, 'a')
<< QByteArray::fromHex("E6880AF202BA3C12");
QTest::newRow("DES-CBC, length 2")
<< QSslKeyPrivate::DesCbc << QByteArray("01234567")
<< QByteArray(2, 'a')
<< QByteArray::fromHex("A82492386EED6026");
QTest::newRow("DES-CBC, length 3")
<< QSslKeyPrivate::DesCbc << QByteArray("01234567")
<< QByteArray(3, 'a')
<< QByteArray::fromHex("90B76D5B79519CBA");
QTest::newRow("DES-CBC, length 4")
<< QSslKeyPrivate::DesCbc << QByteArray("01234567")
<< QByteArray(4, 'a')
<< QByteArray::fromHex("63E3DD6FED87052A");
QTest::newRow("DES-CBC, length 5")
<< QSslKeyPrivate::DesCbc << QByteArray("01234567")
<< QByteArray(5, 'a')
<< QByteArray::fromHex("03ACDB0EACBDFA94");
QTest::newRow("DES-CBC, length 6")
<< QSslKeyPrivate::DesCbc << QByteArray("01234567")
<< QByteArray(6, 'a')
<< QByteArray::fromHex("7D95024E42A3A88A");
QTest::newRow("DES-CBC, length 7")
<< QSslKeyPrivate::DesCbc << QByteArray("01234567")
<< QByteArray(7, 'a')
<< QByteArray::fromHex("5003436B8A8E42E9");
QTest::newRow("DES-CBC, length 8")
<< QSslKeyPrivate::DesCbc << QByteArray("01234567")
<< QByteArray(8, 'a')
<< QByteArray::fromHex("E4C1F054BF5521C0A4A0FD4A2BC6C1B1");
QTest::newRow("DES-EDE3-CBC, length 0")
<< QSslKeyPrivate::DesEde3Cbc << QByteArray("0123456789abcdefghijklmn")
<< QByteArray()
<< QByteArray::fromHex("3B2B4CD0B0FD495F");
QTest::newRow("DES-EDE3-CBC, length 8")
<< QSslKeyPrivate::DesEde3Cbc << QByteArray("0123456789abcdefghijklmn")
<< QByteArray(8, 'a')
<< QByteArray::fromHex("F2A5A87763C54A72A3224103D90CDB03");
QTest::newRow("RC2-40-CBC, length 0")
<< QSslKeyPrivate::Rc2Cbc << QByteArray("01234")
<< QByteArray()
<< QByteArray::fromHex("6D05D52392FF6E7A");
QTest::newRow("RC2-40-CBC, length 8")
<< QSslKeyPrivate::Rc2Cbc << QByteArray("01234")
<< QByteArray(8, 'a')
<< QByteArray::fromHex("75768E64C5749072A5D168F3AFEB0005");
QTest::newRow("RC2-64-CBC, length 0")
<< QSslKeyPrivate::Rc2Cbc << QByteArray("01234567")
<< QByteArray()
<< QByteArray::fromHex("ADAE6BF70F420130");
QTest::newRow("RC2-64-CBC, length 8")
<< QSslKeyPrivate::Rc2Cbc << QByteArray("01234567")
<< QByteArray(8, 'a')
<< QByteArray::fromHex("C7BF5C80AFBE9FBEFBBB9FD935F6D0DF");
QTest::newRow("RC2-128-CBC, length 0")
<< QSslKeyPrivate::Rc2Cbc << QByteArray("012345679abcdefg")
<< QByteArray()
<< QByteArray::fromHex("1E965D483A13C8FB");
QTest::newRow("RC2-128-CBC, length 8")
<< QSslKeyPrivate::Rc2Cbc << QByteArray("012345679abcdefg")
<< QByteArray(8, 'a')
<< QByteArray::fromHex("5AEC1A5B295660B02613454232F7DECE");
}
void tst_QSslKey::encrypt()
{
QFETCH(QSslKeyPrivate::Cipher, cipher);
QFETCH(QByteArray, key);
QFETCH(QByteArray, plainText);
QFETCH(QByteArray, cipherText);
QByteArray iv("abcdefgh");
#ifdef Q_OS_WINRT
QEXPECT_FAIL("RC2-40-CBC, length 0", "WinRT treats RC2 as 128-bit", Abort);
QEXPECT_FAIL("RC2-40-CBC, length 8", "WinRT treats RC2 as 128-bit", Abort);
QEXPECT_FAIL("RC2-64-CBC, length 0", "WinRT treats RC2 as 128-bit", Abort);
QEXPECT_FAIL("RC2-64-CBC, length 8", "WinRT treats RC2 as 128-bit", Abort);
#endif
QByteArray encrypted = QSslKeyPrivate::encrypt(cipher, plainText, key, iv);
QCOMPARE(encrypted, cipherText);
QByteArray decrypted = QSslKeyPrivate::decrypt(cipher, cipherText, key, iv);
QCOMPARE(decrypted, plainText);
}
#endif
#endif
QTEST_MAIN(tst_QSslKey)