Vanetza
Loading...
Searching...
No Matches
certificate_validator.cpp
1#include <vanetza/common/position_provider.hpp>
2#include <vanetza/common/position_fix.hpp>
3#include <vanetza/common/runtime.hpp>
4#include <vanetza/security/v3/certificate.hpp>
5#include <vanetza/security/v3/certificate_validator.hpp>
6#include <vanetza/security/v3/issuer_lookup.hpp>
7#include <vanetza/security/v3/revocation_lookup.hpp>
8#include <vanetza/security/v3/trust_store.hpp>
9#include <cstdint>
10
11
12namespace vanetza
13{
14namespace security
15{
16namespace v3
17{
18
19namespace
20{
21
22bool check_time_consistency(const CertificateView& subject, const CertificateView& issuer)
23{
24 const auto subject_time = subject.get_start_and_end_validity();
25 const auto issuer_time = issuer.get_start_and_end_validity();
26 return issuer_time.start_validity <= subject_time.start_validity &&
27 issuer_time.end_validity >= subject_time.end_validity;
28}
29
30bool check_permission_consistency(const CertificateView& subject, const CertificateView& issuer, ItsAid its_aid)
31{
32 if (subject.is_ca_certificate() && !subject.is_allowed_to_issue(its_aid)) {
33 return false;
34 } else if (subject.is_at_certificate() && !subject.valid_for_application(its_aid)) {
35 return false;
36 }
37
38 return issuer.is_allowed_to_issue(its_aid);
39}
40
41bool check_assurance_consistency(const CertificateView& subject, const CertificateView& issuer)
42{
43 const auto subject_assurance = subject.assurance_level();
44 const auto issuer_assurance = issuer.assurance_level();
45 if (!subject_assurance) {
46 return true;
47 } else if (!issuer_assurance) {
48 return false;
49 }
50
51 const auto subject_value = *subject_assurance;
52 const auto issuer_value = *issuer_assurance;
53 const std::uint8_t subject_level = (subject_value >> 5) & 0x07;
54 const std::uint8_t issuer_level = (issuer_value >> 5) & 0x07;
55 const std::uint8_t subject_confidence = (subject_value >> 2) & 0x07;
56 const std::uint8_t issuer_confidence = (issuer_value >> 2) & 0x07;
57
58 return subject_level < issuer_level ||
59 (subject_level == issuer_level && subject_confidence <= issuer_confidence);
60}
61
62bool check_region_consistency(const CertificateView& subject, const CertificateView& issuer)
63{
64 return subject.region_is_within(issuer);
65}
66
67} // namespace
68
69auto DefaultCertificateValidator::valid_for_signing(const CertificateView& signing_cert, ItsAid its_aid) -> Verdict
70{
71 if (!m_disable_time_checks && !m_runtime) {
72 return Verdict::Misconfiguration;
73 } else if (!m_disable_location_checks && (!m_position_provider || !m_location_checker)) {
74 return Verdict::Misconfiguration;
75 } else if (!signing_cert.valid_for_application(its_aid)) {
76 return Verdict::InsufficientPermission;
77 } else if (m_runtime && !signing_cert.valid_at_timepoint(m_runtime->now())) {
78 return Verdict::Expired;
79 } else if (!is_chain_anchored(signing_cert)) {
80 return Verdict::Untrusted;
81 } else if (!chain_is_consistent(signing_cert, its_aid)) {
82 return Verdict::InconsistentChain;
83 } else if (chain_is_revoked(signing_cert)) {
84 return Verdict::Revoked;
85 } else {
86 Verdict verdict = Verdict::Valid;
87 if (!m_disable_location_checks) {
88 if (m_position_provider) {
89 auto location = m_position_provider->position_fix();
90 if (signing_cert.has_region_restriction()) {
91 if (!signing_cert.valid_at_location(location, m_location_checker)) {
92 verdict = Verdict::OutsideRegion;
93 }
94 } else {
95 auto issuing_cert = find_issuer_certificate(signing_cert);
96 if (issuing_cert && !issuing_cert->valid_at_location(location, m_location_checker)) {
97 verdict = Verdict::OutsideRegion;
98 }
99 }
100 }
101 }
102 return verdict;
103 }
104}
105
106void DefaultCertificateValidator::use_runtime(const Runtime* runtime)
107{
108 m_runtime = runtime;
109}
110
111void DefaultCertificateValidator::use_position_provider(PositionProvider* pp)
112{
113 m_position_provider = pp;
114}
115
116void DefaultCertificateValidator::use_issuer_lookup(const IssuerLookup* lookup)
117{
118 m_issuer_lookup = lookup;
119}
120
121void DefaultCertificateValidator::use_location_checker(const LocationChecker* checker)
122{
123 m_location_checker = checker;
124}
125
126void DefaultCertificateValidator::use_revocation_lookup(const RevocationLookup* lookup)
127{
128 m_revocation_lookup = lookup;
129}
130
131void DefaultCertificateValidator::use_trust_store(const TrustStore* store)
132{
133 m_trust_store = store;
134}
135
136void DefaultCertificateValidator::disable_time_checks(bool flag)
137{
138 m_disable_time_checks = flag;
139}
140
141void DefaultCertificateValidator::disable_location_checks(bool flag)
142{
143 m_disable_location_checks = flag;
144}
145
146void DefaultCertificateValidator::disable_chain_consistency_checks(bool flag)
147{
148 m_disable_chain_consistency_checks = flag;
149}
150
151void DefaultCertificateValidator::disable_region_consistency_checks(bool flag)
152{
153 m_disable_region_consistency_checks = flag;
154}
155
156const Certificate* DefaultCertificateValidator::find_issuer_certificate(const CertificateView& at_cert) const
157{
158 if (m_issuer_lookup) {
159 auto maybe_issuer_digest = at_cert.issuer_digest();
160 if (maybe_issuer_digest) {
161 return m_issuer_lookup->find_issuer(*maybe_issuer_digest);
162 }
163 }
164
165 return nullptr;
166}
167
168bool DefaultCertificateValidator::is_chain_anchored(const CertificateView& signing_cert) const
169{
170 if (!m_trust_store || !m_issuer_lookup) {
171 // No anchoring policy configured: fail open, matching the existing optional-injection pattern.
172 return true;
173 }
174
175 constexpr int max_chain_depth = 8;
176 const CertificateView* cert = &signing_cert;
177 for (int depth = 0; depth < max_chain_depth; ++depth) {
178 if (cert->issuer_is_self()) {
179 // Reached a self-signed cert: anchored iff it's in the trust store.
180 const auto cert_digest = cert->calculate_digest();
181 return cert_digest && !m_trust_store->lookup(*cert_digest).empty();
182 }
183 const auto issuer_digest = cert->issuer_digest();
184 if (!issuer_digest) {
185 return false;
186 }
187 const Certificate* issuer = m_issuer_lookup->find_issuer(*issuer_digest);
188 if (!issuer) {
189 return false;
190 }
191 cert = issuer;
192 }
193 return false;
194}
195
196bool DefaultCertificateValidator::chain_is_consistent(const CertificateView& signing_cert, ItsAid its_aid) const
197{
198 if (m_disable_chain_consistency_checks) {
199 return true;
200 }
201
202 if (!m_issuer_lookup) {
203 return true;
204 }
205
206 constexpr int max_chain_depth = 8;
207 const CertificateView* subject = &signing_cert;
208 for (int depth = 0; depth < max_chain_depth && !subject->issuer_is_self(); ++depth) {
209 const Certificate* issuer = find_issuer_certificate(*subject);
210 if (!issuer) {
211 return true;
212 }
213 if (!check_consistency(*subject, *issuer, its_aid)) {
214 return false;
215 }
216 subject = issuer;
217 }
218
219 return true;
220}
221
222bool DefaultCertificateValidator::chain_is_revoked(const CertificateView& signing_cert) const
223{
224 if (!m_revocation_lookup || !m_issuer_lookup) {
225 return false;
226 }
227
228 // C-ITS chains are AT -> AA -> RCA (depth 3). The bound is defense against IssuerLookup cycles.
229 // The walk stops at the self-signed root: RCA revocation is the ECTL's job, not a CRL's.
230 constexpr int max_chain_depth = 8;
231 const CertificateView* cert = &signing_cert;
232 for (int depth = 0; depth < max_chain_depth; ++depth) {
233 const auto cert_digest = cert->calculate_digest();
234 const auto issuer_digest = cert->issuer_digest();
235 if (!cert_digest || !issuer_digest) {
236 break;
237 }
238 if (m_revocation_lookup->is_revoked(*issuer_digest, *cert_digest)) {
239 return true;
240 }
241 const Certificate* issuer = m_issuer_lookup->find_issuer(*issuer_digest);
242 if (!issuer || issuer->issuer_is_self()) {
243 break;
244 }
245 cert = issuer;
246 }
247 return false;
248}
249
250bool DefaultCertificateValidator::check_consistency(
251 const CertificateView& subject, const CertificateView& issuer, ItsAid its_aid) const
252{
253 return check_time_consistency(subject, issuer) &&
254 check_permission_consistency(subject, issuer, its_aid) &&
255 check_assurance_consistency(subject, issuer) &&
256 (m_disable_region_consistency_checks || check_region_consistency(subject, issuer));
257}
258
259} // namespace v3
260} // namespace security
261} // namespace vanetza
virtual const PositionFix & position_fix()=0
virtual Clock::time_point now() const =0
bool valid_at_timepoint(const Clock::time_point &time_point) const
StartAndEndValidity get_start_and_end_validity() const
bool is_allowed_to_issue(ItsAid aid) const
bool valid_for_application(ItsAid aid) const
boost::optional< HashedId8 > calculate_digest() const
bool valid_at_location(const PositionFix &location, const LocationChecker *lc) const
bool region_is_within(const CertificateView &issuer) const
boost::optional< std::uint8_t > assurance_level() const
boost::optional< HashedId8 > issuer_digest() const
Verdict valid_for_signing(const CertificateView &, ItsAid) override
virtual const Certificate * find_issuer(const HashedId8 &digest) const =0
virtual bool is_revoked(const HashedId8 &issuer, const HashedId8 &cert) const =0