/* vi: set sw=4 ts=4: * * Copyright (C) 2024 Christian Hohnstaedt. * * All rights reserved. */ #include #include #include #include #include #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 #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 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); if (key) { QCOMPARE(key->isPrivKey(), priv); } } } void verify_file(const QString &name, QList 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(); 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 { 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 { 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 { 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 { 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{ new pw_expect("pass", pw_ok), new pw_expect("pass", pw_ok), }); export_by_id(7, file, list, certs); verify_key(file, QList { 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 { ROOT_HASH, INTER_HASH, END_HASH, ENDKEY_HASH }, true); check_pems(file, 4, QStringList { " RSA PRIVATE KEY-", " CERTIFICATE-", " CERTIFICATE-"," CERTIFICATE-", "", "", "", "", "", "", "", "" }); // Export Endentity as PKCS#7 file = AUTOFILE(CERTP7) export_by_id(8, file, list, certs); verify_file(file, QList { 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 { 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 { END_HASH }); check_pems(file, 0); // Export Endentity key list.clear(); key = dynamic_cast(certs->getByName("Endentity"))->getRefKey(); db_base *keys = Database.model(); list << keys->index(key); // Public Key file = AUTOFILE(PUBKEY) export_by_id(19, file, list, keys); verify_key(file, QList { 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 { ENDKEY_HASH }, true); check_pems(file, 1, QStringList{ "RSA PRIVATE KEY" }); // Private Key Openssl Encrypted file = AUTOFILE(PRIVKEY) pwdialog->setExpectations(QList{ new pw_expect("pass", pw_ok), new pw_expect("pass", pw_ok), }); export_by_id(21, file, list, keys); verify_key(file, QList { 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 { 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 { 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 { ENDKEY_HASH }, false); check_pems(file, 0); // Private DER Key file = AUTOFILE(PRIVDER) export_by_id(25, file, list, keys); verify_key(file, QList { ENDKEY_HASH }, true); check_pems(file, 0); // Private PVK Key file = AUTOFILE(PVK) export_by_id(26, file, list, keys); verify_key(file, QList { ENDKEY_HASH }, true); check_pems(file, 0); // Private PVK Key encrypted file = AUTOFILE(PVK) pwdialog->setExpectations(QList{ new pw_expect("pass", pw_ok), new pw_expect("pass", pw_ok), }); export_by_id(27, file, list, keys); verify_key(file, QList { ENDKEY_HASH }, true); check_pems(file, 0); } catch (...) { QString m = QString("Exception thrown L %1").arg(l); QVERIFY2(false, m.toUtf8().constData()); } }