KHealthCertificate

coseparser.cpp
1 /*
2  * SPDX-FileCopyrightText: 2021 Volker Krause <[email protected]>
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 #include "verify_p.h"
10 
11 #include <QCborMap>
12 #include <QCborStreamReader>
13 #include <QCborStreamWriter>
14 #include <QCborValue>
15 #include <QDebug>
16 #include <QFile>
17 
18 #include <openssl/bn.h>
19 #include <openssl/engine.h>
20 #include <openssl/err.h>
21 #include <openssl/pem.h>
22 
23 enum {
24  CoseHeaderAlgorithm = 1,
25  CoseHeaderKid = 4
26 };
27 
28 enum {
29  CoseAlgorithmECDSA_SHA256 = -7,
30  CoseAlgorithmECDSA_SHA384 = -35,
31  CoseAlgorithmECDSA_SHA512 = -36,
32  CoseAlgorithmRSA_PSS_256 = -37,
33  CoseAlgorithmRSA_PSS_384 = -38,
34  CoseAlgorithmRSA_PSS_512 = -39,
35 };
36 
37 void CoseParser::parse(const QByteArray &data)
38 {
39  clear();
40 
41  // only single signer case implemented atm
42  QCborStreamReader reader(data);
43  if (reader.type() != QCborStreamReader::Tag || reader.toTag() != QCborKnownTags::COSE_Sign1) {
44  qCWarning(Log) << "wrong COSE tag:" << reader.toTag();
45  return;
46  }
47 
48  reader.next();
49  if (!reader.isArray()) {
50  return;
51  }
52 
53  reader.enterContainer();
54  m_protectedParams = CborUtils::readByteArray(reader);
55  auto params = QCborValue::fromCbor(m_protectedParams);
56  const auto algorithm = params.toMap().value(CoseHeaderAlgorithm).toInteger();
57  m_kid = params.toMap().value(CoseHeaderKid).toByteArray();
58  params = QCborValue::fromCbor(reader);
59  if (m_kid.isEmpty()) {
60  m_kid = params.toMap().value(CoseHeaderKid).toByteArray();
61  }
62  m_payload = CborUtils::readByteArray(reader);
63  m_signature = CborUtils::readByteArray(reader);
64 
65  // find and load certificate
66  QFile certFile(QLatin1String(":/org.kde.khealthcertificate/eu-dgc/certs/") + QString::fromUtf8(m_kid.toHex()) + QLatin1String(".pem"));
67  if (!certFile.open(QFile::ReadOnly)) {
68  qCWarning(Log) << "unable to find certificate for key id:" << m_kid.toHex();
69  m_signatureState = UnknownCertificate;
70  return;
71  }
72 
73  const auto certData = certFile.readAll();
74  const openssl::bio_ptr bio(BIO_new_mem_buf(certData.constData(), certData.size()), &BIO_free_all);
75  const openssl::x509_ptr cert(PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr), &X509_free);
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()), &EVP_PKEY_free);
82  if (!pkey) {
83  qCWarning(Log) << "failed to load public key";
84  m_signatureState = UnknownCertificate;
85  return;
86  }
87  m_certificate = QSslCertificate(certData);
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 
107 QByteArray CoseParser::payload() const
108 {
109  return m_payload;
110 }
111 
112 CoseParser::SignatureState CoseParser::signatureState() const
113 {
114  return m_signatureState;
115 }
116 
117 QSslCertificate CoseParser::certificate() const
118 {
119  return m_certificate;
120 }
121 
122 void 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 
131 void 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()), &EC_KEY_free);
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 
154 void 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), &EVP_PKEY_CTX_free);
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 
199 QByteArray 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 }
QCborValue fromCbor(QCborStreamReader &reader)
QString fromUtf8(const char *str, int size)
QByteArray readAll()
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Mon Oct 18 2021 23:21:56 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.