File indexing completed on 2025-02-16 04:52:28

0001 /*
0002     SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "vdvcertificate_p.h"
0008 
0009 #include <QCoreApplication>
0010 #include <QDate>
0011 #include <QDebug>
0012 #include <QFile>
0013 #include <QProcess>
0014 #include <QRegularExpression>
0015 
0016 #include <vector>
0017 
0018 using namespace KItinerary;
0019 
0020 static std::vector<QString> listCerts()
0021 {
0022     QProcess proc;
0023     proc.setProgram(QStringLiteral("kioclient"));
0024     proc.setArguments({QStringLiteral("ls"), QStringLiteral("ldap://ldap-vdv-ion.telesec.de:389/ou=VDV%20KA,o=VDV%20Kernapplikations%20GmbH,c=de")});
0025     proc.setProcessChannelMode(QProcess::ForwardedErrorChannel);
0026     proc.start();
0027     if (!proc.waitForFinished() || proc.exitStatus() != QProcess::NormalExit) {
0028         qFatal("Failed to list certificates from LDAP server.");
0029     }
0030 
0031     std::vector<QString> certs;
0032     for (const auto &line : proc.readAllStandardOutput().split('\n')) {
0033         if (line.size() <= 5) {
0034             continue;
0035         }
0036         certs.push_back(QString::fromUtf8(line.left(line.size() - 5)));
0037     }
0038     return certs;
0039 }
0040 
0041 static void downloadCert(const QString &certName)
0042 {
0043     QProcess proc;
0044     proc.setProgram(QStringLiteral("kioclient"));
0045     proc.setArguments({QStringLiteral("cat"), QStringLiteral("ldap://ldap-vdv-ion.telesec.de:389/cn=") + certName + QStringLiteral(",ou=VDV%20KA,o=VDV%20Kernapplikations%20GmbH,c=de")});
0046     proc.setProcessChannelMode(QProcess::ForwardedErrorChannel);
0047     proc.start();
0048     if (!proc.waitForFinished() || proc.exitStatus() != QProcess::NormalExit) {
0049         qFatal("Failed to download certificate %s from LDAP server.", qPrintable(certName));
0050     }
0051 
0052     // primitive LDIF parser, would be nicer with something like KLDAP
0053     const auto certLdif = QString::fromUtf8(proc.readAllStandardOutput());
0054     QRegularExpression regExp(QStringLiteral("cACertificate:: ([\\w\\W]*?)\n[^ ]"));
0055     const auto match = regExp.match(certLdif);
0056     const auto certData = match.captured(1).remove(QLatin1Char('\n')).remove(QLatin1Char(' ')).toUtf8();
0057 
0058     QFile f(certName + QLatin1StringView(".vdv-cert"));
0059     f.open(QFile::WriteOnly);
0060     f.write(QByteArray::fromBase64(certData));
0061 }
0062 
0063 static void writeQrc(const std::vector<QString> &certNames)
0064 {
0065     QFile qrc(QStringLiteral("vdv-certs.qrc"));
0066     if (!qrc.open(QFile::WriteOnly)) {
0067         qFatal("Failed to open file %s: %s", qPrintable(qrc.fileName()), qPrintable(qrc.errorString()));
0068     }
0069     qrc.write(R"(<!--
0070     SPDX-FileCopyrightText: none
0071     SPDX-License-Identifier: CC0-1.0
0072 -->
0073 <RCC>
0074     <qresource prefix="/org.kde.pim/kitinerary/vdv/certs">
0075 )");
0076     for (const auto &certName : certNames) {
0077         qrc.write("        <file>");
0078         qrc.write(certName.toUtf8());
0079         qrc.write(".vdv-cert</file>\n");
0080     }
0081     qrc.write("    </qresource>\n</RCC>\n");
0082 }
0083 
0084 static VdvCertificate loadCert(const QString &certName)
0085 {
0086   QFile f(certName + QLatin1StringView(".vdv-cert"));
0087   if (!f.open(QFile::ReadOnly)) {
0088     qFatal("Failed to open file %s: %s", qPrintable(f.fileName()),
0089            qPrintable(f.errorString()));
0090     }
0091     return VdvCertificate(f.readAll());
0092 }
0093 
0094 static void decodeCert(const QString &certName)
0095 {
0096     auto cert = loadCert(certName);
0097     if (cert.needsCaKey()) {
0098         qDebug() << certName << "needs decoding";
0099         const auto rootCa = loadCert(QStringLiteral("4555564456100106"));
0100         cert.setCaCertificate(rootCa);
0101         if (cert.isValid()) {
0102           QFile f(certName + QLatin1StringView(".vdv-cert"));
0103           if (!f.open(QFile::WriteOnly)) {
0104             qFatal("Failed to open file %s: %s", qPrintable(f.fileName()),
0105                    qPrintable(f.errorString()));
0106             }
0107             cert.writeKey(&f);
0108         } else {
0109             qFatal("Decoding failed for %s", qPrintable(certName));;
0110         }
0111     } else if (cert.isValid()) {
0112         // this removes the signature and other unknown elements, leaving just the key
0113         QFile f(certName + QLatin1StringView(".vdv-cert"));
0114         if (!f.open(QFile::WriteOnly)) {
0115             qFatal("Failed to open file %s: %s", qPrintable(f.fileName()), qPrintable(f.errorString()));
0116         }
0117         cert.writeKey(&f);
0118     } else {
0119         qWarning("%s is invalid", qPrintable(certName));
0120     }
0121 }
0122 
0123 int main(int argc, char **argv)
0124 {
0125     QCoreApplication app(argc, argv);
0126 
0127     // (1) list all certificates
0128     auto certNames = listCerts();
0129 
0130     // (2) load all certificates we don't have yet
0131     for (auto it = certNames.begin(); it != certNames.end();) {
0132       if (QFile::exists(QLatin1Char('.') + (*it) +
0133                         QLatin1StringView(".vdv-cert"))) {
0134         // expired certificate, but cached from previous run
0135         it = certNames.erase(it);
0136         continue;
0137       }
0138         qDebug() << "checking certificate" << (*it);
0139         if (!QFile::exists((*it) + QLatin1StringView(".vdv-cert"))) {
0140           downloadCert(*it);
0141         }
0142         ++it;
0143     }
0144 
0145     // (3) decode certificates (avoids runtime cost and shrinks the file size)
0146     for (const auto &certName : certNames) {
0147         decodeCert(certName);
0148     }
0149 
0150     // (4) discard old sub-CA certificates we don't need
0151     for (auto it = certNames.begin(); it != certNames.end();) {
0152         const auto cert = loadCert(*it);
0153         if (!cert.isValid()) {
0154             qWarning("Invalid certificate: %s", qPrintable(*it));
0155             it = certNames.erase(it);
0156             continue;
0157         }
0158         if (!cert.isSelfSigned() && cert.endOfValidity().year() < 2019) {
0159             qDebug() << "discarding" << (*it) << "due to being expired" << cert.endOfValidity();
0160             QFile::rename((*it) + QLatin1StringView(".vdv-cert"),
0161                           QLatin1Char('.') + (*it) +
0162                               QLatin1String(".vdv-cert"));
0163             it = certNames.erase(it);
0164             continue;
0165         }
0166         ++it;
0167     }
0168 
0169     // (5) write qrc file
0170     std::sort(certNames.begin(), certNames.end());
0171     writeQrc(certNames);
0172 
0173     return 0;
0174 }