Commit b1256fdb authored by Volker Krause's avatar Volker Krause
Browse files

Complete the ISO 9796-2 decoding

In particular:
- use the full CAR for identifying (sub)CA certs
- support (sub)CA in ISO 9796-2 formats, not just raw ones
- decode signed (sub)CA certificates before using them
- decode the ticket data, now that we have the decoded CV cert key
parent db3fa92e
......@@ -88,8 +88,7 @@ if (TARGET PhoneNumber::PhoneNumber)
set(HAVE_PHONENUMBER ON)
endif()
if (TARGET OpenSSL::Crypto)
# TODO breaks FreeBSD build due to not finding the ssl/rsa.h include
#set(HAVE_OPENSSL_RSA ON)
set(HAVE_OPENSSL_RSA ON)
endif()
add_definitions(-DTRANSLATION_DOMAIN=\"kitinerary\")
......
The two CA certs in here are manually retrieved from:
ldap://ldap-vdv-ion.telesec.de:389/cn=4445564456110506,ou=VDV%20KA,o=VDV%20Kernapplikations%20GmbH,c=de
ldap://ldap-vdv-ion.telesec.de:389/cn=4445564456110706,ou=VDV%20KA,o=VDV%20Kernapplikations%20GmbH,c=de
The CA certs in here are manually retrieved from:
ldap://ldap-vdv-ion.telesec.de:389/cn=<CAR>,ou=VDV%20KA,o=VDV%20Kernapplikations%20GmbH,c=de
TODO: do this automatically for all relevant certs
<RCC>
<qresource prefix="/org.kde.pim/kitinerary/vdv/certs">
<file>2.vdv-cert</file>
<file>8.vdv-cert</file>
<file>4445564456110216.vdv-cert</file>
<file>4445564456110506.vdv-cert</file>
<file>4445564456110706.vdv-cert</file>
<file>4445564456110811.vdv-cert</file>
<file>4445564456110816.vdv-cert</file>
<file>4555564456100106.vdv-cert</file>
</qresource>
</RCC>
......@@ -20,7 +20,8 @@
#include <QDebug>
#ifdef HAVE_OPENSSL_RSA
#include <ssl/bn.h>
#include <openssl/bn.h>
#include <openssl/err.h>
#endif
using namespace KItinerary;
......@@ -54,8 +55,18 @@ void Iso9796_2Decoder::addWithRecoveredMessage(const uint8_t *data, int size)
QByteArray out;
out.resize(RSA_size(m_rsa.get()));
const auto outSize = RSA_public_decrypt(size, data, (uint8_t*)out.data(), m_rsa.get(), RSA_NO_PADDING);
if (outSize < 0) {
qWarning() << "RSA error:" << ERR_error_string(ERR_get_error(), nullptr);
return;
}
out.resize(outSize);
qDebug() << outSize << out.toHex();
if ((uint8_t)out[0] != 0x6a || (uint8_t)out[out.size() - 1] != 0xbc || out.size() < 22) { // 20 byte SHA-1 + padding/trailer
qWarning() << "RSA message recovery failed:" << out.toHex() << outSize;
return;
}
m_recoveredMsg.append(out.constData() + 1, out.size() - 22);
#else
Q_UNUSED(data);
Q_UNUSED(size);
......@@ -64,15 +75,13 @@ void Iso9796_2Decoder::addWithRecoveredMessage(const uint8_t *data, int size)
void Iso9796_2Decoder::add(const uint8_t *data, int size)
{
#ifdef HAVE_OPENSSL_RSA
// TODO
#else
Q_UNUSED(data);
Q_UNUSED(size);
#endif
if (m_recoveredMsg.isEmpty()) { // previous failure
return;
}
m_recoveredMsg.append((const char*)data, size);
}
QByteArray Iso9796_2Decoder::recoveredMessage() const
{
return {}; // TODO
return m_recoveredMsg;
}
......@@ -23,7 +23,7 @@
#include <QByteArray>
#ifdef HAVE_OPENSSL_RSA
#include <ssl/rsa.h>
#include <openssl/rsa.h>
#endif
#include <cstdint>
......@@ -53,6 +53,7 @@ private:
#ifdef HAVE_OPENSSL_RSA
std::unique_ptr<RSA, void(*)(RSA*)> m_rsa;
#endif
QByteArray m_recoveredMsg;
};
}
......
......@@ -17,6 +17,7 @@
#include "vdvcertificate_p.h"
#include "vdvdata_p.h"
#include "iso9796_2decoder_p.h"
#include <QDebug>
#include <QFile>
......@@ -41,24 +42,46 @@ VdvCertificate::VdvCertificate(const QByteArray &data, int offset)
return;
}
const auto certKeyBlock = hdr->contentAt<VdvCertificateKeyBlock>(0);
if (!certKeyBlock->isValid()) {
qWarning() << "Invalid certificate key block.";
if (certKeyBlock->isValid()) {
m_type = Raw;
qDebug() << "found 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;
qDebug() << "cha:" << QByteArray(certKey()->cha.name, 6);
qDebug() << "modulus:" << modulusSize() << *modulus() << *(modulus() + modulusSize() - 1) << (modulus() - (const uint8_t*)certKey());
qDebug() << "exponent:" << exponentSize() << *exponent() << *(exponent() + exponentSize() - 1) << (exponent() - (const uint8_t*)certKey());
return;
}
const auto sig = hdr->contentAt<VdvCertificateSignature>(0);
if (!sig->isValid()) {
qWarning() << "Invalid certificate content: neither a key nor a signature!";
m_data.clear();
return;
}
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;
qDebug() << "cha:" << QByteArray(certKey()->cha.name, 6);
qDebug() << "modulus:" << modulusSize() << *modulus() << *(modulus() + modulusSize() - 1);
qDebug() << "exponent:" << exponentSize() << *exponent() << *(exponent() + exponentSize() - 1);
m_type = Signed;
qDebug() << "found encrypted key";
}
VdvCertificate::~VdvCertificate() = default;
bool VdvCertificate::isValid() const
{
return !m_data.isEmpty();
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
......@@ -68,6 +91,8 @@ uint16_t VdvCertificate::modulusSize() const
return 1536 / 8;
case 4:
return 1024 / 8;
case 7:
return 1984 / 8;
}
qWarning() << "Unknown certificate profile identifier: " << certKey()->certificateProfileIdentifier;
return 0;
......@@ -75,7 +100,8 @@ uint16_t VdvCertificate::modulusSize() const
const uint8_t* VdvCertificate::modulus() const
{
return &(certKey()->modulusBegin);
const auto k = certKey();
return (&k->oidBegin) + k->oidSize();
}
uint16_t VdvCertificate::exponentSize() const
......@@ -85,7 +111,50 @@ uint16_t VdvCertificate::exponentSize() const
const uint8_t* VdvCertificate::exponent() const
{
return &(certKey()->modulusBegin) + modulusSize();
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<VdvCertificateSignature>(0);
decoder.addWithRecoveredMessage(sig->contentData(), sig->contentSize());
if (header()->contentSize() > sig->size()) {
const auto rem = header()->contentAt<VdvCertificateSignatureRemainder>(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();
}
qDebug() << rem->isValid() << rem->contentOffset() << rem->contentSize();
}
m_recoveredData = decoder.recoveredMessage();
qDebug() << m_recoveredData.toHex() << m_recoveredData.size();
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;
qDebug() << "cha:" << QByteArray(certKey()->cha.name, 6);
qDebug() << "modulus:" << modulusSize() << *modulus() << *(modulus() + modulusSize() - 1) << (modulus() - (const uint8_t*)certKey());
qDebug() << "exponent:" << exponentSize() << *exponent() << *(exponent() + exponentSize() - 1) << (exponent() - (const uint8_t*)certKey());
} else {
qWarning() << "decrypting certificate key failed!";
qDebug() << "size is:" << m_recoveredData.size() << "expected:" << (certKey()->headerSize() + modulusSize() + exponentSize());
qDebug() << QByteArray((const char*)caCert.modulus(), caCert.modulusSize()).toHex();
qDebug() << QByteArray((const char*)caCert.exponent(), caCert.exponentSize()).toHex();
qDebug() << QByteArray((const char*)sig->contentData(), sig->contentSize()).toHex();;
m_type = Invalid;
m_recoveredData.clear();
}
}
const VdvCertificateHeader* VdvCertificate::header() const
......@@ -95,19 +164,35 @@ const VdvCertificateHeader* VdvCertificate::header() const
const VdvCertificateKey* VdvCertificate::certKey() const
{
// TODO check if m_data is large enough
return header()->contentAt<VdvCertificateKeyBlock>(0)->contentAt<VdvCertificateKey>(0);
if (m_type == Signed) {
return reinterpret_cast<const VdvCertificateKey*>(m_recoveredData.constData());
} else if (m_type == Raw) {
return header()->contentAt<VdvCertificateKeyBlock>(0)->contentAt<VdvCertificateKey>(0);
}
return nullptr;
}
VdvCertificate VdvPkiRepository::caCertificate(uint8_t serNum)
VdvCertificate VdvPkiRepository::caCertificate(const VdvCaReference *car)
{
QFile f(QLatin1String(":/org.kde.pim/kitinerary/vdv/certs/") + QString::number(serNum) + QLatin1String(".vdv-cert"));
QFile f(QLatin1String(":/org.kde.pim/kitinerary/vdv/certs/")
+ QString::fromLatin1(QByteArray(reinterpret_cast<const char*>(car), sizeof(VdvCaReference)).toHex())
+ QLatin1String(".vdv-cert"));
if (!f.open(QFile::ReadOnly)) {
qWarning() << "Failed to open CA cert file" << serNum << f.errorString();
qWarning() << "Failed to open CA cert file" << f.fileName() << f.errorString();
return VdvCertificate();
}
qDebug() << f.size();
return VdvCertificate(f.readAll());
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;
}
......@@ -22,10 +22,16 @@
namespace KItinerary {
struct VdvCaReference;
struct VdvCertificateHeader;
struct VdvCertificateKey;
/** Certificate object, to obtain the RSA parameters. */
/** 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:
......@@ -34,6 +40,10 @@ public:
~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;
......@@ -45,19 +55,28 @@ public:
/** RSA exponent. */
const uint8_t* exponent() const;
/** Sets the CA certificate for decoding ISO 9796-2 signed certificates. */
void setCaCertificate(const VdvCertificate &caCert);
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 serial number. */
VdvCertificate caCertificate(uint8_t serNum);
/** Returns the (sub)CA certificate for the given CA Reference (CAR). */
VdvCertificate caCertificate(const VdvCaReference *car);
}
}
......
......@@ -178,8 +178,17 @@ struct VdvCertificateKey {
VdvCertificateHolderReference chr;
VdvCertificateHolderAuthorization cha;
uint8_t date[4];
uint8_t oid[9];
uint8_t modulusBegin;
uint8_t oidBegin;
inline uint8_t oidSize() const
{
return oidBegin == 0x2a ? 9 : 7; // ugly, but works for now
}
inline uint8_t headerSize() const
{
return sizeof(VdvCertificateKey) + oidSize() - 1;
}
};
struct VdvCertificateKeyBlock : public VdvTaggedSizeDataBlock<uint16_t, TagCertificateContent> {};
......
......@@ -45,14 +45,13 @@ void VdvTicketParser::parse(const QByteArray &data)
qDebug() << sigRemainder->contentSize();
const auto cvCertOffset = VdvSignatureRemainder::Offset + sigRemainder->size();
const auto cvCert = reinterpret_cast<const VdvCertificateHeader*>(data.constData() + cvCertOffset);
if (!cvCert->isValid() || cvCertOffset + cvCert->size() + sizeof(VdvCaReferenceBlock) > (unsigned)data.size()) {
qWarning() << "Invalid CV signature:" << cvCert->isValid() << cvCertOffset << cvCert->size();
auto cvCert = VdvCertificate(data ,cvCertOffset);
if ((!cvCert.isValid() && !cvCert.needsCaKey()) || cvCertOffset + cvCert.size() + sizeof(VdvCaReferenceBlock) > (unsigned)data.size()) {
qWarning() << "Invalid CV signature:" << cvCert.isValid() << cvCertOffset << cvCert.size();
return;
}
qDebug() << cvCert->contentSize();
const auto carOffset = cvCertOffset + cvCert->size();
const auto carOffset = cvCertOffset + cvCert.size();
const auto carBlock = reinterpret_cast<const VdvCaReferenceBlock*>(data.constData() + carOffset);
if (!carBlock->isValid() || carBlock->contentSize() < sizeof(VdvCaReference)) {
qWarning() << "Invalid CA Reference.";
......@@ -61,40 +60,31 @@ void VdvTicketParser::parse(const QByteArray &data)
const auto car = carBlock->contentAt<VdvCaReference>(0);
qDebug() << QByteArray(car->name, 3) << car->serviceIndicator << car->discretionaryData << car->algorithmReference << car->year;
const auto caCert = VdvPkiRepository::caCertificate(car->algorithmReference);
const auto caCert = VdvPkiRepository::caCertificate(car);
if (!caCert.isValid()) {
qWarning() << "Could not find CA certificate" << car->algorithmReference;
qWarning() << "Could not find CA certificate" << QByteArray(reinterpret_cast<const char*>(car), sizeof(VdvCaReference)).toHex();
return;
}
// (2) decode the CV certificate
const auto cvSig = cvCert->contentAt<VdvCertificateSignature>(0);
if (!cvSig->isValid()) {
qWarning() << "Invalid CV certificate signature structure.";
return;
}
qDebug() << cvCert->contentSize() << cvSig->size() << (uint8_t)*(cvCert->contentData() + cvSig->size());
const auto cvRem = cvCert->contentAt<VdvCertificateSignatureRemainder>(cvSig->size());
if (!cvRem->isValid()) {
qWarning() << "Invalid CV certificate signature remainder structure.";
return;
}
qDebug() << cvSig->contentSize() << cvRem->contentSize();
Iso9796_2Decoder cvDecoder;
cvDecoder.setRsaParameters(caCert.modulus(), caCert.modulusSize(), caCert.exponent(), caCert.exponentSize());
cvDecoder.addWithRecoveredMessage(cvSig->contentData(), cvSig->contentSize());
cvDecoder.add(cvRem->contentData(), cvRem->contentSize());
const auto cvDecoded = cvDecoder.recoveredMessage();
if (cvDecoded.isEmpty()) {
cvCert.setCaCertificate(caCert);
if (!cvCert.isValid()) {
qDebug() << "Failed to decode CV certificate.";
return;
}
// (3) decode the ticket data using the decoded CV certificate
// TODO
const auto sig = reinterpret_cast<const VdvSignature*>(data.constData());
qDebug() << sig->isValid() << sig->contentSize();
Iso9796_2Decoder decoder;
decoder.setRsaParameters(cvCert.modulus(), cvCert.modulusSize(), cvCert.exponent(), cvCert.exponentSize());
decoder.addWithRecoveredMessage(sig->contentData(), sig->contentSize());
decoder.add(sigRemainder->contentData(), sigRemainder->contentSize());
// (4) profit!
qDebug() << decoder.recoveredMessage();
qDebug() << decoder.recoveredMessage().toHex();
// TODO
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment