diff --git a/src/vdv/certs/.gitignore b/src/vdv/certs/.gitignore new file mode 100644 index 0000000..0ef6f89 --- /dev/null +++ b/src/vdv/certs/.gitignore @@ -0,0 +1 @@ +.*.vdv-cert diff --git a/src/vdv/certs/cert-downloader.cpp b/src/vdv/certs/cert-downloader.cpp index 1ba99ca..3b82134 100644 --- a/src/vdv/certs/cert-downloader.cpp +++ b/src/vdv/certs/cert-downloader.cpp @@ -1,154 +1,172 @@ /* Copyright (C) 2019 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include +#include #include #include #include #include #include using namespace KItinerary; static std::vector listCerts() { QProcess proc; proc.setProgram(QStringLiteral("kioclient5")); proc.setArguments({QStringLiteral("ls"), QStringLiteral("ldap://ldap-vdv-ion.telesec.de:389/ou=VDV%20KA,o=VDV%20Kernapplikations%20GmbH,c=de")}); proc.setProcessChannelMode(QProcess::ForwardedErrorChannel); proc.start(); if (!proc.waitForFinished() || proc.exitStatus() != QProcess::NormalExit) { qFatal("Failed to list certificates from LDAP server."); } std::vector certs; for (const auto &line : proc.readAllStandardOutput().split('\n')) { if (line.size() <= 5) { continue; } certs.push_back(QString::fromUtf8(line.left(line.size() - 5))); } return certs; } static void downloadCert(const QString &certName) { QProcess proc; proc.setProgram(QStringLiteral("kioclient5")); proc.setArguments({QStringLiteral("cat"), QStringLiteral("ldap://ldap-vdv-ion.telesec.de:389/cn=") + certName + QStringLiteral(",ou=VDV%20KA,o=VDV%20Kernapplikations%20GmbH,c=de")}); proc.setProcessChannelMode(QProcess::ForwardedErrorChannel); proc.start(); if (!proc.waitForFinished() || proc.exitStatus() != QProcess::NormalExit) { qFatal("Failed to download certificate %s from LDAP server.", qPrintable(certName)); } // primitive LDIF parser, would be nicer with something like KLDAP const auto certLdif = QString::fromUtf8(proc.readAllStandardOutput()); QRegularExpression regExp(QStringLiteral("cACertificate:: ([\\w\\W]*?)\n[^ ]")); const auto match = regExp.match(certLdif); const auto certData = match.captured(1).remove(QLatin1Char('\n')).remove(QLatin1Char(' ')).toUtf8(); QFile f(certName + QLatin1String(".vdv-cert")); f.open(QFile::WriteOnly); f.write(QByteArray::fromBase64(certData)); } static void writeQrc(const std::vector &certNames) { QFile qrc(QStringLiteral("vdv-certs.qrc")); if (!qrc.open(QFile::WriteOnly)) { qFatal("Failed to open file %s: %s", qPrintable(qrc.fileName()), qPrintable(qrc.errorString())); } qrc.write("\n \n"); for (const auto &certName : certNames) { qrc.write(" "); qrc.write(certName.toUtf8()); qrc.write(".vdv-cert\n"); } qrc.write(" \n\n"); } static VdvCertificate loadCert(const QString &certName) { QFile f(certName + QLatin1String(".vdv-cert")); if (!f.open(QFile::ReadOnly)) { qFatal("Failed to open file %s: %s", qPrintable(f.fileName()), qPrintable(f.errorString())); } return VdvCertificate(f.readAll()); } static void decodeCert(const QString &certName) { auto cert = loadCert(certName); if (cert.needsCaKey()) { qDebug() << certName << "needs decoding"; const auto rootCa = loadCert(QStringLiteral("4555564456100106")); cert.setCaCertificate(rootCa); if (cert.isValid()) { QFile f(certName + QLatin1String(".vdv-cert")); if (!f.open(QFile::WriteOnly)) { qFatal("Failed to open file %s: %s", qPrintable(f.fileName()), qPrintable(f.errorString())); } cert.writeKey(&f); } else { qFatal("Decoding failed for %s", qPrintable(certName));; } } else if (cert.isValid()) { // this removes the signature and other unknown elements, leaving just the key QFile f(certName + QLatin1String(".vdv-cert")); if (!f.open(QFile::WriteOnly)) { qFatal("Failed to open file %s: %s", qPrintable(f.fileName()), qPrintable(f.errorString())); } cert.writeKey(&f); } else { qFatal("%s is invalid", qPrintable(certName)); } } int main(int argc, char **argv) { QCoreApplication app(argc, argv); // (1) list all certificates auto certNames = listCerts(); // (2) load all certificates we don't have yet - for (const auto &certName : certNames) { - qDebug() << "checking certificate" << certName; - if (QFile::exists(certName + QLatin1String(".vdv-cert"))) { + for (auto it = certNames.begin(); it != certNames.end();) { + if (QFile::exists(QLatin1Char('.') + (*it) + QLatin1String(".vdv-cert"))) { + // expired certificate, but cached from previous run + it = certNames.erase(it); continue; } - downloadCert(certName); + qDebug() << "checking certificate" << (*it); + if (!QFile::exists((*it) + QLatin1String(".vdv-cert"))) { + downloadCert(*it); + } + ++it; } // (3) decode certificates (avoids runtime cost and shrinks the file size) for (const auto &certName : certNames) { decodeCert(certName); } // (4) discard old sub-CA certificates we don't need - // TODO + for (auto it = certNames.begin(); it != certNames.end();) { + const auto cert = loadCert(*it); + if (!cert.isValid()) { + qFatal("Invalid certificate: %s", qPrintable(*it)); + } + if (!cert.isSelfSigned() && cert.endOfValidity().year() < 2019) { + qDebug() << "discarding" << (*it) << "due to being expired" << cert.endOfValidity(); + QFile::rename((*it) + QLatin1String(".vdv-cert"), QLatin1Char('.') + (*it) + QLatin1String(".vdv-cert")); + it = certNames.erase(it); + } else { + ++it; + } + } // (5) write qrc file std::sort(certNames.begin(), certNames.end()); writeQrc(certNames); return 0; }