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 }