File indexing completed on 2024-11-24 04:44:40

0001 /*
0002  * SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
0003  * SPDX-License-Identifier: LGPL-2.0-or-later
0004  */
0005 
0006 #include "irmaverifier_p.h"
0007 #include "irmapublickey_p.h"
0008 
0009 #include "openssl/bignum_p.h"
0010 
0011 #include <QCryptographicHash>
0012 #include <QDebug>
0013 
0014 #include <cstring>
0015 
0016 IrmaProof::IrmaProof() = default;
0017 
0018 bool IrmaProof::isNull() const
0019 {
0020     return !C || !A || !EResponse || !VResponse
0021         || std::any_of(AResponses.begin(), AResponses.end(), [](const auto &n) -> bool { return !n; })
0022         || std::any_of(ADisclosed.begin(), ADisclosed.end(), [](const auto &n) -> bool { return !n; });
0023 }
0024 
0025 // see https://github.com/privacybydesign/gabi/blob/75a6590e506ce8e35b5f4f9f9823ba30e88e74a5/proofs.go#L171
0026 static bool checkResponseSize(const IrmaProof &proof, const IrmaPublicKey &pubKey)
0027 {
0028     if (std::any_of(proof.AResponses.begin(), proof.AResponses.end(), [&pubKey](const auto &ares) {
0029         return BN_num_bits(ares.get()) > pubKey.LmCommit();
0030     })) {
0031         qDebug() << "AResponse entry too large";
0032         return false;
0033     }
0034 
0035     if (BN_num_bits(proof.EResponse.get()) > pubKey.LeCommit()) {
0036         qDebug() << "EResponse too large";
0037         return false;
0038     }
0039 
0040     return true;
0041 }
0042 
0043 // see https://github.com/minvws/nl-covid19-coronacheck-idemix/blob/main/common/common.go#L132
0044 // SHA-256 of the string representation of @p timestampe, cut of to a defined maximum length
0045 static openssl::bn_ptr calculateTimeBasedChallenge(int64_t timestamp)
0046 {
0047     auto h = QCryptographicHash::hash(QByteArray::number((qlonglong)timestamp), QCryptographicHash::Sha256);
0048     h.truncate(16);
0049     return Bignum::fromByteArray(h);
0050 }
0051 
0052 // SHA-256 on the binary data of the input number, returned as a number
0053 static openssl::bn_ptr bignum_sha256(const openssl::bn_ptr &in)
0054 {
0055     const auto h = QCryptographicHash::hash(Bignum::toByteArray(in), QCryptographicHash::Sha256);
0056     return Bignum::fromByteArray(h);
0057 }
0058 
0059 // see https://github.com/privacybydesign/gabi/blob/master/proofs.go#L194
0060 static openssl::bn_ptr reconstructZ(const IrmaProof &proof, const IrmaPublicKey &pubKey)
0061 {
0062     openssl::bn_ctx_ptr bnCtx(BN_CTX_new());
0063 
0064     openssl::bn_ptr numerator(BN_new()), tmp(BN_new());
0065     BN_one(numerator.get());
0066     BN_lshift(tmp.get(), numerator.get(), pubKey.Le() - 1);
0067     std::swap(tmp, numerator);
0068 
0069     BN_mod_exp(tmp.get(), proof.A.get(), numerator.get(), pubKey.N.get(), bnCtx.get());
0070     std::swap(tmp, numerator);
0071 
0072     for (std::size_t i = 0; i < proof.ADisclosed.size(); ++i) {
0073         openssl::bn_ptr exp;
0074         if (BN_num_bits(proof.ADisclosed[i].get()) > pubKey.Lm()) {
0075             exp = bignum_sha256(proof.ADisclosed[i]);
0076         }
0077 
0078         BN_mod_exp(tmp.get(), pubKey.R[i+1].get(), exp ? exp.get() : proof.ADisclosed[i].get(), pubKey.N.get(), bnCtx.get());
0079         openssl::bn_ptr tmp2(BN_new());
0080         BN_mul(tmp2.get(), numerator.get(), tmp.get(), bnCtx.get());
0081         std::swap(tmp2, numerator);
0082     }
0083 
0084     openssl::bn_ptr known(BN_new());
0085     BN_mod_inverse(known.get(), numerator.get(), pubKey.N.get(), bnCtx.get());
0086     BN_mul(tmp.get(), pubKey.Z.get(), known.get(), bnCtx.get());
0087     std::swap(tmp, known);
0088 
0089     openssl::bn_ptr knownC(BN_new());
0090     BN_mod_inverse(tmp.get(), known.get(), pubKey.N.get(), bnCtx.get());
0091     BN_mod_exp(knownC.get(), tmp.get(), proof.C.get(), pubKey.N.get(), bnCtx.get());
0092 
0093     openssl::bn_ptr Ae(BN_new());
0094     BN_mod_exp(Ae.get(), proof.A.get(), proof.EResponse.get(), pubKey.N.get(), bnCtx.get());
0095     openssl::bn_ptr Sv(BN_new());
0096     BN_mod_exp(Sv.get(), pubKey.S.get(), proof.VResponse.get(), pubKey.N.get(), bnCtx.get());
0097 
0098     openssl::bn_ptr Rs(BN_new());
0099     BN_one(Rs.get());
0100     for (std::size_t i = 0; i < proof.AResponses.size(); ++i) {
0101         openssl::bn_ptr tmp2(BN_new());
0102         BN_mod_exp(tmp2.get(), pubKey.R[i].get(), proof.AResponses[i].get(), pubKey.N.get(), bnCtx.get());
0103         BN_mul(tmp.get(), Rs.get(), tmp2.get(), bnCtx.get());
0104         std::swap(tmp, Rs);
0105     }
0106 
0107     openssl::bn_ptr Z(BN_new());
0108     BN_mul(Z.get(), knownC.get(), Ae.get(), bnCtx.get());
0109 
0110     BN_mul(tmp.get(), Z.get(), Rs.get(), bnCtx.get());
0111     std::swap(tmp, Z);
0112     BN_mod_mul(tmp.get(), Z.get(), Sv.get(), pubKey.N.get(), bnCtx.get());
0113     std::swap(tmp, Z);
0114     return Z;
0115 }
0116 
0117 // encode a sequence of arbitrary size INTEGERs into an ASN.1 SEQUENCE
0118 static QByteArray asn1EncodeSequence(const std::vector<const BIGNUM*> &numbers)
0119 {
0120     QByteArray payloadBuffer;
0121     for (auto number : numbers) {
0122         openssl::asn1_integer_ptr num(BN_to_ASN1_INTEGER(number, nullptr));
0123         openssl::asn1_type_ptr obj(ASN1_TYPE_new());
0124         ASN1_TYPE_set(obj.get(),  V_ASN1_INTEGER, num.release());
0125 
0126         uint8_t *buffer = nullptr;
0127         const auto size = i2d_ASN1_TYPE(obj.get(), &buffer);
0128         payloadBuffer.append(reinterpret_cast<const char*>(buffer), size);
0129         free(buffer);
0130     }
0131 
0132     QByteArray result;
0133     result.resize(ASN1_object_size(1, payloadBuffer.size(), V_ASN1_SEQUENCE));
0134     auto resultIt = reinterpret_cast<uint8_t*>(result.data());
0135     ASN1_put_object(&resultIt, 1, payloadBuffer.size(), V_ASN1_SEQUENCE, 0);
0136     std::memcpy(resultIt, payloadBuffer.constData(), payloadBuffer.size());
0137     return result;
0138 }
0139 
0140 // see https://github.com/privacybydesign/gabi/blob/master/prooflist.go#L77
0141 bool IrmaVerifier::verify(const IrmaProof &proof, const IrmaPublicKey &pubKey)
0142 {
0143     if (!checkResponseSize(proof, pubKey)) {
0144         return false;
0145     }
0146 
0147     openssl::bn_ptr context(BN_new());
0148     BN_one(context.get());
0149 
0150     const auto timeBasedChallenge = calculateTimeBasedChallenge(proof.disclosureTime);
0151     const auto Z = reconstructZ(proof, pubKey);
0152 
0153     // create challenge: https://github.com/privacybydesign/gabi/blob/master/proofs.go#L27
0154     openssl::bn_ptr numElements(BN_new());
0155     BN_set_word(numElements.get(), 4);
0156 
0157     const auto encoded = asn1EncodeSequence({numElements.get(), context.get(), proof.A.get(), Z.get(), timeBasedChallenge.get()});
0158     const auto challenge = QCryptographicHash::hash(encoded, QCryptographicHash::Sha256);
0159 
0160     const auto proofC = Bignum::toByteArray(proof.C);
0161     return proofC == challenge;
0162 }