File indexing completed on 2025-07-13 04:48:32

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 #include "vdvdata_p.h"
0009 #include "iso9796_2decoder_p.h"
0010 
0011 #include "../asn1/berelement.h"
0012 
0013 #include <QDate>
0014 #include <QDebug>
0015 #include <QFile>
0016 
0017 using namespace KItinerary;
0018 
0019 VdvCertificate::VdvCertificate() = default;
0020 
0021 VdvCertificate::VdvCertificate(const QByteArray &data, int offset)
0022     : m_offset(offset)
0023 {
0024     const auto hdr = BER::TypedElement<TagCertificate>(data, offset);
0025     if (!hdr.isValid()) {
0026         qDebug() << "Invalid certificate header:" << hdr.isValid() << data.size() << offset;
0027         return;
0028     }
0029 
0030     m_data = data;
0031     const auto certKeyBlock = hdr.find(TagCertificateContent);
0032     if (certKeyBlock.isValid()) {
0033         m_type = Raw;
0034         qDebug() << "found decrypted key";
0035         qDebug() << "CHR:" << QByteArray(certKey()->chr.name, 5) << certKey()->chr.algorithmReference << certKey()->chr.year;
0036         qDebug() << "CAR:" << QByteArray(certKey()->car.region, 2) << QByteArray(certKey()->car.name, 3);
0037         return;
0038     }
0039 
0040     const auto sig = hdr.find(TagCertificateSignature);
0041     if (!sig.isValid()) {
0042         qWarning() << "Invalid certificate content: neither a key nor a signature!";
0043         m_data.clear();
0044         return;
0045     }
0046 
0047     m_type = Signed;
0048     qDebug() << "found encrypted key";
0049 }
0050 
0051 VdvCertificate::~VdvCertificate() = default;
0052 
0053 bool VdvCertificate::isValid() const
0054 {
0055     if (m_type == Invalid) {
0056         return false;
0057     }
0058     return m_type == Signed ? !m_recoveredData.isEmpty() : !m_data.isEmpty();
0059 }
0060 
0061 bool VdvCertificate::needsCaKey() const
0062 {
0063     return m_type == Signed && m_recoveredData.isEmpty();
0064 }
0065 
0066 int VdvCertificate::size() const
0067 {
0068     return m_type == Invalid ? 0 : header().size();
0069 }
0070 
0071 uint16_t VdvCertificate::modulusSize() const
0072 {
0073     switch (certKey()->certificateProfileIdentifier) {
0074         case 3:
0075             return 1536 / 8;
0076         case 4:
0077             return 1024 / 8;
0078         case 7:
0079             return 1984 / 8;
0080     }
0081     qWarning() << "Unknown certificate profile identifier: " << certKey()->certificateProfileIdentifier;
0082     return 0;
0083 }
0084 
0085 const uint8_t* VdvCertificate::modulus() const
0086 {
0087     const auto k = certKey();
0088     return (&k->oidBegin) + k->oidSize();
0089 }
0090 
0091 uint16_t VdvCertificate::exponentSize() const
0092 {
0093     return 4;
0094 }
0095 
0096 const uint8_t* VdvCertificate::exponent() const
0097 {
0098     return modulus() + modulusSize();
0099 }
0100 
0101 void VdvCertificate::setCaCertificate(const VdvCertificate &caCert)
0102 {
0103     if (!caCert.isValid()) {
0104         qWarning() << "Invalid CA certificate.";
0105         return;
0106     }
0107 
0108     Iso9796_2Decoder decoder;
0109     decoder.setRsaParameters(caCert.modulus(), caCert.modulusSize(), caCert.exponent(), caCert.exponentSize());
0110 
0111     const auto sig = header().find(TagCertificateSignature);
0112     decoder.addWithRecoveredMessage(sig.contentData(), sig.contentSize());
0113 
0114     if (header().contentSize() > sig.size()) {
0115         const auto rem = header().find(TagCertificateSignatureRemainder);
0116         if (rem.isValid()) {
0117             decoder.add(rem.contentData(), rem.contentSize());
0118         } else {
0119             qWarning() << "Invalid signature remainder!" << rem.isValid() << rem.size() << sig.size() << header().contentSize();
0120         }
0121     }
0122 
0123     m_recoveredData = decoder.recoveredMessage();
0124     if (!m_recoveredData.isEmpty() && m_recoveredData.size() >= (certKey()->headerSize() + modulusSize() + exponentSize())) {
0125         qDebug() << "successfully decrypted key";
0126         qDebug() << "CAR:" << QByteArray(certKey()->car.region, 2) << QByteArray(certKey()->car.name, 3);
0127         qDebug() << "CHR:" << QByteArray(certKey()->chr.name, 5) << certKey()->chr.algorithmReference << certKey()->chr.year;
0128     } else {
0129         qWarning() << "decrypting certificate key failed!";
0130         qDebug() << "size is:" << m_recoveredData.size() << "expected:" << (certKey()->headerSize() + modulusSize() + exponentSize());
0131         qDebug() << QByteArray((const char*)sig.contentData(), sig.contentSize()).toHex();
0132         m_type = Invalid;
0133         m_recoveredData.clear();
0134     }
0135 }
0136 
0137 void VdvCertificate::writeKey(QIODevice *out) const
0138 {
0139     out->write("\x7F\x21");
0140     if (m_type == Signed) {
0141         BER::Element::writeSize(out, m_recoveredData.size() + 3);
0142         out->write("\x5F\x4E");
0143         BER::Element::writeSize(out, m_recoveredData.size());
0144         out->write(m_recoveredData);
0145     } else if (m_type == Raw) {
0146         const auto keyBlock = header().find(TagCertificateContent);
0147         BER::Element::writeSize(out, keyBlock.size());
0148         out->write(keyBlock.rawData(), keyBlock.size());
0149     }
0150 }
0151 
0152 bool VdvCertificate::isSelfSigned() const
0153 {
0154     return memcmp(&certKey()->car, certKey()->chr.name, sizeof(VdvCaReference)) == 0;
0155 }
0156 
0157 QDate VdvCertificate::endOfValidity() const
0158 {
0159     const auto key = certKey();
0160     return key->date;
0161 }
0162 
0163 BER::Element VdvCertificate::header() const
0164 {
0165     return BER::Element(m_data, m_offset);
0166 }
0167 
0168 const VdvCertificateKey* VdvCertificate::certKey() const
0169 {
0170     if (m_type == Signed) {
0171         return reinterpret_cast<const VdvCertificateKey*>(m_recoveredData.constData());
0172     } else if (m_type == Raw) {
0173         return header().find(TagCertificateContent).contentAt<VdvCertificateKey>();
0174     }
0175     return nullptr;
0176 }
0177 
0178 
0179 VdvCertificate VdvPkiRepository::caCertificate(const VdvCaReference *car)
0180 {
0181   QFile f(QLatin1StringView(":/org.kde.pim/kitinerary/vdv/certs/") +
0182           QString::fromLatin1(QByteArray(reinterpret_cast<const char *>(car),
0183                                          sizeof(VdvCaReference))
0184                                   .toHex()) +
0185           QLatin1StringView(".vdv-cert"));
0186   if (!f.open(QFile::ReadOnly)) {
0187     qWarning() << "Failed to open CA cert file" << f.fileName()
0188                << f.errorString();
0189     return VdvCertificate();
0190     }
0191 
0192     VdvCertificate cert(f.readAll());
0193     if (cert.needsCaKey()) {
0194         VdvCaReference rootCAR;
0195         rootCAR.region[0] = 'E'; rootCAR.region[1] = 'U';
0196         rootCAR.name[0] = 'V'; rootCAR.name[1] = 'D'; rootCAR.name[2] = 'V';
0197         rootCAR.serviceIndicator = 0;
0198         rootCAR.discretionaryData = 1;
0199         rootCAR.algorithmReference = 1;
0200         rootCAR.year = 6;
0201         cert.setCaCertificate(caCertificate(&rootCAR));
0202     }
0203     return cert;
0204 }