1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the test suite of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 
43 #include <QtTest/QtTest>
44 #include <qsslkey.h>
45 #include <qsslsocket.h>
46 
47 #include <QtNetwork/qhostaddress.h>
48 #include <QtNetwork/qnetworkproxy.h>
49 
50 #ifdef Q_OS_SYMBIAN
51 // In Symbian OS test data is located in applications private dir
52 // Current path (C:\private\<UID>) contains only ascii chars
53 #define SRCDIR "."
54 #endif
55 
56 class tst_QSslKey : public QObject
57 {
58     Q_OBJECT
59 
60     struct KeyInfo {
61         QFileInfo fileInfo;
62         QSsl::KeyAlgorithm algorithm;
63         QSsl::KeyType type;
64         int length;
65         QSsl::EncodingFormat format;
KeyInfotst_QSslKey::KeyInfo66         KeyInfo(
67             const QFileInfo &fileInfo, QSsl::KeyAlgorithm algorithm, QSsl::KeyType type,
68             int length, QSsl::EncodingFormat format)
69             : fileInfo(fileInfo), algorithm(algorithm), type(type), length(length)
70             , format(format) {}
71     };
72 
73     QList<KeyInfo> keyInfoList;
74 
75     void createPlainTestRows();
76 
77 public:
78     tst_QSslKey();
79     virtual ~tst_QSslKey();
80 
81 public slots:
82     void initTestCase_data();
83     void init();
84     void cleanup();
85 
86 #ifndef QT_NO_OPENSSL
87 
88 private slots:
89     void emptyConstructor();
90     void constructor_data();
91     void constructor();
92     void copyAndAssign_data();
93     void copyAndAssign();
94     void equalsOperator();
95     void length_data();
96     void length();
97     void toPemOrDer_data();
98     void toPemOrDer();
99     void toEncryptedPemOrDer_data();
100     void toEncryptedPemOrDer();
101 
102     void passphraseChecks();
103 #endif
104 };
105 
tst_QSslKey()106 tst_QSslKey::tst_QSslKey()
107 {
108 #ifdef Q_WS_MAC
109     // applicationDirPath() points to a path inside the app bundle on Mac.
110     QDir dir(qApp->applicationDirPath() + QLatin1String("/../../../keys"));
111 #else
112     QDir dir(SRCDIR + QLatin1String("/keys"));  // prefer this way to avoid ifdeffery and support shadow builds?
113 #endif
114     QFileInfoList fileInfoList = dir.entryInfoList(QDir::Files | QDir::Readable);
115     QRegExp rx(QLatin1String("^(rsa|dsa)-(pub|pri)-(\\d+)\\.(pem|der)$"));
116     foreach (QFileInfo fileInfo, fileInfoList) {
117         if (rx.indexIn(fileInfo.fileName()) >= 0)
118             keyInfoList << KeyInfo(
119                 fileInfo,
120                 rx.cap(1) == QLatin1String("rsa") ? QSsl::Rsa : QSsl::Dsa,
121                 rx.cap(2) == QLatin1String("pub") ? QSsl::PublicKey : QSsl::PrivateKey,
122                 rx.cap(3).toInt(),
123                 rx.cap(4) == QLatin1String("pem") ? QSsl::Pem : QSsl::Der);
124     }
125 }
126 
~tst_QSslKey()127 tst_QSslKey::~tst_QSslKey()
128 {
129 }
130 
initTestCase_data()131 void tst_QSslKey::initTestCase_data()
132 {
133 }
134 
init()135 void tst_QSslKey::init()
136 {
137 }
138 
cleanup()139 void tst_QSslKey::cleanup()
140 {
141 }
142 
readFile(const QString & absFilePath)143 static QByteArray readFile(const QString &absFilePath)
144 {
145     QFile file(absFilePath);
146     if (!file.open(QIODevice::ReadOnly)) {
147         QWARN("failed to open file");
148         return QByteArray();
149     }
150     return file.readAll();
151 }
152 
153 #ifndef QT_NO_OPENSSL
154 
emptyConstructor()155 void tst_QSslKey::emptyConstructor()
156 {
157     if (!QSslSocket::supportsSsl())
158         return;
159 
160     QSslKey key;
161     QVERIFY(key.isNull());
162     QVERIFY(key.length() < 0);
163 
164     QSslKey key2;
165     QCOMPARE(key, key2);
166 }
167 
168 Q_DECLARE_METATYPE(QSsl::KeyAlgorithm)
Q_DECLARE_METATYPE(QSsl::KeyType)169 Q_DECLARE_METATYPE(QSsl::KeyType)
170 Q_DECLARE_METATYPE(QSsl::EncodingFormat)
171 
172 void tst_QSslKey::createPlainTestRows()
173 {
174     QTest::addColumn<QString>("absFilePath");
175     QTest::addColumn<QSsl::KeyAlgorithm>("algorithm");
176     QTest::addColumn<QSsl::KeyType>("type");
177     QTest::addColumn<int>("length");
178     QTest::addColumn<QSsl::EncodingFormat>("format");
179     foreach (KeyInfo keyInfo, keyInfoList) {
180         QTest::newRow(keyInfo.fileInfo.fileName().toLatin1())
181             << keyInfo.fileInfo.absoluteFilePath() << keyInfo.algorithm << keyInfo.type
182             << keyInfo.length << keyInfo.format;
183     }
184 }
185 
constructor_data()186 void tst_QSslKey::constructor_data()
187 {
188     createPlainTestRows();
189 }
190 
constructor()191 void tst_QSslKey::constructor()
192 {
193     if (!QSslSocket::supportsSsl())
194         return;
195 
196     QFETCH(QString, absFilePath);
197     QFETCH(QSsl::KeyAlgorithm, algorithm);
198     QFETCH(QSsl::KeyType, type);
199     QFETCH(QSsl::EncodingFormat, format);
200 
201     QByteArray encoded = readFile(absFilePath);
202     QSslKey key(encoded, algorithm, format, type);
203     QVERIFY(!key.isNull());
204 }
205 
copyAndAssign_data()206 void tst_QSslKey::copyAndAssign_data()
207 {
208     createPlainTestRows();
209 }
210 
copyAndAssign()211 void tst_QSslKey::copyAndAssign()
212 {
213     if (!QSslSocket::supportsSsl())
214         return;
215 
216     QFETCH(QString, absFilePath);
217     QFETCH(QSsl::KeyAlgorithm, algorithm);
218     QFETCH(QSsl::KeyType, type);
219     QFETCH(QSsl::EncodingFormat, format);
220 
221     QByteArray encoded = readFile(absFilePath);
222     QSslKey key(encoded, algorithm, format, type);
223 
224     QSslKey copied(key);
225     QCOMPARE(key, copied);
226     QCOMPARE(key.algorithm(), copied.algorithm());
227     QCOMPARE(key.type(), copied.type());
228     QCOMPARE(key.length(), copied.length());
229     QCOMPARE(key.toPem(), copied.toPem());
230     QCOMPARE(key.toDer(), copied.toDer());
231 
232     QSslKey assigned = key;
233     QCOMPARE(key, assigned);
234     QCOMPARE(key.algorithm(), assigned.algorithm());
235     QCOMPARE(key.type(), assigned.type());
236     QCOMPARE(key.length(), assigned.length());
237     QCOMPARE(key.toPem(), assigned.toPem());
238     QCOMPARE(key.toDer(), assigned.toDer());
239 }
240 
equalsOperator()241 void tst_QSslKey::equalsOperator()
242 {
243     // ### unimplemented
244 }
245 
length_data()246 void tst_QSslKey::length_data()
247 {
248     createPlainTestRows();
249 }
250 
length()251 void tst_QSslKey::length()
252 {
253     if (!QSslSocket::supportsSsl())
254         return;
255 
256     QFETCH(QString, absFilePath);
257     QFETCH(QSsl::KeyAlgorithm, algorithm);
258     QFETCH(QSsl::KeyType, type);
259     QFETCH(int, length);
260     QFETCH(QSsl::EncodingFormat, format);
261 
262     QByteArray encoded = readFile(absFilePath);
263     QSslKey key(encoded, algorithm, format, type);
264     QVERIFY(!key.isNull());
265     QCOMPARE(key.length(), length);
266 }
267 
toPemOrDer_data()268 void tst_QSslKey::toPemOrDer_data()
269 {
270     createPlainTestRows();
271 }
272 
toPemOrDer()273 void tst_QSslKey::toPemOrDer()
274 {
275     if (!QSslSocket::supportsSsl())
276         return;
277 
278     QFETCH(QString, absFilePath);
279     QFETCH(QSsl::KeyAlgorithm, algorithm);
280     QFETCH(QSsl::KeyType, type);
281     QFETCH(QSsl::EncodingFormat, format);
282 
283     QByteArray encoded = readFile(absFilePath);
284     QSslKey key(encoded, algorithm, format, type);
285     QVERIFY(!key.isNull());
286     if (format == QSsl::Pem)
287         encoded.replace('\r', "");
288     QCOMPARE(format == QSsl::Pem ? key.toPem() : key.toDer(), encoded);
289 }
290 
toEncryptedPemOrDer_data()291 void tst_QSslKey::toEncryptedPemOrDer_data()
292 {
293     QTest::addColumn<QString>("absFilePath");
294     QTest::addColumn<QSsl::KeyAlgorithm>("algorithm");
295     QTest::addColumn<QSsl::KeyType>("type");
296     QTest::addColumn<QSsl::EncodingFormat>("format");
297     QTest::addColumn<QString>("password");
298 
299     QStringList passwords;
300     passwords << " " << "foobar" << "foo bar"
301               << "aAzZ`1234567890-=~!@#$%^&*()_+[]{}\\|;:'\",.<>/?"; // ### add more (?)
302     foreach (KeyInfo keyInfo, keyInfoList) {
303         foreach (QString password, passwords) {
304             QString testName = QString("%1-%2-%3-%4").arg(keyInfo.fileInfo.fileName())
305                 .arg(keyInfo.algorithm == QSsl::Rsa ? "RSA" : "DSA")
306                 .arg(keyInfo.type == QSsl::PrivateKey ? "PrivateKey" : "PublicKey")
307                 .arg(keyInfo.format == QSsl::Pem ? "PEM" : "DER");
308             QTest::newRow(testName.toLatin1())
309                 << keyInfo.fileInfo.absoluteFilePath() << keyInfo.algorithm << keyInfo.type
310                 << keyInfo.format << password;
311         }
312     }
313 }
314 
toEncryptedPemOrDer()315 void tst_QSslKey::toEncryptedPemOrDer()
316 {
317     if (!QSslSocket::supportsSsl())
318         return;
319 
320     QFETCH(QString, absFilePath);
321     QFETCH(QSsl::KeyAlgorithm, algorithm);
322     QFETCH(QSsl::KeyType, type);
323     QFETCH(QSsl::EncodingFormat, format);
324     QFETCH(QString, password);
325 
326     QByteArray plain = readFile(absFilePath);
327     QSslKey key(plain, algorithm, format, type);
328     QVERIFY(!key.isNull());
329 
330     QByteArray pwBytes(password.toLatin1());
331 
332     if (type == QSsl::PrivateKey) {
333         QByteArray encryptedPem = key.toPem(pwBytes);
334         QVERIFY(!encryptedPem.isEmpty());
335         QSslKey keyPem(encryptedPem, algorithm, QSsl::Pem, type, pwBytes);
336         QVERIFY(!keyPem.isNull());
337         QCOMPARE(keyPem, key);
338         QCOMPARE(keyPem.toPem(), key.toPem());
339     } else {
340         // verify that public keys are never encrypted by toPem()
341         QByteArray encryptedPem = key.toPem(pwBytes);
342         QVERIFY(!encryptedPem.isEmpty());
343         QByteArray plainPem = key.toPem();
344         QVERIFY(!plainPem.isEmpty());
345         QCOMPARE(encryptedPem, plainPem);
346     }
347 
348     if (type == QSsl::PrivateKey) {
349         QByteArray encryptedDer = key.toDer(pwBytes);
350         // ### at this point, encryptedDer is invalid, hence the below QEXPECT_FAILs
351         QVERIFY(!encryptedDer.isEmpty());
352         QSslKey keyDer(encryptedDer, algorithm, QSsl::Der, type, pwBytes);
353         if (type == QSsl::PrivateKey)
354             QEXPECT_FAIL(
355                 QTest::currentDataTag(), "We're not able to decrypt these yet...", Continue);
356         QVERIFY(!keyDer.isNull());
357         if (type == QSsl::PrivateKey)
358             QEXPECT_FAIL(
359                 QTest::currentDataTag(), "We're not able to decrypt these yet...", Continue);
360         QCOMPARE(keyDer.toPem(), key.toPem());
361     } else {
362         // verify that public keys are never encrypted by toDer()
363         QByteArray encryptedDer = key.toDer(pwBytes);
364         QVERIFY(!encryptedDer.isEmpty());
365         QByteArray plainDer = key.toDer();
366         QVERIFY(!plainDer.isEmpty());
367         QCOMPARE(encryptedDer, plainDer);
368     }
369 
370     // ### add a test to verify that public keys are _decrypted_ correctly (by the ctor)
371 }
372 
passphraseChecks()373 void tst_QSslKey::passphraseChecks()
374 {
375     {
376         QString fileName(SRCDIR "/rsa-with-passphrase.pem");
377         QFile keyFile(fileName);
378         QVERIFY(keyFile.exists());
379         {
380             if (!keyFile.isOpen())
381                 keyFile.open(QIODevice::ReadOnly);
382             else
383                 keyFile.reset();
384             QSslKey key(&keyFile,QSsl::Rsa,QSsl::Pem, QSsl::PrivateKey);
385             QVERIFY(key.isNull()); // null passphrase => should not be able to decode key
386         }
387         {
388             if (!keyFile.isOpen())
389                 keyFile.open(QIODevice::ReadOnly);
390             else
391                 keyFile.reset();
392             QSslKey key(&keyFile,QSsl::Rsa,QSsl::Pem, QSsl::PrivateKey, "");
393             QVERIFY(key.isNull()); // empty passphrase => should not be able to decode key
394         }
395         {
396             if (!keyFile.isOpen())
397                 keyFile.open(QIODevice::ReadOnly);
398             else
399                 keyFile.reset();
400             QSslKey key(&keyFile,QSsl::Rsa,QSsl::Pem, QSsl::PrivateKey, "WRONG!");
401             QVERIFY(key.isNull()); // wrong passphrase => should not be able to decode key
402         }
403         {
404             if (!keyFile.isOpen())
405                 keyFile.open(QIODevice::ReadOnly);
406             else
407                 keyFile.reset();
408             QSslKey key(&keyFile,QSsl::Rsa,QSsl::Pem, QSsl::PrivateKey, "123");
409             QVERIFY(!key.isNull()); // correct passphrase
410         }
411     }
412 
413     {
414         // be sure and check a key without passphrase too
415         QString fileName(SRCDIR "/rsa-without-passphrase.pem");
416         QFile keyFile(fileName);
417         {
418             if (!keyFile.isOpen())
419                 keyFile.open(QIODevice::ReadOnly);
420             else
421                 keyFile.reset();
422             QSslKey key(&keyFile,QSsl::Rsa,QSsl::Pem, QSsl::PrivateKey);
423             QVERIFY(!key.isNull()); // null passphrase => should be able to decode key
424         }
425         {
426             if (!keyFile.isOpen())
427                 keyFile.open(QIODevice::ReadOnly);
428             else
429                 keyFile.reset();
430             QSslKey key(&keyFile,QSsl::Rsa,QSsl::Pem, QSsl::PrivateKey, "");
431             QVERIFY(!key.isNull()); // empty passphrase => should be able to decode key
432         }
433         {
434             if (!keyFile.isOpen())
435                 keyFile.open(QIODevice::ReadOnly);
436             else
437                 keyFile.reset();
438             QSslKey key(&keyFile,QSsl::Rsa,QSsl::Pem, QSsl::PrivateKey, "xxx");
439             QVERIFY(!key.isNull()); // passphrase given but key is not encrypted anyway => should work
440         }
441     }
442 }
443 
444 #endif
445 
446 QTEST_MAIN(tst_QSslKey)
447 #include "tst_qsslkey.moc"
448