Vanetza
Loading...
Searching...
No Matches
straight_verify_service.cpp
1#include <vanetza/common/its_aid.hpp>
2#include <vanetza/common/position_provider.hpp>
3#include <vanetza/common/runtime.hpp>
4#include <vanetza/security/backend.hpp>
5#include <vanetza/security/public_key.hpp>
6#include <vanetza/security/straight_verify_service.hpp>
7#include <vanetza/security/v2/basic_elements.hpp>
8#include <vanetza/security/v2/certificate_cache.hpp>
9#include <vanetza/security/v2/certificate_provider.hpp>
10#include <vanetza/security/v2/certificate_validator.hpp>
11#include <vanetza/security/v2/sign_header_policy.hpp>
12#include <vanetza/security/v2/verification.hpp>
13#include <vanetza/security/v3/asn1_conversions.hpp>
14#include <vanetza/security/v3/asn1_types.hpp>
15#include <vanetza/security/v3/certificate_cache.hpp>
16#include <vanetza/security/v3/certificate_provider.hpp>
17#include <vanetza/security/v3/certificate_validator.hpp>
18#include <vanetza/security/v3/hash.hpp>
19#include <vanetza/security/v3/sign_header_policy.hpp>
20#include <boost/optional.hpp>
21
22// asn1c quirk
23struct Vanetza_Security_Certificate : public Vanetza_Security_CertificateBase {};
24
25namespace vanetza
26{
27namespace security
28{
29
30namespace
31{
32
33bool assign_permissions(const v2::Certificate& certificate, VerifyConfirm& confirm)
34{
35 for (auto& subject_attribute : certificate.subject_attributes) {
36 if (get_type(subject_attribute) != v2::SubjectAttributeType::ITS_AID_SSP_List) {
37 continue;
38 }
39
40 auto& permissions = boost::get<std::list<v2::ItsAidSsp> >(subject_attribute);
41 for (auto& permission : permissions) {
42 if (permission.its_aid == confirm.its_aid) {
43 confirm.permissions = permission.service_specific_permissions;
44 return true;
45 }
46 }
47
48 break;
49 }
50
51 return false;
52}
53
54} // namespace
55
56StraightVerifyService::StraightVerifyService(const Runtime& runtime, Backend& backend) :
57 m_runtime(runtime), m_backend(backend)
58{
59}
60
61StraightVerifyService::StraightVerifyService(const Runtime& runtime, Backend& backend, PositionProvider& position) :
62 m_runtime(runtime), m_backend(backend), m_position_provider(&position)
63{
64}
65
66void StraightVerifyService::use_certificate_cache(v2::CertificateCache* cache)
67{
68 m_context_v2.m_cert_cache = cache;
69}
70
71void StraightVerifyService::use_certificate_provider(v2::CertificateProvider* provider)
72{
73 m_context_v2.m_cert_provider = provider;
74}
75
76void StraightVerifyService::use_certificate_validator(v2::CertificateValidator* validator)
77{
78 m_context_v2.m_cert_validator = validator;
79}
80
81void StraightVerifyService::use_sign_header_policy(v2::SignHeaderPolicy* policy)
82{
83 m_context_v2.m_sign_policy = policy;
84}
85
86void StraightVerifyService::use_certificate_provider(v3::CertificateProvider* provider)
87{
88 if (provider)
89 {
90 use_certificate_cache(&provider->cache());
91 }
92}
93
94void StraightVerifyService::use_certificate_cache(v3::CertificateCache* cache)
95{
96 m_context_v3.m_cert_cache = cache;
97}
98
99void StraightVerifyService::use_certificate_validator(v3::CertificateValidator* validator)
100{
101 m_context_v3.m_cert_validator = validator;
102}
103
104void StraightVerifyService::use_sign_header_policy(v3::SignHeaderPolicy* policy)
105{
106 m_context_v3.m_sign_policy = policy;
107}
108
110{
111 struct visitor : public boost::static_visitor<VerifyConfirm>
112 {
113 visitor(StraightVerifyService* service) : m_service(service)
114 {
115 }
116
117 VerifyConfirm operator()(const v2::SecuredMessage& msg)
118 {
119 return m_service->verify(msg);
120 }
121
122 VerifyConfirm operator()(const v3::SecuredMessage& msg)
123 {
124 return m_service->verify(msg);
125 }
126
127 StraightVerifyService* m_service = nullptr;
128 } visitor(this);
129
130 return boost::apply_visitor(visitor, request.secured_message);
131}
132
133VerifyConfirm StraightVerifyService::verify(const v2::SecuredMessage& secured_message)
134{
135 // TODO check if certificates in chain have been revoked for all CA certificates, ATs are never revoked
136 VerifyConfirm confirm;
137 using namespace v2;
138
139 if (PayloadType::Signed != secured_message.payload.type) {
140 confirm.report = VerificationReport::Unsigned_Message;
141 return confirm;
142 }
143
144 if (2 != secured_message.protocol_version()) {
145 confirm.report = VerificationReport::Incompatible_Protocol;
146 return confirm;
147 }
148
149 if (!m_context_v2.complete() || !m_position_provider) {
150 confirm.report = VerificationReport::Configuration_Problem;
151 return confirm;
152 }
153
154 v2::CertificateProvider& cert_provider = *m_context_v2.m_cert_provider;
155 v2::CertificateCache& cert_cache = *m_context_v2.m_cert_cache;
156 v2::CertificateValidator& cert_validator = *m_context_v2.m_cert_validator;
157 v2::SignHeaderPolicy& sign_policy = *m_context_v2.m_sign_policy;
158
159 const std::list<HashedId3>* requested_certs = secured_message.header_field<HeaderFieldType::Request_Unrecognized_Certificate>();
160 if (requested_certs) {
161 for (auto& requested_cert : *requested_certs) {
162 if (truncate(calculate_hash(cert_provider.own_certificate())) == requested_cert) {
163 sign_policy.request_certificate();
164 }
165
166 for (auto& cert : cert_provider.own_chain()) {
167 if (truncate(calculate_hash(cert)) == requested_cert) {
168 sign_policy.request_certificate_chain();
169 }
170 }
171 }
172 }
173
174 const IntX* its_aid = secured_message.header_field<HeaderFieldType::Its_Aid>();
175 if (!its_aid) {
176 // ITS-AID is required to be present, report as incompatible protocol, as that's the closest match
177 confirm.report = VerificationReport::Incompatible_Protocol;
178 return confirm;
179 }
180 confirm.its_aid = its_aid->get();
181
182 const SignerInfo* signer_info = secured_message.header_field<HeaderFieldType::Signer_Info>();
183 std::list<v2::Certificate> possible_certificates;
184 bool possible_certificates_from_cache = false;
185
186 // use a dummy hash for initialization
187 HashedId8 signer_hash;
188 signer_hash.fill(0x00);
189
190 if (signer_info) {
191 switch (get_type(*signer_info)) {
192 case SignerInfoType::Certificate:
193 possible_certificates.push_back(boost::get<v2::Certificate>(*signer_info));
194 signer_hash = calculate_hash(boost::get<v2::Certificate>(*signer_info));
195
196 if (confirm.its_aid == aid::CA && cert_cache.lookup(signer_hash, SubjectType::Authorization_Ticket).size() == 0) {
197 // Previously unknown certificate, send own certificate in next CAM
198 // See TS 103 097 v1.2.1, section 7.1, 1st bullet, 3rd dash
199 sign_policy.request_certificate();
200 }
201
202 break;
203 case SignerInfoType::Certificate_Digest_With_SHA256:
204 signer_hash = boost::get<HashedId8>(*signer_info);
205 possible_certificates.splice(possible_certificates.end(), cert_cache.lookup(signer_hash, SubjectType::Authorization_Ticket));
206 possible_certificates_from_cache = true;
207 break;
208 case SignerInfoType::Certificate_Chain:
209 {
210 std::list<v2::Certificate> chain = boost::get<std::list<v2::Certificate>>(*signer_info);
211 if (chain.size() == 0) {
212 confirm.report = VerificationReport::Signer_Certificate_Not_Found;
213 return confirm;
214 } else if (chain.size() > 3) {
215 // prevent DoS by sending very long chains, maximum length is three certificates, because:
216 // AT → AA → Root and no other signatures are allowed, sending the Root is optional
217 confirm.report = VerificationReport::Invalid_Certificate;
218 return confirm;
219 }
220 // pre-check chain certificates, otherwise they're not available for the ticket check
221 for (auto& cert : chain) {
222 // root certificates must already be known, otherwise the validation will fail anyway
223 if (cert.subject_info.subject_type == SubjectType::Authorization_Authority) {
224 // there's no need to report unknown signers at this point, see comment above
225 CertificateValidity validity = cert_validator.check_certificate(cert);
226
227 // we can abort early if there are invalid AA certificates in the chain
228 if (!validity) {
229 confirm.report = VerificationReport::Invalid_Certificate;
230 confirm.certificate_validity = validity;
231 return confirm;
232 }
233
234 // We won't cache outdated or premature certificates in the cache and abort early.
235 // This check isn't required as it would just fail below or in the consistency checks,
236 // but it's an optimization and saves us from polluting the cache with such certificates.
237 if (!check_certificate_time(cert, m_runtime.now()) || !check_certificate_region(cert, m_position_provider->position_fix())) {
238 confirm.report = VerificationReport::Invalid_Certificate;
239 return confirm;
240 }
241
242 cert_cache.insert(cert);
243 }
244 }
245 // last certificate must be the authorization ticket
246 signer_hash = calculate_hash(chain.back());
247 possible_certificates.push_back(chain.back());
248 }
249 break;
250 default:
251 confirm.report = VerificationReport::Unsupported_Signer_Identifier_Type;
252 return confirm;
253 break;
254 }
255 }
256
257 if (possible_certificates.size() == 0) {
258 confirm.report = VerificationReport::Signer_Certificate_Not_Found;
259 confirm.certificate_id = signer_hash;
260 sign_policy.request_unrecognized_certificate(signer_hash);
261 return confirm;
262 }
263
264 if (!check_generation_time(secured_message, m_runtime.now())) {
265 confirm.report = VerificationReport::Invalid_Timestamp;
266 return confirm;
267 }
268
269 // TODO check Duplicate_Message, Invalid_Mobility_Data, Unencrypted_Message, Decryption_Error
270
271 // check signature
272 const TrailerField* signature_field = secured_message.trailer_field(TrailerFieldType::Signature);
273 const v2::Signature* signature = boost::get<v2::Signature>(signature_field);
274
275 if (!signature) {
276 confirm.report = VerificationReport::Unsigned_Message;
277 return confirm;
278 }
279
280 if (PublicKeyAlgorithm::ECDSA_NISTP256_With_SHA256 != get_type(*signature)) {
281 confirm.report = VerificationReport::False_Signature;
282 return confirm;
283 }
284
285 // check the size of signature.R and siganture.s
286 auto ecdsa = extract_ecdsa_signature(*signature);
287 const auto field_len = field_size(PublicKeyAlgorithm::ECDSA_NISTP256_With_SHA256);
288 if (!ecdsa || ecdsa->s.size() != field_len) {
289 confirm.report = VerificationReport::False_Signature;
290 return confirm;
291 }
292
293 // verify payload signature with given signature
294 ByteBuffer payload = convert_for_signing(secured_message, secured_message.trailer_fields);
295 boost::optional<v2::Certificate> signer;
296
297 for (const auto& cert : possible_certificates) {
298 SubjectType subject_type = cert.subject_info.subject_type;
299 if (subject_type != SubjectType::Authorization_Ticket) {
300 confirm.report = VerificationReport::Invalid_Certificate;
301 confirm.certificate_validity = CertificateInvalidReason::Invalid_Signer;
302 return confirm;
303 }
304
305 boost::optional<ecdsa256::PublicKey> public_key = get_public_key(cert, m_backend);
306
307 // public key could not be extracted
308 if (!public_key) {
309 confirm.report = VerificationReport::Invalid_Certificate;
310 confirm.certificate_validity = CertificateInvalidReason::Missing_Public_Key;
311 return confirm;
312 }
313
314 if (m_backend.verify_data(public_key.get(), payload, *ecdsa)) {
315 signer = cert;
316 break;
317 }
318 }
319
320 if (!signer) {
321 // HashedId8 of authorization tickets is not guaranteed to be globally unique.
322 // The collision probability is rather low, but it might happen.
323 if (signer_info && get_type(*signer_info) == SignerInfoType::Certificate_Digest_With_SHA256) {
324 // assume a hash collision since we got only a digest with message
325 confirm.report = VerificationReport::Signer_Certificate_Not_Found;
326 } else {
327 // signature does not match the certificate received with this message
328 confirm.report = VerificationReport::False_Signature;
329 }
330
331 confirm.certificate_id = signer_hash;
332 sign_policy.request_unrecognized_certificate(signer_hash);
333 return confirm;
334 }
335
336 // we can only check the generation location after we have identified the correct certificate
337 if (!check_generation_location(secured_message, *signer)) {
338 confirm.report = VerificationReport::Invalid_Certificate;
339 confirm.certificate_validity = CertificateInvalidReason::Off_Region;
340 return confirm;
341 }
342
344 if (!possible_certificates_from_cache) { // certificates from cache are already verified as trusted
345 cert_validity = cert_validator.check_certificate(*signer);
346 }
347
348 confirm.certificate_validity = cert_validity;
349
350 // if certificate could not be verified return correct DecapReport
351 if (!cert_validity) {
352 confirm.report = VerificationReport::Invalid_Certificate;
353
354 if (cert_validity.reason() == CertificateInvalidReason::Unknown_Signer) {
355 if (get_type(signer->signer_info) == SignerInfoType::Certificate_Digest_With_SHA256) {
356 auto signer_hash = boost::get<HashedId8>(signer->signer_info);
357 confirm.certificate_id = signer_hash;
358 sign_policy.request_unrecognized_certificate(signer_hash);
359 }
360 }
361
362 return confirm;
363 }
364
365 if (!check_certificate_time(*signer, m_runtime.now())) {
366 confirm.report = VerificationReport::Invalid_Certificate;
367 confirm.certificate_validity = CertificateInvalidReason::Off_Time_Period;
368 return confirm;
369 }
370
371 if (!check_certificate_region(*signer, m_position_provider->position_fix())) {
372 confirm.report = VerificationReport::Invalid_Certificate;
373 confirm.certificate_validity = CertificateInvalidReason::Off_Region;
374 return confirm;
375 }
376
377 // Assign permissions from the certificate based on the message AID already present in the confirm
378 // and reject the certificate if no permissions are present for the claimed AID.
379 if (!assign_permissions(*signer, confirm)) {
380 // This might seem weird, because the certificate itself is valid, but not for the received message.
381 confirm.report = VerificationReport::Invalid_Certificate;
382 confirm.certificate_validity = CertificateInvalidReason::Insufficient_ITS_AID;
383 return confirm;
384 }
385
386 // cache only certificates that are useful, one that mismatches its restrictions isn't
387 cert_cache.insert(*signer);
388
389 confirm.report = VerificationReport::Success;
390 return confirm;
391}
392
394{
395 /*
396 * TS 103 097 v1.3.1 demands to assess the validity of signed data
397 * according to IEEE 1609.2 clause 5.2.
398 */
399 VerifyConfirm confirm;
400 confirm.report = VerificationReport::Incompatible_Protocol; /*< fallback error code */
401
402 if (!msg.is_signed()) {
403 confirm.report = VerificationReport::Unsigned_Message;
404 return confirm;
405 }
406
407 if (msg.protocol_version() != 3) {
408 confirm.report = VerificationReport::Incompatible_Protocol;
409 return confirm;
410 }
411
412 auto gen_time = msg.generation_time();
413 if (!gen_time) {
414 // TS 103 097 v1.3.1 demands generation time to be always present
415 confirm.report = VerificationReport::Invalid_Timestamp;
416 return confirm;
417 }
418 // TODO further generation time checks depending on application profile
419
420 auto signature = msg.signature();
421 if (!signature) {
422 confirm.report = VerificationReport::Unsigned_Message;
423 return confirm;
424 }
425
426 struct certificate_lookup_visitor : public boost::static_visitor<const v3::asn1::Certificate*> {
427 certificate_lookup_visitor(v3::CertificateCache* cache) :
428 m_cache(cache)
429 {
430 }
431
432 const v3::asn1::Certificate* operator()(const v3::asn1::HashedId8* digest)
433 {
434 // look up certificate matching digest in local storage
435 if (m_cache && digest) {
436 const v3::Certificate* found = m_cache->lookup(v3::convert(*digest));
437 return found ? found->content() : nullptr;
438 } else {
439 return nullptr;
440 }
441 }
442
443 const v3::asn1::Certificate* operator()(const v3::asn1::Certificate* cert)
444 {
445 return cert;
446 }
447
448 v3::CertificateCache* m_cache;
449 } certificate_lookup_visitor(m_context_v3.m_cert_cache);
450 auto signer_identifier = msg.signer_identifier();
451
452 // track "known" stations
453 auto maybe_digest = v3::get_certificate_id(signer_identifier);
454 if (m_context_v3.m_cert_cache && maybe_digest) {
455 bool was_unknown_station = m_context_v3.m_cert_cache->announce(*maybe_digest);
456 if (was_unknown_station && msg.its_aid() == aid::CA && m_context_v3.m_sign_policy) {
457 // CAM from unknown station received, add our certificate to next outgoing message
458 m_context_v3.m_sign_policy->request_certificate();
459 }
460 }
461
462 // check if a ITS-AID 36 (CAM) with inline cert request shall be handled by us
463 if (msg.its_aid() == aid::CA && m_context_v3.m_sign_policy) {
464 Vanetza_Security_SequenceOfHashedId3* requests = msg->content->choice.signedData->tbsData->headerInfo.inlineP2pcdRequest;
465 if (requests) {
466 for (int i = 0; i < requests->list.count; ++i)
467 {
468 const Vanetza_Security_HashedId3_t* req_digest = requests->list.array[i];
469 const HashedId3 hid3 = create_hashed_id3(*req_digest);
470 m_context_v3.m_sign_policy->enqueue_p2p_request(hid3);
471 }
472 }
473
474 Vanetza_Security_Certificate* included_cert = msg->content->choice.signedData->tbsData->headerInfo.requestedCertificate;
475 if (included_cert) {
476 auto maybe_digest = v3::calculate_digest(*included_cert);
477 if (maybe_digest) {
478 m_context_v3.m_sign_policy->discard_p2p_request(truncate(*maybe_digest));
479 }
480 }
481 }
482
483 const v3::asn1::Certificate* certificate = boost::apply_visitor(certificate_lookup_visitor, signer_identifier);
484 if (!certificate) {
485 if (msg.its_aid() == aid::CA && m_context_v3.m_sign_policy && maybe_digest) {
486 // for received CAMs (having digest as signer identifier) with unknown AT we request the full AT certificate
487 m_context_v3.m_sign_policy->request_unrecognized_certificate(*maybe_digest);
488 }
489 confirm.report = VerificationReport::Signer_Certificate_Not_Found;
490 return confirm;
491 }
492
493 // code below can safely dereference certificate
494 assert(certificate != nullptr);
495
496 // check AT certificate's validity
497 if (m_context_v3.m_cert_validator) {
498 auto verdict = m_context_v3.m_cert_validator->valid_for_signing(v3::CertificateView { certificate }, msg.its_aid());
499 if (verdict != v3::CertificateValidator::Verdict::Valid) {
500 confirm.report = VerificationReport::Invalid_Certificate;
501 return confirm;
502 }
503 }
504
505 boost::optional<HashedId8> aa_digest;
506 switch (certificate->issuer.present)
507 {
508 case Vanetza_Security_IssuerIdentifier_PR_sha256AndDigest:
509 aa_digest = v3::convert(certificate->issuer.choice.sha256AndDigest);
510 break;
511 case Vanetza_Security_IssuerIdentifier_PR_sha384AndDigest:
512 aa_digest = v3::convert(certificate->issuer.choice.sha384AndDigest);
513 break;
514 default:
515 break;
516 }
517
518 if (aa_digest && m_context_v3.m_cert_cache) {
519 if (!m_context_v3.m_cert_cache->is_known(*aa_digest)) {
520 if (m_context_v3.m_sign_policy) {
521 // request unknown AA certificate for any received message signed with AT issued by this AA
522 m_context_v3.m_sign_policy->request_unrecognized_certificate(*aa_digest);
523 }
524 }
525 // TODO: if AA is validated and fails, announce it in cache
526 }
527
528 auto public_key = v3::get_public_key(*certificate);
529 if (!public_key) {
530 confirm.report = VerificationReport::Invalid_Certificate;
531 confirm.certificate_validity = CertificateInvalidReason::Missing_Public_Key;
532 return confirm;
533 }
534
535 ByteBuffer msg_hash = v3::calculate_message_hash(m_backend, msg.hash_id(),
536 msg.signing_payload(), v3::CertificateView { certificate });
537 if (!m_backend.verify_digest(*public_key, msg_hash, *signature)) {
538 confirm.report = VerificationReport::False_Signature;
539 return confirm;
540 }
541
542 confirm.its_aid = msg.its_aid();
543 confirm.permissions = v3::get_app_permissions(*certificate, confirm.its_aid);
544 confirm.certificate_id = maybe_digest;
545 confirm.report = VerificationReport::Success;
546
547 if (m_context_v3.m_cert_cache && confirm.certificate_id) {
548 v3::CertificateCache& cache = *m_context_v3.m_cert_cache;
549 bool already_cached = cache.lookup(*confirm.certificate_id) != nullptr;
550 if (!already_cached && confirm.its_aid == aid::CA && m_context_v3.m_sign_policy) {
551 // CAM from unknown station received, add our certificate to next outgoing message
552 m_context_v3.m_sign_policy->request_certificate();
553 }
554
555 // update certificate cache with received certificate
556 if (v3::contains_certificate(signer_identifier)) {
557 cache.store(v3::Certificate { *certificate });
558 }
559 }
560
561 return confirm;
562}
563
564} // namespace security
565} // namespace vanetza
virtual const PositionFix & position_fix()=0
virtual Clock::time_point now() const =0
static CertificateValidity valid()
Create CertificateValidity signalling a valid certificate This method is equivalent to default constr...
CertificateInvalidReason reason() const
Get reason for certificate invalidity This call is only safe if reason is available,...
void insert(const Certificate &certificate)
virtual CertificateValidity check_certificate(const Certificate &certificate)=0
IntX specified in TS 103 097 v1.2.1, section 4.2.1.
Definition int_x.hpp:21
virtual void request_unrecognized_certificate(HashedId8 id)=0
bool is_known(const HashedId8 &digest) const
bool announce(const HashedId8 &digest)
virtual CertificateCache & cache()=0
virtual Verdict valid_for_signing(const CertificateView &certificate, ItsAid app)=0
virtual void discard_p2p_request(HashedId3 id)=0
virtual void enqueue_p2p_request(HashedId3 id)=0
virtual void request_unrecognized_certificate(HashedId8 id)=0
described in TS 103 097 v1.2.1 (2015-06), section 6.1
SecuredMessage as specified in TS 103 097 v1.2.1, section 5.1.
const TrailerField * trailer_field(TrailerFieldType type) const