Vanetza
Loading...
Searching...
No Matches
hybrid_certificate.cpp
1#include <vanetza/security/pqc/hybrid_certificate.hpp>
2#include <vanetza/asn1/asn1c_wrapper.hpp>
3#include <vanetza/asn1/security_profile.hpp>
4#include VANETZA_ASN1_SECURITY_HEADER(PublicVerificationKey.h)
5#include VANETZA_ASN1_SECURITY_HEADER(Signature.h)
6#include VANETZA_ASN1_SECURITY_HEADER(ToBeSignedCertificate.h)
7#include <vanetza/security/backend.hpp>
8#include <vanetza/security/private_key.hpp>
9#include <vanetza/security/v3/asn1_conversions.hpp>
10#include <vanetza/security/v3/certificate.hpp>
11#include <vanetza/security/v3/hash.hpp>
12#include <limits>
13#include <stdexcept>
14#include <utility>
15
16namespace vanetza
17{
18namespace security
19{
20namespace pqc
21{
22
23using v3::Certificate;
24using v3::CertificateView;
25
26namespace
27{
28
29boost::optional<Certificate> copy_certificate(const CertificateView& view)
30{
31 try {
32 Certificate certificate;
33 if (certificate.decode(view.encode())) {
34 return certificate;
35 }
36 } catch (const std::exception&) {
37 // A malformed or empty view cannot expose hybrid certificate data.
38 }
39 return boost::none;
40}
41
42Certificate copy_certificate_or_throw(const CertificateView& view)
43{
44 auto certificate = copy_certificate(view);
45 if (!certificate) {
46 throw std::invalid_argument("certificate cannot be encoded and decoded");
47 }
48 return std::move(*certificate);
49}
50
51ByteBuffer copy_octets(const OCTET_STRING_t& value)
52{
53 if (!value.buf || value.size == 0) {
54 return {};
55 }
56 return ByteBuffer(value.buf, value.buf + value.size);
57}
58
59boost::optional<PublicKey> extract_alternative_public_key(const Certificate& certificate)
60{
61 const auto* key = certificate->toBeSigned.altVerificationKey;
62 if (!key || key->present != Vanetza_Security_PublicVerificationKey_PR_fnDsa512) {
63 return boost::none;
64 }
65
66 PublicKey result { copy_octets(key->choice.fnDsa512) };
67 return result.bytes.size() == fndsa512_public_key_size ?
68 boost::optional<PublicKey> { std::move(result) } : boost::none;
69}
70
71boost::optional<Signature> extract_alternative_signature(const Certificate& certificate)
72{
73 const auto* signature = certificate->toBeSigned.altSignatureValue;
74 if (!signature || signature->present != Vanetza_Security_Signature_PR_fnDsa512Signature) {
75 return boost::none;
76 }
77
78 Signature result { copy_octets(signature->choice.fnDsa512Signature) };
79 return result.bytes.size() == fndsa512_signature_size ?
80 boost::optional<Signature> { std::move(result) } : boost::none;
81}
82
83void assign_octets(OCTET_STRING_t& destination, const ByteBuffer& source)
84{
85 if (source.size() > static_cast<std::size_t>(std::numeric_limits<int>::max()) ||
86 OCTET_STRING_fromBuf(&destination,
87 reinterpret_cast<const char*>(source.data()),
88 static_cast<int>(source.size())) != 0) {
89 throw std::runtime_error("cannot allocate ASN.1 octet string");
90 }
91}
92
93void set_primary_signature(
94 Certificate& certificate, const ::vanetza::security::Signature& signature)
95{
96 if (signature.r.size() != key_length(signature.type) ||
97 signature.s.size() != key_length(signature.type)) {
98 throw std::invalid_argument("ECC certificate signature has an invalid size");
99 }
100
101 if (certificate->signature) {
102 vanetza::asn1::free(asn_DEF_Vanetza_Security_Signature, certificate->signature);
103 }
104 certificate->signature = vanetza::asn1::allocate<v3::asn1::Signature>();
105
106 v3::asn1::EccP256CurvePoint* r256 = nullptr;
107 v3::asn1::EccP384CurvePoint* r384 = nullptr;
108 OCTET_STRING_t* s = nullptr;
109 switch (signature.type) {
110 case KeyType::NistP256:
111 certificate->signature->present = Vanetza_Security_Signature_PR_ecdsaNistP256Signature;
112 r256 = &certificate->signature->choice.ecdsaNistP256Signature.rSig;
113 s = &certificate->signature->choice.ecdsaNistP256Signature.sSig;
114 break;
115 case KeyType::BrainpoolP256r1:
116 certificate->signature->present = Vanetza_Security_Signature_PR_ecdsaBrainpoolP256r1Signature;
117 r256 = &certificate->signature->choice.ecdsaBrainpoolP256r1Signature.rSig;
118 s = &certificate->signature->choice.ecdsaBrainpoolP256r1Signature.sSig;
119 break;
120 case KeyType::BrainpoolP384r1:
121 certificate->signature->present = Vanetza_Security_Signature_PR_ecdsaBrainpoolP384r1Signature;
122 r384 = &certificate->signature->choice.ecdsaBrainpoolP384r1Signature.rSig;
123 s = &certificate->signature->choice.ecdsaBrainpoolP384r1Signature.sSig;
124 break;
125 default:
126 throw std::invalid_argument("unsupported ECC certificate signature type");
127 }
128
129 if (r256) {
130 r256->present = Vanetza_Security_EccP256CurvePoint_PR_x_only;
131 assign_octets(r256->choice.x_only, signature.r);
132 } else {
133 r384->present = Vanetza_Security_EccP384CurvePoint_PR_x_only;
134 assign_octets(r384->choice.x_only, signature.r);
135 }
136 assign_octets(*s, signature.s);
137}
138
139const CertificateView& signing_certificate(
140 const CertificateView& subject, const CertificateView* issuer)
141{
142 if (subject.issuer_is_self()) {
143 return subject;
144 }
145 if (!issuer) {
146 throw std::invalid_argument("issuer certificate is required for a non-self-signed certificate");
147 }
148 return *issuer;
149}
150
151ByteBuffer canonical_tbs(const CertificateView& subject, SignatureLayer layer)
152{
153 Certificate certificate = copy_certificate_or_throw(subject);
154 if (layer == SignatureLayer::Alternative) {
155 clear_alternative_signature(certificate);
156 }
157
158 auto canonical = certificate.canonicalize();
159 if (!canonical) {
160 throw std::invalid_argument("certificate cannot be canonicalized");
161 }
162
163 return vanetza::asn1::encode_oer(
164 asn_DEF_Vanetza_Security_ToBeSignedCertificate, &canonical->content()->toBeSigned);
165}
166
167ByteBuffer canonical_issuer(const CertificateView& subject, const CertificateView* issuer)
168{
169 if (subject.issuer_is_self()) {
170 return {};
171 }
172 if (!issuer) {
173 throw std::invalid_argument("issuer certificate is required for a non-self-signed certificate");
174 }
175
176 const auto expected_digest = subject.issuer_digest();
177 const auto actual_digest = issuer->calculate_digest();
178 if (!expected_digest || !actual_digest || *expected_digest != *actual_digest) {
179 throw std::invalid_argument("issuer certificate does not match the subject issuer identifier");
180 }
181
182 auto canonical = issuer->canonicalize();
183 if (!canonical) {
184 throw std::invalid_argument("issuer certificate cannot be canonicalized");
185 }
186 return canonical->encode();
187}
188
189} // namespace
190
191boost::optional<PublicKey> get_alternative_public_key(const CertificateView& view)
192{
193 auto certificate = copy_certificate(view);
194 return certificate ? extract_alternative_public_key(*certificate) : boost::none;
195}
196
197boost::optional<Signature> get_alternative_signature(const CertificateView& view)
198{
199 auto certificate = copy_certificate(view);
200 return certificate ? extract_alternative_signature(*certificate) : boost::none;
201}
202
203MaterialState alternative_material_state(const CertificateView& view)
204{
205 auto certificate = copy_certificate(view);
206 if (!certificate) {
207 return MaterialState::Inconsistent;
208 }
209
210 const bool key_present = certificate->content()->toBeSigned.altVerificationKey;
211 const bool signature_present = certificate->content()->toBeSigned.altSignatureValue;
212 const bool has_key = static_cast<bool>(extract_alternative_public_key(*certificate));
213 const bool has_signature = static_cast<bool>(extract_alternative_signature(*certificate));
214 if (!key_present && !signature_present) {
215 return MaterialState::None;
216 }
217 if (key_present != has_key || signature_present != has_signature) {
218 return MaterialState::Inconsistent;
219 }
220 if (view.is_at_certificate()) {
221 return !has_key && has_signature ? MaterialState::EndEntity : MaterialState::Inconsistent;
222 }
224 return has_key && has_signature ? MaterialState::Authority : MaterialState::Inconsistent;
225 }
226 return MaterialState::Inconsistent;
227}
228
229void set_alternative_public_key(Certificate& certificate, const PublicKey& key)
230{
231 if (key.bytes.size() != fndsa512_public_key_size) {
232 throw std::invalid_argument("FN-DSA-512 public key has an invalid size");
233 }
234 clear_alternative_public_key(certificate);
235 certificate->toBeSigned.altVerificationKey =
236 vanetza::asn1::allocate<v3::asn1::PublicVerificationKey>();
237 certificate->toBeSigned.altVerificationKey->present =
238 Vanetza_Security_PublicVerificationKey_PR_fnDsa512;
239 assign_octets(certificate->toBeSigned.altVerificationKey->choice.fnDsa512, key.bytes);
240}
241
242void set_alternative_signature(Certificate& certificate, const Signature& signature)
243{
244 if (signature.bytes.size() != fndsa512_signature_size) {
245 throw std::invalid_argument("FN-DSA-512 signature has an invalid size");
246 }
247 clear_alternative_signature(certificate);
248 certificate->toBeSigned.altSignatureValue =
249 vanetza::asn1::allocate<v3::asn1::Signature>();
250 certificate->toBeSigned.altSignatureValue->present =
251 Vanetza_Security_Signature_PR_fnDsa512Signature;
252 assign_octets(
253 certificate->toBeSigned.altSignatureValue->choice.fnDsa512Signature,
254 signature.bytes);
255}
256
257void clear_alternative_public_key(Certificate& certificate)
258{
259 if (certificate->toBeSigned.altVerificationKey) {
260 vanetza::asn1::free(
261 asn_DEF_Vanetza_Security_PublicVerificationKey,
262 certificate->toBeSigned.altVerificationKey);
263 certificate->toBeSigned.altVerificationKey = nullptr;
264 }
265}
266
267void clear_alternative_signature(Certificate& certificate)
268{
269 if (certificate->toBeSigned.altSignatureValue) {
270 vanetza::asn1::free(
271 asn_DEF_Vanetza_Security_Signature,
272 certificate->toBeSigned.altSignatureValue);
273 certificate->toBeSigned.altSignatureValue = nullptr;
274 }
275}
276
277ByteBuffer calculate_certificate_hash(
278 ::vanetza::security::Backend& backend, HashAlgorithm algorithm,
279 const CertificateView& subject,
280 const CertificateView* issuer, SignatureLayer layer)
281{
282 if (algorithm == HashAlgorithm::Unspecified) {
283 throw std::invalid_argument("certificate hash algorithm is unspecified");
284 }
285
286 const ByteBuffer data_input = canonical_tbs(subject, layer);
287 const ByteBuffer signer_input = canonical_issuer(subject, issuer);
288 const ByteBuffer data_hash = backend.calculate_hash(algorithm, data_input);
289 const ByteBuffer signer_hash = backend.calculate_hash(algorithm, signer_input);
290
291 ByteBuffer concatenated;
292 concatenated.reserve(data_hash.size() + signer_hash.size());
293 concatenated.insert(concatenated.end(), data_hash.begin(), data_hash.end());
294 concatenated.insert(concatenated.end(), signer_hash.begin(), signer_hash.end());
295 return backend.calculate_hash(algorithm, concatenated);
296}
297
298void sign_primary_certificate(
299 Certificate& subject, const CertificateView* issuer,
300 ::vanetza::security::Backend& backend,
301 const ::vanetza::security::PrivateKey& issuer_key)
302{
303 const CertificateView& signer = signing_certificate(subject, issuer);
304 const auto public_key = v3::get_public_key(*copy_certificate_or_throw(signer).content());
305 if (!public_key || public_key->type != issuer_key.type) {
306 throw std::invalid_argument("ECC private key does not match the issuer certificate key type");
307 }
308
309 const HashAlgorithm algorithm = v3::specified_hash_algorithm(issuer_key.type);
310 const ByteBuffer digest = calculate_certificate_hash(
311 backend, algorithm, subject, issuer, SignatureLayer::Primary);
312 const auto signature = backend.sign_digest(issuer_key, digest);
313 if (!backend.verify_digest(*public_key, digest, signature)) {
314 throw std::invalid_argument(
315 "ECC private key does not match the issuer certificate public key");
316 }
317 set_primary_signature(subject, signature);
318}
319
320bool verify_primary_certificate(
321 const CertificateView& subject, const CertificateView* issuer,
322 ::vanetza::security::Backend& backend)
323{
324 try {
325 const CertificateView& signer = signing_certificate(subject, issuer);
326 Certificate signer_copy = copy_certificate_or_throw(signer);
327 Certificate subject_copy = copy_certificate_or_throw(subject);
328 const auto public_key = v3::get_public_key(*signer_copy.content());
329 const auto signature = v3::get_signature(*subject_copy.content());
330 if (!public_key || !signature || public_key->type != signature->type) {
331 return false;
332 }
333
334 const HashAlgorithm algorithm = v3::specified_hash_algorithm(public_key->type);
335 const ByteBuffer digest = calculate_certificate_hash(
336 backend, algorithm, subject, issuer, SignatureLayer::Primary);
337 return backend.verify_digest(*public_key, digest, *signature);
338 } catch (const std::exception&) {
339 return false;
340 }
341}
342
343void sign_alternative_certificate(
344 Certificate& subject, const CertificateView* issuer,
345 ::vanetza::security::Backend& hash_backend, Backend& backend,
346 const PrivateKey& issuer_key)
347{
348 const CertificateView& signer = signing_certificate(subject, issuer);
349 const auto public_key = get_alternative_public_key(signer);
350 if (!public_key) {
351 throw std::invalid_argument("issuer certificate has no FN-DSA-512 alternative key");
352 }
353
354 const ByteBuffer digest = calculate_certificate_hash(
355 hash_backend, HashAlgorithm::SHA256, subject, issuer, SignatureLayer::Alternative);
356 const auto signature = backend.sign(issuer_key, digest);
357 if (!backend.verify(*public_key, digest, signature)) {
358 throw std::invalid_argument(
359 "FN-DSA-512 private key does not match the issuer certificate public key");
360 }
361 set_alternative_signature(subject, signature);
362}
363
364bool verify_alternative_certificate(
365 const CertificateView& subject, const CertificateView* issuer,
366 ::vanetza::security::Backend& hash_backend, Backend& backend)
367{
368 try {
369 const CertificateView& signer = signing_certificate(subject, issuer);
370 const auto public_key = get_alternative_public_key(signer);
371 const auto signature = get_alternative_signature(subject);
372 if (!public_key || !signature) {
373 return false;
374 }
375
376 const ByteBuffer digest = calculate_certificate_hash(
377 hash_backend, HashAlgorithm::SHA256, subject, issuer, SignatureLayer::Alternative);
378 return backend.verify(*public_key, digest, *signature);
379 } catch (const std::exception&) {
380 return false;
381 }
382}
383
384} // namespace pqc
385} // namespace security
386} // namespace vanetza
virtual Signature sign_digest(const PrivateKey &, const ByteBuffer &digest)=0
calculate signature for given digest and private key
virtual bool verify_digest(const PublicKey &public_key, const ByteBuffer &digest, const Signature &sig)=0
try to verify digest using public key and signature
virtual ByteBuffer calculate_hash(HashAlgorithm algo, const ByteBuffer &data)=0
calculate hash value of data
boost::optional< Certificate > canonicalize() const
boost::optional< HashedId8 > calculate_digest() const
boost::optional< HashedId8 > issuer_digest() const