KHealthCertificate

irmaverifier.cpp
1/*
2 * SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
3 * SPDX-License-Identifier: LGPL-2.0-or-later
4 */
5
6#include "irmaverifier_p.h"
7#include "irmapublickey_p.h"
8
9#include "openssl/bignum_p.h"
10
11#include <QCryptographicHash>
12#include <QDebug>
13
14#include <cstring>
15
16IrmaProof::IrmaProof() = default;
17
18bool IrmaProof::isNull() const
19{
20 return !C || !A || !EResponse || !VResponse
21 || std::any_of(AResponses.begin(), AResponses.end(), [](const auto &n) -> bool { return !n; })
22 || std::any_of(ADisclosed.begin(), ADisclosed.end(), [](const auto &n) -> bool { return !n; });
23}
24
25// see https://github.com/privacybydesign/gabi/blob/75a6590e506ce8e35b5f4f9f9823ba30e88e74a5/proofs.go#L171
26static bool checkResponseSize(const IrmaProof &proof, const IrmaPublicKey &pubKey)
27{
28 if (std::any_of(proof.AResponses.begin(), proof.AResponses.end(), [&pubKey](const auto &ares) {
29 return BN_num_bits(ares.get()) > pubKey.LmCommit();
30 })) {
31 qDebug() << "AResponse entry too large";
32 return false;
33 }
34
35 if (BN_num_bits(proof.EResponse.get()) > pubKey.LeCommit()) {
36 qDebug() << "EResponse too large";
37 return false;
38 }
39
40 return true;
41}
42
43// see https://github.com/minvws/nl-covid19-coronacheck-idemix/blob/main/common/common.go#L132
44// SHA-256 of the string representation of @p timestampe, cut of to a defined maximum length
45static openssl::bn_ptr calculateTimeBasedChallenge(int64_t timestamp)
46{
48 h.truncate(16);
49 return Bignum::fromByteArray(h);
50}
51
52// SHA-256 on the binary data of the input number, returned as a number
53static openssl::bn_ptr bignum_sha256(const openssl::bn_ptr &in)
54{
55 const auto h = QCryptographicHash::hash(Bignum::toByteArray(in), QCryptographicHash::Sha256);
56 return Bignum::fromByteArray(h);
57}
58
59// see https://github.com/privacybydesign/gabi/blob/master/proofs.go#L194
60static openssl::bn_ptr reconstructZ(const IrmaProof &proof, const IrmaPublicKey &pubKey)
61{
62 openssl::bn_ctx_ptr bnCtx(BN_CTX_new());
63
64 openssl::bn_ptr numerator(BN_new()), tmp(BN_new());
65 BN_one(numerator.get());
66 BN_lshift(tmp.get(), numerator.get(), pubKey.Le() - 1);
67 std::swap(tmp, numerator);
68
69 BN_mod_exp(tmp.get(), proof.A.get(), numerator.get(), pubKey.N.get(), bnCtx.get());
70 std::swap(tmp, numerator);
71
72 for (std::size_t i = 0; i < proof.ADisclosed.size(); ++i) {
73 openssl::bn_ptr exp;
74 if (BN_num_bits(proof.ADisclosed[i].get()) > pubKey.Lm()) {
75 exp = bignum_sha256(proof.ADisclosed[i]);
76 }
77
78 BN_mod_exp(tmp.get(), pubKey.R[i+1].get(), exp ? exp.get() : proof.ADisclosed[i].get(), pubKey.N.get(), bnCtx.get());
79 openssl::bn_ptr tmp2(BN_new());
80 BN_mul(tmp2.get(), numerator.get(), tmp.get(), bnCtx.get());
81 std::swap(tmp2, numerator);
82 }
83
84 openssl::bn_ptr known(BN_new());
85 BN_mod_inverse(known.get(), numerator.get(), pubKey.N.get(), bnCtx.get());
86 BN_mul(tmp.get(), pubKey.Z.get(), known.get(), bnCtx.get());
87 std::swap(tmp, known);
88
89 openssl::bn_ptr knownC(BN_new());
90 BN_mod_inverse(tmp.get(), known.get(), pubKey.N.get(), bnCtx.get());
91 BN_mod_exp(knownC.get(), tmp.get(), proof.C.get(), pubKey.N.get(), bnCtx.get());
92
93 openssl::bn_ptr Ae(BN_new());
94 BN_mod_exp(Ae.get(), proof.A.get(), proof.EResponse.get(), pubKey.N.get(), bnCtx.get());
95 openssl::bn_ptr Sv(BN_new());
96 BN_mod_exp(Sv.get(), pubKey.S.get(), proof.VResponse.get(), pubKey.N.get(), bnCtx.get());
97
98 openssl::bn_ptr Rs(BN_new());
99 BN_one(Rs.get());
100 for (std::size_t i = 0; i < proof.AResponses.size(); ++i) {
101 openssl::bn_ptr tmp2(BN_new());
102 BN_mod_exp(tmp2.get(), pubKey.R[i].get(), proof.AResponses[i].get(), pubKey.N.get(), bnCtx.get());
103 BN_mul(tmp.get(), Rs.get(), tmp2.get(), bnCtx.get());
104 std::swap(tmp, Rs);
105 }
106
107 openssl::bn_ptr Z(BN_new());
108 BN_mul(Z.get(), knownC.get(), Ae.get(), bnCtx.get());
109
110 BN_mul(tmp.get(), Z.get(), Rs.get(), bnCtx.get());
111 std::swap(tmp, Z);
112 BN_mod_mul(tmp.get(), Z.get(), Sv.get(), pubKey.N.get(), bnCtx.get());
113 std::swap(tmp, Z);
114 return Z;
115}
116
117// encode a sequence of arbitrary size INTEGERs into an ASN.1 SEQUENCE
118static QByteArray asn1EncodeSequence(const std::vector<const BIGNUM*> &numbers)
119{
120 QByteArray payloadBuffer;
121 for (auto number : numbers) {
122 openssl::asn1_integer_ptr num(BN_to_ASN1_INTEGER(number, nullptr));
123 openssl::asn1_type_ptr obj(ASN1_TYPE_new());
124 ASN1_TYPE_set(obj.get(), V_ASN1_INTEGER, num.release());
125
126 uint8_t *buffer = nullptr;
127 const auto size = i2d_ASN1_TYPE(obj.get(), &buffer);
128 payloadBuffer.append(reinterpret_cast<const char*>(buffer), size);
129 free(buffer);
130 }
131
132 QByteArray result;
133 result.resize(ASN1_object_size(1, payloadBuffer.size(), V_ASN1_SEQUENCE));
134 auto resultIt = reinterpret_cast<uint8_t*>(result.data());
135 ASN1_put_object(&resultIt, 1, payloadBuffer.size(), V_ASN1_SEQUENCE, 0);
136 std::memcpy(resultIt, payloadBuffer.constData(), payloadBuffer.size());
137 return result;
138}
139
140// see https://github.com/privacybydesign/gabi/blob/master/prooflist.go#L77
141bool IrmaVerifier::verify(const IrmaProof &proof, const IrmaPublicKey &pubKey)
142{
143 if (!checkResponseSize(proof, pubKey)) {
144 return false;
145 }
146
147 openssl::bn_ptr context(BN_new());
148 BN_one(context.get());
149
150 const auto timeBasedChallenge = calculateTimeBasedChallenge(proof.disclosureTime);
151 const auto Z = reconstructZ(proof, pubKey);
152
153 // create challenge: https://github.com/privacybydesign/gabi/blob/master/proofs.go#L27
154 openssl::bn_ptr numElements(BN_new());
155 BN_set_word(numElements.get(), 4);
156
157 const auto encoded = asn1EncodeSequence({numElements.get(), context.get(), proof.A.get(), Z.get(), timeBasedChallenge.get()});
158 const auto challenge = QCryptographicHash::hash(encoded, QCryptographicHash::Sha256);
159
160 const auto proofC = Bignum::toByteArray(proof.C);
161 return proofC == challenge;
162}
QByteArray & append(QByteArrayView data)
const char * constData() const const
char * data()
QByteArray number(double n, char format, int precision)
void resize(qsizetype newSize, char c)
qsizetype size() const const
QByteArray hash(QByteArrayView data, Algorithm method)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 29 2024 11:47:02 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.