KHealthCertificate

coseparser.cpp
1/*
2 * SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
3 * SPDX-License-Identifier: LGPL-2.0-or-later
4 */
5
6#include "coseparser_p.h"
7#include "cborutils_p.h"
8#include "logging.h"
9
10#include <openssl/verify_p.h>
11#include <openssl/x509loader_p.h>
12
13#include <QCborMap>
14#include <QCborStreamReader>
15#include <QCborStreamWriter>
16#include <QCborValue>
17#include <QFile>
18
19#include <openssl/bn.h>
20#include <openssl/evp.h>
21#include <openssl/err.h>
22#include <openssl/pem.h>
23
24enum {
25 CoseHeaderAlgorithm = 1,
26 CoseHeaderKid = 4
27};
28
29enum {
30 CoseAlgorithmECDSA_SHA256 = -7,
31 CoseAlgorithmECDSA_SHA384 = -35,
32 CoseAlgorithmECDSA_SHA512 = -36,
33 CoseAlgorithmRSA_PSS_256 = -37,
34 CoseAlgorithmRSA_PSS_384 = -38,
35 CoseAlgorithmRSA_PSS_512 = -39,
36};
37
38void CoseParser::parse(const QByteArray &data)
39{
40 clear();
41
42 // only single signer case implemented atm
43 QCborStreamReader reader(data);
44 if (reader.type() != QCborStreamReader::Tag || reader.toTag() != QCborKnownTags::COSE_Sign1) {
45 qCWarning(Log) << "wrong COSE tag:" << reader.toTag();
46 return;
47 }
48
49 reader.next();
50 if (!reader.isArray()) {
51 return;
52 }
53
54 reader.enterContainer();
55 m_protectedParams = CborUtils::readByteArray(reader);
56 auto params = QCborValue::fromCbor(m_protectedParams);
57 const auto algorithm = params.toMap().value(CoseHeaderAlgorithm).toInteger();
58 m_kid = params.toMap().value(CoseHeaderKid).toByteArray();
59 params = QCborValue::fromCbor(reader);
60 if (m_kid.isEmpty()) {
61 m_kid = params.toMap().value(CoseHeaderKid).toByteArray();
62 }
63 m_payload = CborUtils::readByteArray(reader);
64 m_signature = CborUtils::readByteArray(reader);
65
66 // find and load certificate
67 QFile certFile(QLatin1String(":/org.kde.khealthcertificate/eu-dgc/certs/") + QString::fromUtf8(m_kid.toHex()) + QLatin1String(".der"));
68 if (!certFile.open(QFile::ReadOnly)) {
69 qCWarning(Log) << "unable to find certificate for key id:" << m_kid.toHex();
70 m_signatureState = UnknownCertificate;
71 return;
72 }
73
74 const auto certData = certFile.readAll();
75 const auto cert = X509Loader::readFromDER(certData);
76 if (!cert) {
77 qCWarning(Log) << "failed to read X509 certificate";
78 m_signatureState = UnknownCertificate;
79 return;
80 }
81 const openssl::evp_pkey_ptr pkey(X509_get_pubkey(cert.get()));
82 if (!pkey) {
83 qCWarning(Log) << "failed to load public key";
84 m_signatureState = UnknownCertificate;
85 return;
86 }
87 m_certificate = QSslCertificate(certData, QSsl::Der);
88
89 switch (algorithm) {
90 case CoseAlgorithmECDSA_SHA256:
91 case CoseAlgorithmECDSA_SHA384:
92 case CoseAlgorithmECDSA_SHA512:
93 validateECDSA(pkey, algorithm);
94 break;
95 case CoseAlgorithmRSA_PSS_256:
96 case CoseAlgorithmRSA_PSS_384:
97 case CoseAlgorithmRSA_PSS_512:
98 validateRSAPSS(pkey, algorithm);
99 break;
100 default:
101 qCWarning(Log) << "signature algorithm not implemented yet:" << algorithm;
102 m_signatureState = UnsupportedAlgorithm;
103 return;
104 }
105}
106
107QByteArray CoseParser::payload() const
108{
109 return m_payload;
110}
111
112CoseParser::SignatureState CoseParser::signatureState() const
113{
114 return m_signatureState;
115}
116
117QSslCertificate CoseParser::certificate() const
118{
119 return m_certificate;
120}
121
122void CoseParser::clear()
123{
124 m_protectedParams.clear();
125 m_payload.clear();
126 m_signature.clear();
127 m_kid.clear();
128 m_signatureState = Unknown;
129}
130
131void CoseParser::validateECDSA(const openssl::evp_pkey_ptr &pkey, int algorithm)
132{
133 const openssl::ec_key_ptr ecKey(EVP_PKEY_get1_EC_KEY(pkey.get()));
134
135 const EVP_MD *digest = nullptr;
136 switch (algorithm) {
137 case CoseAlgorithmECDSA_SHA256:
138 digest = EVP_sha256();
139 break;
140 case CoseAlgorithmECDSA_SHA384:
141 digest = EVP_sha384();
142 break;
143 case CoseAlgorithmECDSA_SHA512:
144 digest = EVP_sha512();
145 break;
146 }
147
148 // compute hash of the signed data
149 const auto signedData = sigStructure();
150 m_signatureState = Verify::verifyECDSA(pkey, digest, signedData.constData(), signedData.size(), m_signature.constData(), m_signature.size()) ?
151 ValidSignature : InvalidSignature;
152}
153
154void CoseParser::validateRSAPSS(const openssl::evp_pkey_ptr &pkey, int algorithm)
155{
156 // compute hash of the signed data
157 const EVP_MD *digest = nullptr;
158 switch (algorithm) {
159 case CoseAlgorithmRSA_PSS_256:
160 digest = EVP_sha256();
161 break;
162 case CoseAlgorithmRSA_PSS_384:
163 digest = EVP_sha384();
164 break;
165 case CoseAlgorithmRSA_PSS_512:
166 digest = EVP_sha512();
167 break;
168 }
169
170 const auto signedData = sigStructure();
171 uint8_t digestData[EVP_MAX_MD_SIZE];
172 uint32_t digestSize = 0;
173 EVP_Digest(reinterpret_cast<const uint8_t*>(signedData.constData()), signedData.size(), digestData, &digestSize, digest, nullptr);
174
175 // verify
176 openssl::evp_pkey_ctx_ptr ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr));
177 if (!ctx || EVP_PKEY_verify_init(ctx.get()) <= 0) {
178 return;
179 }
180 if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_PSS_PADDING) <= 0 || EVP_PKEY_CTX_set_signature_md(ctx.get(), digest) <= 0) {
181 return;
182 }
183
184 const auto verifyResult = EVP_PKEY_verify(ctx.get(), reinterpret_cast<const uint8_t*>(m_signature.constData()), m_signature.size(), digestData, digestSize);
185 switch (verifyResult) {
186 case -1: // technical issue
187 m_signatureState = InvalidSignature;
188 qCWarning(Log) << "Failed to verify signature:" << ERR_error_string(ERR_get_error(), nullptr);
189 break;
190 case 0: // invalid signature
191 m_signatureState = InvalidSignature;
192 break;
193 case 1: // valid signature;
194 m_signatureState = ValidSignature;
195 break;
196 }
197}
198
199QByteArray CoseParser::sigStructure() const
200{
201 QByteArray data;
202 {
203 QCborStreamWriter writer(&data);
204 writer.startArray(4);
205 writer.append(QLatin1String("Signature1"));
206 writer.append(m_protectedParams);
207 writer.append(QByteArray());
208 writer.append(m_payload);
209 writer.endArray();
210 }
211 return data;
212}
KGuiItem clear()
QCborValue fromCbor(QCborStreamReader &reader)
QString fromUtf8(QByteArrayView str)
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.