Skip to content

Commit 469d24e

Browse files
committed
Test export functionality and indirectly the import
1 parent 2f482fc commit 469d24e

File tree

7 files changed

+554
-72
lines changed

7 files changed

+554
-72
lines changed

test/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11

2-
list(APPEND srcs PwDialogMock.h importPEM.cpp main.cpp main.h newKey.cpp pem.cpp)
2+
list(APPEND srcs PwDialogMock.h importPEM.cpp main.cpp main.h
3+
newKey.cpp pem.cpp export.cpp)
34

45
list(TRANSFORM srcs PREPEND ${PROJECT_SOURCE_DIR}/test/)
56

test/PwDialogMock.h

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
* All rights reserved.
66
*/
77

8+
#ifndef __PWDIALOGMOCK_H
9+
#define __PWDIALOGMOCK_H
810
#include <QDebug>
911

1012
#include "lib/debug_info.h"
@@ -42,17 +44,27 @@ class PwDialogMock: public PwDialogUI_i
4244
pwe->pi.setTitle(p->getTitle());
4345
pwe->pi.setDescription(p->getDescription());
4446

45-
qWarning() << "PwDialogMock" << p->getDescription() << expect_idx;
47+
qWarning() << "PwDialogMock" << p->getDescription() << expect_idx
48+
<< "Password:" << pwe->pass_return;
4649
*passwd = pwe->pass_return;
4750
return pwe->result;
4851
}
52+
53+
~PwDialogMock()
54+
{
55+
qDeleteAll(pw_expectations);
56+
}
57+
4958
public:
50-
int expect_idx{};
51-
QList<pw_expect*> pw_expectations{};
52-
void setExpectations(const QList<pw_expect*> pwe)
53-
{
54-
qDeleteAll(pw_expectations);
55-
pw_expectations = pwe;
56-
expect_idx = 0;
57-
}
59+
60+
int expect_idx{};
61+
QList<pw_expect*> pw_expectations{};
62+
63+
void setExpectations(const QList<pw_expect*> pwe)
64+
{
65+
qDeleteAll(pw_expectations);
66+
pw_expectations = pwe;
67+
expect_idx = 0;
68+
}
5869
};
70+
#endif

