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 }