xca/test/export.cpp
2024-02-14 22:33:18 +01:00

315 lines
8.8 KiB
C++

/* vi: set sw=4 ts=4:
*
* Copyright (C) 2024 Christian Hohnstaedt.
*
* All rights reserved.
*/
#include <QTest>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "lib/pki_multi.h"
#include "lib/db_x509.h"
#include "lib/pki_x509.h"
#include "lib/xfile.h"
#include "lib/database_model.h"
#include <widgets/MainWindow.h>
#include "main.h"
void check_pems(const QString &name, int n, QStringList matches = QStringList())
{
int begin = 0, end = 0;
qWarning() << "Expecting" << n << "PEMs in" << name;
#if 0
// This is an endless loop: open_read() succeeds,
// but isOpen returns false. Stop investigating, use POSIX open()
XFile F(name);
while (!F.isOpen()) {
qWarning() << "OPEN" << name;
F.close();
Q_ASSERT(F.open_read());
}
QByteArray all = F.readAll();
#else
int fd = open(qPrintable(name), O_RDONLY);
Q_ASSERT(fd != -1);
char buf[65536];
ssize_t ret = read(fd, buf, sizeof buf);
Q_ASSERT(ret != -1);
QByteArray all(buf, ret);
close(fd);
#endif
qWarning() << "ALL" << name << all.size();
foreach(QByteArray b, all.split('\n')) {
if (b.indexOf("-----BEGIN ") == 0)
begin++;
if (b.indexOf("-----END ") == 0)
end++;
QMutableStringListIterator i(matches);
while (i.hasNext()) {
QByteArray match = i.next().toUtf8();
if (b.indexOf(match) != -1)
i.remove();
}
}
QCOMPARE(begin, n);
QCOMPARE(end, n);
foreach(QString m, matches) {
qWarning() << QString("Pattern %1 not found in %2").arg(m).arg(name);
}
QCOMPARE(matches.size(), 0);
}
void verify_key(const QString &name, QList<unsigned> hashes, bool priv)
{
pki_multi *pems = new pki_multi();
QVERIFY(pems != nullptr);
pems->probeAnything(name);
QCOMPARE(pems->get().size(), hashes.size());
foreach (pki_base *pki, pems->get()) {
unsigned hash = pki->hash();
qWarning() << pki->getIntName() << hash;
QVERIFY2(hashes.contains(hash),
qPrintable(QString("%1 not expected in %2")
.arg(pki->getIntName())
.arg(name)
)
);
pki_key *key = dynamic_cast<pki_key*>(pki);
if (key) {
QCOMPARE(key->isPrivKey(), priv);
}
}
}
void verify_file(const QString &name, QList<unsigned> hashes)
{
verify_key(name, hashes, false);
}
void export_by_id(int id, const QString &name,
QModelIndexList &list, db_base *db)
{
const pki_export *xport = pki_export::by_id(id);
QVERIFY(xport != nullptr);
XFile F(name);
F.open_write();
if (xport->match_all(F_PEM)) {
QString prefix = QString("%1\n").arg(xport->help);
foreach (QModelIndex idx, list) {
pki_base *pki = db_base::fromIndex(idx);
QVERIFY(pki != nullptr);
prefix += QString(" - %1[%2]\n")
.arg(pki->getIntName())
.arg(pki->getTypeString());
}
F.write(prefix.toUtf8());
}
db->exportItems(list, xport, F);
F.close();
}
void test_main::exportFormat()
{
int l=0;
QModelIndex idx;
QModelIndexList list;
try {
ign_openssl_error();
openDB();
dbstatus();
pki_multi *pem = new pki_multi();
QString all = pemdata["Inter CA 1"] +
pemdata["Inter CA 1 Key"] +
pemdata["Root CA"] +
pemdata["Endentity"];
pem->fromPEMbyteArray(all.toUtf8(), QString());
QCOMPARE(pem->failed_files.count(), 0);
Database.insert(pem);
dbstatus();
db_base *certs = Database.model<db_x509>();
QVERIFY(certs != nullptr);
// Root CA as only item: No chain, no private key
idx = certs->index(certs->getByName("Root CA"));
list << idx;
QCOMPARE(certs->exportFlags(idx) , F_CHAIN | F_PRIVATE);
QCOMPARE(certs->exportFlags(list) , F_CHAIN | F_PRIVATE | F_MULTI);
// Inter CA 1: All export options permitted
// Together with "Root CA" in "list": No chain, private or single
idx = certs->index(certs->getByName("Inter CA 1"));
list << idx;
QCOMPARE(certs->exportFlags(idx) , 0);
QCOMPARE(certs->exportFlags(list) , F_CHAIN | F_PRIVATE | F_SINGLE);
// Endentity has no private key and id no CA
idx = certs->index(certs->getByName("Endentity"));
list << idx;
QVERIFY(idx.isValid());
QCOMPARE(certs->exportFlags(idx) , F_PRIVATE | F_CA);
pki_key *key = new pki_evp();
key->fromPEMbyteArray(pemdata["Endentity Key"].toUtf8(), QString());
openssl_error();
Database.insert(key);
dbstatus();
// Endentity now has a private key, but is still no CA
QCOMPARE(certs->exportFlags(idx) , F_CA);
#define ROOT_HASH 531145749
#define INTER_HASH 376625776
#define END_HASH 94304590
#define ENDKEY_HASH 1121702347
#define xstr(s) str(s)
#define str(s) #s
#define AUTOFILE(type) "_" # type "_Line" xstr(__LINE__) ".data" ;
const char *file = AUTOFILE(ALLCERT)
// Export All certs in one PEM File
export_by_id(3, file, list, certs);
verify_file(file, QList<unsigned> { ROOT_HASH, INTER_HASH, END_HASH });
check_pems(file, 3);
// Export 2 cert Chain from Inter CA1
file = AUTOFILE(CERTCHAIN)
list.clear();
list << certs->index(certs->getByName("Inter CA 1"));
export_by_id(2, file, list, certs);
verify_file(file, QList<unsigned> { ROOT_HASH, INTER_HASH });
check_pems(file, 2);
// Export 3 cert Chain from Endentity
file = AUTOFILE(CERTCHAIN)
list.clear();
list << certs->index(certs->getByName("Endentity"));
export_by_id(2, file, list, certs);
verify_file(file, QList<unsigned> { ROOT_HASH, INTER_HASH, END_HASH });
check_pems(file, 3);
// Export Endentity + corresponding key
file = AUTOFILE(CERTKEY)
export_by_id(6, file, list, certs);
verify_key(file, QList<unsigned> { END_HASH, ENDKEY_HASH }, true);
check_pems(file, 2, QStringList { " RSA PRIVATE KEY-", " CERTIFICATE-" });
// Export Endentity + corresponding PKCS#8 key
file = AUTOFILE(CERTPK8)
pwdialog->setExpectations(QList<pw_expect*>{
new pw_expect("pass", pw_ok),
new pw_expect("pass", pw_ok),
});
export_by_id(7, file, list, certs);
verify_key(file, QList<unsigned> { END_HASH, ENDKEY_HASH }, true);
check_pems(file, 2, QStringList { " ENCRYPTED PRIVATE KEY-", " CERTIFICATE-" });
// Export OpenVPN format
file = AUTOFILE(OPENVPN)
export_by_id(4, file, list, certs);
verify_key(file, QList<unsigned> {
ROOT_HASH, INTER_HASH, END_HASH, ENDKEY_HASH }, true);
check_pems(file, 4, QStringList { " RSA PRIVATE KEY-",
" CERTIFICATE-", " CERTIFICATE-"," CERTIFICATE-",
"<ca>", "</ca>", "<extra-certs>", "</extra-certs>",
"<cert>", "</cert>", "<key>", "</key>" });
// Export Endentity as PKCS#7
file = AUTOFILE(CERTP7)
export_by_id(8, file, list, certs);
verify_file(file, QList<unsigned> { END_HASH });
check_pems(file, 0);
// Export Endentity as PKCS#7 chain
file = AUTOFILE(CERTP7)
export_by_id(12, file, list, certs);
verify_file(file, QList<unsigned> { ROOT_HASH, INTER_HASH, END_HASH });
check_pems(file, 0);
// Export Endentity as DER certificate
export_by_id(13, file, list, certs);
verify_file(file, QList<unsigned> { END_HASH });
check_pems(file, 0);
// Export Endentity key
list.clear();
key = dynamic_cast<pki_x509*>(certs->getByName("Endentity"))->getRefKey();
db_base *keys = Database.model<db_key>();
list << keys->index(key);
// Public Key
file = AUTOFILE(PUBKEY)
export_by_id(19, file, list, keys);
verify_key(file, QList<unsigned> { ENDKEY_HASH }, false);
check_pems(file, 1, QStringList{ "PUBLIC KEY" });
// Private Key
file = AUTOFILE(PRIVKEY)
export_by_id(20, file, list, keys);
verify_key(file, QList<unsigned> { ENDKEY_HASH }, true);
check_pems(file, 1, QStringList{ "RSA PRIVATE KEY" });
// Private Key Openssl Encrypted
file = AUTOFILE(PRIVKEY)
pwdialog->setExpectations(QList<pw_expect*>{
new pw_expect("pass", pw_ok),
new pw_expect("pass", pw_ok),
});
export_by_id(21, file, list, keys);
verify_key(file, QList<unsigned> { ENDKEY_HASH }, true);
check_pems(file, 1, QStringList { "DEK-Info: ", "Proc-Type: 4,ENCRYPTED", "BEGIN RSA PRIVATE KEY" });
// Private SSH Key
file = AUTOFILE(PRIVSSH)
export_by_id(22, file, list, keys);
verify_key(file, QList<unsigned> { ENDKEY_HASH }, true);
check_pems(file, 1, QStringList{ "RSA PRIVATE KEY" });
// Public SSH Key
file = AUTOFILE(PUBSSH)
export_by_id(23, file, list, keys);
verify_key(file, QList<unsigned> { ENDKEY_HASH }, false);
check_pems(file, 0, QStringList{ "ssh-rsa " });
// Public DER Key
file = AUTOFILE(PUBDER)
export_by_id(24, file, list, keys);
verify_key(file, QList<unsigned> { ENDKEY_HASH }, false);
check_pems(file, 0);
// Private DER Key
file = AUTOFILE(PRIVDER)
export_by_id(25, file, list, keys);
verify_key(file, QList<unsigned> { ENDKEY_HASH }, true);
check_pems(file, 0);
// Private PVK Key
file = AUTOFILE(PVK)
export_by_id(26, file, list, keys);
verify_key(file, QList<unsigned> { ENDKEY_HASH }, true);
check_pems(file, 0);
// Private PVK Key encrypted
file = AUTOFILE(PVK)
pwdialog->setExpectations(QList<pw_expect*>{
new pw_expect("pass", pw_ok),
new pw_expect("pass", pw_ok),
});
export_by_id(27, file, list, keys);
verify_key(file, QList<unsigned> { ENDKEY_HASH }, true);
check_pems(file, 0);
} catch (...) {
QString m = QString("Exception thrown L %1").arg(l);
QVERIFY2(false, m.toUtf8().constData());
}
}