test/export.cpp

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
/* vi: set sw=4 ts=4:
2+
*
3+
* Copyright (C) 2023 Christian Hohnstaedt.
4+
*
5+
* All rights reserved.
6+
*/
7+
8+
#include <QTest>
9+
10+
#include <sys/types.h>
11+
#include <sys/stat.h>
12+
#include <fcntl.h>
13+
#include <unistd.h>
14+
15+
#include "lib/pki_multi.h"
16+
#include "lib/db_x509.h"
17+
#include "lib/pki_x509.h"
18+
#include "lib/xfile.h"
19+
#include "lib/database_model.h"
20+
21+
#include <widgets/MainWindow.h>
22+
#include "main.h"
23+
24+
void check_pems(const QString &name, int n, QStringList matches = QStringList())
25+
{
26+
int begin = 0, end = 0;
27+
qWarning() << "Expecting" << n << "PEMs in" << name;
28+
29+
#if 0
30+
// This is an endless loop: open_read() succeeds,
31+
// but isOpen returns false. Stop investigating, use POSIX open()
32+
XFile F(name);
33+
while (!F.isOpen()) {
34+
qWarning() << "OPEN" << name;
35+
F.close();
36+
Q_ASSERT(F.open_read());
37+
}
38+
QByteArray all = F.readAll();
39+
#else
40+
int fd = open(qPrintable(name), O_RDONLY);
41+
Q_ASSERT(fd != -1);
42+
char buf[65536];
43+
ssize_t ret = read(fd, buf, sizeof buf);
44+
Q_ASSERT(ret != -1);
45+
QByteArray all(buf, ret);
46+
close(fd);
47+
#endif
48+
qWarning() << "ALL" << name << all.size();
49+
50+
foreach(QByteArray b, all.split('\n')) {
51+
if (b.indexOf("-----BEGIN ") == 0)
52+
begin++;
53+
if (b.indexOf("-----END ") == 0)
54+
end++;
55+
56+
QMutableStringListIterator i(matches);
57+
while (i.hasNext()) {
58+
QByteArray match = i.next().toUtf8();
59+
if (b.indexOf(match) != -1)
60+
i.remove();
61+
}
62+
}
63+
QCOMPARE(begin, n);
64+
QCOMPARE(end, n);
65+
foreach(QString m, matches) {
66+
QWARN(qPrintable(QString("Pattern %1 not found in %2").arg(m).arg(name)));
67+
}
68+
QCOMPARE(matches.size(), 0);
69+
}
70+
71+
void verify_key(const QString &name, QList<unsigned> hashes, bool priv)
72+
{
73+
pki_multi *pems = new pki_multi();
74+
QVERIFY(pems != nullptr);
75+
pems->probeAnything(name);
76+
QCOMPARE(pems->get().size(), hashes.size());
77+
foreach (pki_base *pki, pems->get()) {
78+
unsigned hash = pki->hash();
79+
qWarning() << pki->getIntName() << hash;
80+
QVERIFY2(hashes.contains(hash),
81+
qPrintable(QString("%1 not expected in %2")
82+
.arg(pki->getIntName())
83+
.arg(name)
84+
)
85+
);
86+
pki_key *key = dynamic_cast<pki_key*>(pki);
87+
if (key) {
88+
QCOMPARE(key->isPrivKey(), priv);
89+
}
90+
}
91+
}
92+
93+
void verify_file(const QString &name, QList<unsigned> hashes)
94+
{
95+
verify_key(name, hashes, false);
96+
}
97+
98+
void export_by_id(int id, const QString &name,
99+
QModelIndexList &list, db_base *db)
100+
{
101+
const pki_export *xport = pki_export::by_id(id);
102+
QVERIFY(xport != nullptr);
103+
XFile F(name);
104+
F.open_write();
105+
if (xport->match_all(F_PEM)) {
106+
QString prefix = QString("%1\n").arg(xport->help);
107+
foreach (QModelIndex idx, list) {
108+
pki_base *pki = db_base::fromIndex(idx);
109+
QVERIFY(pki != nullptr);
110+
prefix += QString(" - %1[%2]\n")
111+
.arg(pki->getIntName())
112+
.arg(pki->getTypeString());
113+
}
114+
F.write(prefix.toUtf8());
115+
}
116+
db->exportItems(list, xport, F);
117+
F.close();
118+
}
119+
120+
void test_main::exportFormat()
121+
{
122+
int l=0;
123+
QModelIndex idx;
124+
QModelIndexList list;
125+
126+
try {
127+
128+
ign_openssl_error();
129+
openDB();
130+
dbstatus();
131+
pki_multi *pem = new pki_multi();
132+
QString all = pemdata["Inter CA 1"] +
133+
pemdata["Inter CA 1 Key"] +
134+
pemdata["Root CA"] +
135+
pemdata["Endentity"];
136+
137+
pem->fromPEMbyteArray(all.toUtf8(), QString());
138+
QCOMPARE(pem->failed_files.count(), 0);
139+
Database.insert(pem);
140+
dbstatus();
141+
142+
db_base *certs = Database.model<db_x509>();
143+
QVERIFY(certs != nullptr);
144+
145+
// Root CA as only item: No chain, no private key
146+
idx = certs->index(certs->getByName("Root CA"));
147+
list << idx;
148+
QCOMPARE(certs->exportFlags(idx) , F_CHAIN | F_PRIVATE);
149+
QCOMPARE(certs->exportFlags(list) , F_CHAIN | F_PRIVATE | F_MULTI);
150+
151+
// Inter CA 1: All export options permitted
152+
// Together with "Root CA" in "list": No chain, private or single
153+
idx = certs->index(certs->getByName("Inter CA 1"));
154+
list << idx;
155+
QCOMPARE(certs->exportFlags(idx) , 0);
156+
QCOMPARE(certs->exportFlags(list) , F_CHAIN | F_PRIVATE | F_SINGLE);
157+
158+
// Endentity has no private key and id no CA
159+
idx = certs->index(certs->getByName("Endentity"));
160+
list << idx;
161+
QVERIFY(idx.isValid());
162+
QCOMPARE(certs->exportFlags(idx) , F_PRIVATE | F_CA);
163+
164+
pki_key *key = new pki_evp();
165+
key->fromPEMbyteArray(pemdata["Endentity Key"].toUtf8(), QString());
166+
openssl_error();
167+
Database.insert(key);
168+
dbstatus();
169+
170+
// Endentity now has a private key, but is still no CA
171+
QCOMPARE(certs->exportFlags(idx) , F_CA);
172+
173+
#define ROOT_HASH 531145749
174+
#define INTER_HASH 376625776
175+
#define END_HASH 94304590
176+
#define ENDKEY_HASH 1121702347
177+
178+
#define xstr(s) str(s)
179+
#define str(s) #s
180+
#define AUTOFILE(type) "_" # type "_Line" xstr(__LINE__) ".data" ;
181+
182+
const char *file = AUTOFILE(ALLCERT)
183+
// Export All certs in one PEM File
184+
export_by_id(3, file, list, certs);
185+
verify_file(file, QList<unsigned> { ROOT_HASH, INTER_HASH, END_HASH });
186+
check_pems(file, 3);
187+
// Export 2 cert Chain from Inter CA1
188+
file = AUTOFILE(CERTCHAIN)
189+
list.clear();
190+
list << certs->index(certs->getByName("Inter CA 1"));
191+
export_by_id(2, file, list, certs);
192+
verify_file(file, QList<unsigned> { ROOT_HASH, INTER_HASH });
193+
check_pems(file, 2);
194+
195+
// Export 3 cert Chain from Endentity
196+
file = AUTOFILE(CERTCHAIN)
197+
list.clear();
198+
list << certs->index(certs->getByName("Endentity"));
199+
export_by_id(2, file, list, certs);
200+
verify_file(file, QList<unsigned> { ROOT_HASH, INTER_HASH, END_HASH });
201+
check_pems(file, 3);
202+
203+
// Export Endentity + corresponding key
204+
file = AUTOFILE(CERTKEY)
205+
export_by_id(6, file, list, certs);
206+
verify_key(file, QList<unsigned> { END_HASH, ENDKEY_HASH }, true);
207+
check_pems(file, 2, QStringList { " RSA PRIVATE KEY-", " CERTIFICATE-" });
208+
209+
// Export Endentity + corresponding PKCS#8 key
210+
file = AUTOFILE(CERTPK8)
211+
pwdialog->setExpectations(QList<pw_expect*>{
212+
new pw_expect("pass", pw_ok),
213+
new pw_expect("pass", pw_ok),
214+
});
215+
export_by_id(7, file, list, certs);
216+
verify_key(file, QList<unsigned> { END_HASH, ENDKEY_HASH }, true);
217+
check_pems(file, 2, QStringList { " ENCRYPTED PRIVATE KEY-", " CERTIFICATE-" });
218+
// Export Endentity as PKCS#7
219+
file = AUTOFILE(CERTP7)
220+
export_by_id(8, file, list, certs);
221+
verify_file(file, QList<unsigned> { END_HASH });
222+
check_pems(file, 0);
223+
// Export Endentity as PKCS#7 chain
224+
file = AUTOFILE(CERTP7)
225+
export_by_id(12, file, list, certs);
226+
verify_file(file, QList<unsigned> { ROOT_HASH, INTER_HASH, END_HASH });
227+
check_pems(file, 0);
228+
// Export Endentity as DER certificate
229+
export_by_id(13, file, list, certs);
230+
verify_file(file, QList<unsigned> { END_HASH });
231+
check_pems(file, 0);
232+
233+
// Export Endentity key
234+
list.clear();
235+
key = dynamic_cast<pki_x509*>(certs->getByName("Endentity"))->getRefKey();
236+
db_base *keys = Database.model<db_key>();
237+
list << keys->index(key);
238+
239+
// Public Key
240+
file = AUTOFILE(PUBKEY)
241+
export_by_id(19, file, list, keys);
242+
verify_key(file, QList<unsigned> { ENDKEY_HASH }, false);
243+
check_pems(file, 1, QStringList{ "PUBLIC KEY" });
244+
245+
// Private Key
246+
file = AUTOFILE(PRIVKEY)
247+
export_by_id(20, file, list, keys);
248+
verify_key(file, QList<unsigned> { ENDKEY_HASH }, true);
249+
check_pems(file, 1, QStringList{ "RSA PRIVATE KEY" });
250+
251+
// Private Key Openssl Encrypted
252+
file = AUTOFILE(PRIVKEY)
253+
pwdialog->setExpectations(QList<pw_expect*>{
254+
new pw_expect("pass", pw_ok),
255+
new pw_expect("pass", pw_ok),
256+
});
257+
export_by_id(21, file, list, keys);
258+
verify_key(file, QList<unsigned> { ENDKEY_HASH }, true);
259+
check_pems(file, 1, QStringList { "DEK-Info: ", "Proc-Type: 4,ENCRYPTED", "BEGIN RSA PRIVATE KEY" });
260+
261+
// Private SSH Key
262+
file = AUTOFILE(PRIVSSH)
263+
export_by_id(22, file, list, keys);
264+
verify_key(file, QList<unsigned> { ENDKEY_HASH }, true);
265+
check_pems(file, 1, QStringList{ "RSA PRIVATE KEY" });
266+
267+
// Public SSH Key
268+
file = AUTOFILE(PUBSSH)
269+
export_by_id(23, file, list, keys);
270+
verify_key(file, QList<unsigned> { ENDKEY_HASH }, false);
271+
check_pems(file, 0, QStringList{ "ssh-rsa " });
272+
273+
// Public DER Key
274+
file = AUTOFILE(PUBDER)
275+
export_by_id(24, file, list, keys);
276+
verify_key(file, QList<unsigned> { ENDKEY_HASH }, false);
277+
check_pems(file, 0);
278+
279+
// Private DER Key
280+
file = AUTOFILE(PRIVDER)
281+
export_by_id(25, file, list, keys);
282+
verify_key(file, QList<unsigned> { ENDKEY_HASH }, true);
283+
check_pems(file, 0);
284+
285+
// Private PVK Key
286+
file = AUTOFILE(PVK)
287+
export_by_id(26, file, list, keys);
288+
verify_key(file, QList<unsigned> { ENDKEY_HASH }, true);
289+
check_pems(file, 0);
290+
291+
// Private PVK Key encrypted
292+
file = AUTOFILE(PVK)
293+
pwdialog->setExpectations(QList<pw_expect*>{
294+
new pw_expect("pass", pw_ok),
295+
new pw_expect("pass", pw_ok),
296+
});
297+
export_by_id(27, file, list, keys);
298+
verify_key(file, QList<unsigned> { ENDKEY_HASH }, true);
299+
check_pems(file, 0);
300+
301+
} catch (...) {
302+
QString m = QString("Exception thrown L %1").arg(l);
303+
QVERIFY2(false, m.toUtf8().constData());
304+
}
305+
}

0 commit comments

Comments
 (0)