diff --git a/src/vdv/vdvcertificate.cpp b/src/vdv/vdvcertificate.cpp index 3554ffe..3c37b32 100644 --- a/src/vdv/vdvcertificate.cpp +++ b/src/vdv/vdvcertificate.cpp @@ -1,200 +1,228 @@ /* 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 "vdvcertificate_p.h" #include "vdvdata_p.h" #include "iso9796_2decoder_p.h" #include #include #include using namespace KItinerary; VdvCertificate::VdvCertificate() = default; VdvCertificate::VdvCertificate(const QByteArray &data, int offset) : m_offset(offset) { if ((unsigned)data.size() <= m_offset + sizeof(VdvCertificateHeader)) { qDebug() << "Certificate data too small:" << data.size() << offset; return; } m_data = data; const auto hdr = header(); if (!hdr->isValid() || data.size() < hdr->size() + offset) { qDebug() << "Invalid certificate header:" << hdr->isValid() << hdr->size() << data.size() << offset; m_data.clear(); return; } const auto certKeyBlock = hdr->contentAt(0); if (certKeyBlock->isValid()) { m_type = Raw; qDebug() << "found decrypted key"; qDebug() << "CHR:" << QByteArray(certKey()->chr.name, 5) << certKey()->chr.algorithmReference << certKey()->chr.year; qDebug() << "CAR:" << QByteArray(certKey()->car.region, 2) << QByteArray(certKey()->car.name, 3); return; } const auto sig = hdr->contentAt(0); if (!sig->isValid()) { qWarning() << "Invalid certificate content: neither a key nor a signature!"; m_data.clear(); return; } m_type = Signed; qDebug() << "found encrypted key"; } VdvCertificate::~VdvCertificate() = default; bool VdvCertificate::isValid() const { if (m_type == Invalid) { return false; } return m_type == Signed ? !m_recoveredData.isEmpty() : !m_data.isEmpty(); } bool VdvCertificate::needsCaKey() const { return m_type == Signed && m_recoveredData.isEmpty(); } int VdvCertificate::size() const { return m_type == Invalid ? 0 : header()->size(); } uint16_t VdvCertificate::modulusSize() const { switch (certKey()->certificateProfileIdentifier) { case 3: return 1536 / 8; case 4: return 1024 / 8; case 7: return 1984 / 8; } qWarning() << "Unknown certificate profile identifier: " << certKey()->certificateProfileIdentifier; return 0; } const uint8_t* VdvCertificate::modulus() const { const auto k = certKey(); return (&k->oidBegin) + k->oidSize(); } uint16_t VdvCertificate::exponentSize() const { return 4; } const uint8_t* VdvCertificate::exponent() const { return modulus() + modulusSize(); } void VdvCertificate::setCaCertificate(const VdvCertificate &caCert) { if (!caCert.isValid()) { qWarning() << "Invalid CA certificate."; return; } Iso9796_2Decoder decoder; decoder.setRsaParameters(caCert.modulus(), caCert.modulusSize(), caCert.exponent(), caCert.exponentSize()); const auto sig = header()->contentAt(0); decoder.addWithRecoveredMessage(sig->contentData(), sig->contentSize()); if (header()->contentSize() > sig->size()) { const auto rem = header()->contentAt(sig->size()); if (rem->isValid() && rem->size() + sig->size() >= header()->contentSize()) { decoder.add(rem->contentData(), rem->contentSize()); } else { qWarning() << "Invalid signature remainder!" << rem->isValid() << rem->size() << sig->size() << header()->contentSize(); } } m_recoveredData = decoder.recoveredMessage(); if (!m_recoveredData.isEmpty() && m_recoveredData.size() >= (certKey()->headerSize() + modulusSize() + exponentSize())) { qDebug() << "successfully decrypted key"; qDebug() << "CAR:" << QByteArray(certKey()->car.region, 2) << QByteArray(certKey()->car.name, 3); qDebug() << "CHR:" << QByteArray(certKey()->chr.name, 5) << certKey()->chr.algorithmReference << certKey()->chr.year; } else { qWarning() << "decrypting certificate key failed!"; qDebug() << "size is:" << m_recoveredData.size() << "expected:" << (certKey()->headerSize() + modulusSize() + exponentSize()); qDebug() << QByteArray((const char*)sig->contentData(), sig->contentSize()).toHex();; m_type = Invalid; m_recoveredData.clear(); } } +static void writeTaggedSize(QIODevice *out, int size) +{ + if (size <= 255) { + out->write("\x81"); + char size1 = (uint8_t)(size); + out->write(&size1, 1); + } else { + out->write("\x82"); + uint16_t size2 = qToBigEndian((uint16_t)(size)); + out->write((const char*)&size2, 2); + } +} + +void VdvCertificate::writeKey(QIODevice *out) const +{ + out->write("\x7F\x21"); + if (m_type == Signed) { + writeTaggedSize(out, m_recoveredData.size() + 3); + out->write("\x5F\x4E"); + writeTaggedSize(out, m_recoveredData.size()); + out->write(m_recoveredData); + } else if (m_type == Raw) { + const auto keyBlock = header()->contentAt(0); + writeTaggedSize(out, keyBlock->size()); + out->write((const char*)keyBlock, keyBlock->size()); + } +} + bool VdvCertificate::isSelfSigned() const { return memcmp(&certKey()->car, certKey()->chr.name, sizeof(VdvCaReference)) == 0; } QDate KItinerary::VdvCertificate::endOfValidity() const { const auto key = certKey(); return QDate(key->date.year(), key->date.month(), key->date.day()); } const VdvCertificateHeader* VdvCertificate::header() const { return reinterpret_cast(m_data.constData() + m_offset); } const VdvCertificateKey* VdvCertificate::certKey() const { if (m_type == Signed) { return reinterpret_cast(m_recoveredData.constData()); } else if (m_type == Raw) { return header()->contentAt(0)->contentAt(0); } return nullptr; } VdvCertificate VdvPkiRepository::caCertificate(const VdvCaReference *car) { QFile f(QLatin1String(":/org.kde.pim/kitinerary/vdv/certs/") + QString::fromLatin1(QByteArray(reinterpret_cast(car), sizeof(VdvCaReference)).toHex()) + QLatin1String(".vdv-cert")); if (!f.open(QFile::ReadOnly)) { qWarning() << "Failed to open CA cert file" << f.fileName() << f.errorString(); return VdvCertificate(); } VdvCertificate cert(f.readAll()); if (cert.needsCaKey()) { VdvCaReference rootCAR; rootCAR.region[0] = 'E'; rootCAR.region[1] = 'U'; rootCAR.name[0] = 'V'; rootCAR.name[1] = 'D'; rootCAR.name[2] = 'V'; rootCAR.serviceIndicator = 0; rootCAR.discretionaryData = 1; rootCAR.algorithmReference = 1; rootCAR.year = 6; cert.setCaCertificate(caCertificate(&rootCAR)); } return cert; } diff --git a/src/vdv/vdvcertificate_p.h b/src/vdv/vdvcertificate_p.h index 17627e6..562ef6e 100644 --- a/src/vdv/vdvcertificate_p.h +++ b/src/vdv/vdvcertificate_p.h @@ -1,91 +1,95 @@ /* 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 . */ #ifndef KITINERARY_VDVCERTIFICATE_H #define KITINERARY_VDVCERTIFICATE_H #include class QDate; +class QIODevice; namespace KItinerary { struct VdvCaReference; struct VdvCertificateHeader; struct VdvCertificateKey; /** Certificate object, to obtain the RSA parameters. * This can be both a raw certificate which can be directly consumed, * or one with an ISO 9796-2 signature with message recovery. In the latter * case you need to provide the key of the corresponding CA certificate * for decoding too. */ class VdvCertificate { public: VdvCertificate(); explicit VdvCertificate(const QByteArray &data, int offset = 0); ~VdvCertificate(); bool isValid() const; bool needsCaKey() const; /** Size of the entire encoded certificate data. */ int size() const; /** Amount of bytes in the RSA modulus. */ uint16_t modulusSize() const; /** RSA modulus. */ const uint8_t* modulus() const; /** Amount of bytes in the RSA exponent. */ uint16_t exponentSize() const; /** RSA exponent. */ const uint8_t* exponent() const; /** Sets the CA certificate for decoding ISO 9796-2 signed certificates. */ void setCaCertificate(const VdvCertificate &caCert); + /** Write the key to @p out, in ISO 9796-2 format, without signatures. */ + void writeKey(QIODevice *out) const; + /** Returns whether this is a self-signed (== root) certificate. */ bool isSelfSigned() const; /** Returns the date this certificate expires. */ QDate endOfValidity() const; private: const VdvCertificateHeader *header() const; const VdvCertificateKey *certKey() const; QByteArray m_data; QByteArray m_recoveredData; int m_offset = 0; enum CertificateType { Invalid, Raw, Signed } m_type = Invalid; }; /** VDV (sub)CA certificate access. */ namespace VdvPkiRepository { /** Returns the (sub)CA certificate for the given CA Reference (CAR). */ VdvCertificate caCertificate(const VdvCaReference *car); } } #endif // KITINERARY_VDVCERTIFICATE_H