obu-firmware builds against the vanetza-idf C-ITS library, which until now came from the colleague's microbu-esp32c5 tree beside the repository and was not tracked here, so a clone of this repository could not build the firmware it ships. The library alone is now part of obu-firmware, as obu-firmware/external/vanetza-idf: their external/vanetza-idf at commit cf4b99f, unchanged (9775 files; see its PROVENANCE.md). CMake takes it from there by default; -DVANETZA_IDF_DIR still points the build elsewhere. The rest of the colleague's tree (their own VAM firmware, PKI tooling, station-link Python tools, the V2X2MAP bridge) stays out of this repository and gitignored; nothing is pushed to their repository. NOTES.md, docs/06, TODO.md and the pcap verifier's usage line point at the new location.
262 lines
8.4 KiB
C++
262 lines
8.4 KiB
C++
#include <vanetza/common/position_provider.hpp>
|
|
#include <vanetza/common/position_fix.hpp>
|
|
#include <vanetza/common/runtime.hpp>
|
|
#include <vanetza/security/v3/certificate.hpp>
|
|
#include <vanetza/security/v3/certificate_validator.hpp>
|
|
#include <vanetza/security/v3/issuer_lookup.hpp>
|
|
#include <vanetza/security/v3/revocation_lookup.hpp>
|
|
#include <vanetza/security/v3/trust_store.hpp>
|
|
#include <cstdint>
|
|
|
|
|
|
namespace vanetza
|
|
{
|
|
namespace security
|
|
{
|
|
namespace v3
|
|
{
|
|
|
|
namespace
|
|
{
|
|
|
|
bool check_time_consistency(const CertificateView& subject, const CertificateView& issuer)
|
|
{
|
|
const auto subject_time = subject.get_start_and_end_validity();
|
|
const auto issuer_time = issuer.get_start_and_end_validity();
|
|
return issuer_time.start_validity <= subject_time.start_validity &&
|
|
issuer_time.end_validity >= subject_time.end_validity;
|
|
}
|
|
|
|
bool check_permission_consistency(const CertificateView& subject, const CertificateView& issuer, ItsAid its_aid)
|
|
{
|
|
if (subject.is_ca_certificate() && !subject.is_allowed_to_issue(its_aid)) {
|
|
return false;
|
|
} else if (subject.is_at_certificate() && !subject.valid_for_application(its_aid)) {
|
|
return false;
|
|
}
|
|
|
|
return issuer.is_allowed_to_issue(its_aid);
|
|
}
|
|
|
|
bool check_assurance_consistency(const CertificateView& subject, const CertificateView& issuer)
|
|
{
|
|
const auto subject_assurance = subject.assurance_level();
|
|
const auto issuer_assurance = issuer.assurance_level();
|
|
if (!subject_assurance) {
|
|
return true;
|
|
} else if (!issuer_assurance) {
|
|
return false;
|
|
}
|
|
|
|
const auto subject_value = *subject_assurance;
|
|
const auto issuer_value = *issuer_assurance;
|
|
const std::uint8_t subject_level = (subject_value >> 5) & 0x07;
|
|
const std::uint8_t issuer_level = (issuer_value >> 5) & 0x07;
|
|
const std::uint8_t subject_confidence = (subject_value >> 2) & 0x07;
|
|
const std::uint8_t issuer_confidence = (issuer_value >> 2) & 0x07;
|
|
|
|
return subject_level < issuer_level ||
|
|
(subject_level == issuer_level && subject_confidence <= issuer_confidence);
|
|
}
|
|
|
|
bool check_region_consistency(const CertificateView& subject, const CertificateView& issuer)
|
|
{
|
|
return subject.region_is_within(issuer);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
auto DefaultCertificateValidator::valid_for_signing(const CertificateView& signing_cert, ItsAid its_aid) -> Verdict
|
|
{
|
|
if (!m_disable_time_checks && !m_runtime) {
|
|
return Verdict::Misconfiguration;
|
|
} else if (!m_disable_location_checks && (!m_position_provider || !m_location_checker)) {
|
|
return Verdict::Misconfiguration;
|
|
} else if (!signing_cert.valid_for_application(its_aid)) {
|
|
return Verdict::InsufficientPermission;
|
|
} else if (m_runtime && !signing_cert.valid_at_timepoint(m_runtime->now())) {
|
|
return Verdict::Expired;
|
|
} else if (!is_chain_anchored(signing_cert)) {
|
|
return Verdict::Untrusted;
|
|
} else if (!chain_is_consistent(signing_cert, its_aid)) {
|
|
return Verdict::InconsistentChain;
|
|
} else if (chain_is_revoked(signing_cert)) {
|
|
return Verdict::Revoked;
|
|
} else {
|
|
Verdict verdict = Verdict::Valid;
|
|
if (!m_disable_location_checks) {
|
|
if (m_position_provider) {
|
|
auto location = m_position_provider->position_fix();
|
|
if (signing_cert.has_region_restriction()) {
|
|
if (!signing_cert.valid_at_location(location, m_location_checker)) {
|
|
verdict = Verdict::OutsideRegion;
|
|
}
|
|
} else {
|
|
auto issuing_cert = find_issuer_certificate(signing_cert);
|
|
if (issuing_cert && !issuing_cert->valid_at_location(location, m_location_checker)) {
|
|
verdict = Verdict::OutsideRegion;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return verdict;
|
|
}
|
|
}
|
|
|
|
void DefaultCertificateValidator::use_runtime(const Runtime* runtime)
|
|
{
|
|
m_runtime = runtime;
|
|
}
|
|
|
|
void DefaultCertificateValidator::use_position_provider(PositionProvider* pp)
|
|
{
|
|
m_position_provider = pp;
|
|
}
|
|
|
|
void DefaultCertificateValidator::use_issuer_lookup(const IssuerLookup* lookup)
|
|
{
|
|
m_issuer_lookup = lookup;
|
|
}
|
|
|
|
void DefaultCertificateValidator::use_location_checker(const LocationChecker* checker)
|
|
{
|
|
m_location_checker = checker;
|
|
}
|
|
|
|
void DefaultCertificateValidator::use_revocation_lookup(const RevocationLookup* lookup)
|
|
{
|
|
m_revocation_lookup = lookup;
|
|
}
|
|
|
|
void DefaultCertificateValidator::use_trust_store(const TrustStore* store)
|
|
{
|
|
m_trust_store = store;
|
|
}
|
|
|
|
void DefaultCertificateValidator::disable_time_checks(bool flag)
|
|
{
|
|
m_disable_time_checks = flag;
|
|
}
|
|
|
|
void DefaultCertificateValidator::disable_location_checks(bool flag)
|
|
{
|
|
m_disable_location_checks = flag;
|
|
}
|
|
|
|
void DefaultCertificateValidator::disable_chain_consistency_checks(bool flag)
|
|
{
|
|
m_disable_chain_consistency_checks = flag;
|
|
}
|
|
|
|
void DefaultCertificateValidator::disable_region_consistency_checks(bool flag)
|
|
{
|
|
m_disable_region_consistency_checks = flag;
|
|
}
|
|
|
|
const Certificate* DefaultCertificateValidator::find_issuer_certificate(const CertificateView& at_cert) const
|
|
{
|
|
if (m_issuer_lookup) {
|
|
auto maybe_issuer_digest = at_cert.issuer_digest();
|
|
if (maybe_issuer_digest) {
|
|
return m_issuer_lookup->find_issuer(*maybe_issuer_digest);
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool DefaultCertificateValidator::is_chain_anchored(const CertificateView& signing_cert) const
|
|
{
|
|
if (!m_trust_store || !m_issuer_lookup) {
|
|
// No anchoring policy configured: fail open, matching the existing optional-injection pattern.
|
|
return true;
|
|
}
|
|
|
|
constexpr int max_chain_depth = 8;
|
|
const CertificateView* cert = &signing_cert;
|
|
for (int depth = 0; depth < max_chain_depth; ++depth) {
|
|
if (cert->issuer_is_self()) {
|
|
// Reached a self-signed cert: anchored iff it's in the trust store.
|
|
const auto cert_digest = cert->calculate_digest();
|
|
return cert_digest && !m_trust_store->lookup(*cert_digest).empty();
|
|
}
|
|
const auto issuer_digest = cert->issuer_digest();
|
|
if (!issuer_digest) {
|
|
return false;
|
|
}
|
|
const Certificate* issuer = m_issuer_lookup->find_issuer(*issuer_digest);
|
|
if (!issuer) {
|
|
return false;
|
|
}
|
|
cert = issuer;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool DefaultCertificateValidator::chain_is_consistent(const CertificateView& signing_cert, ItsAid its_aid) const
|
|
{
|
|
if (m_disable_chain_consistency_checks) {
|
|
return true;
|
|
}
|
|
|
|
if (!m_issuer_lookup) {
|
|
return true;
|
|
}
|
|
|
|
constexpr int max_chain_depth = 8;
|
|
const CertificateView* subject = &signing_cert;
|
|
for (int depth = 0; depth < max_chain_depth && !subject->issuer_is_self(); ++depth) {
|
|
const Certificate* issuer = find_issuer_certificate(*subject);
|
|
if (!issuer) {
|
|
return true;
|
|
}
|
|
if (!check_consistency(*subject, *issuer, its_aid)) {
|
|
return false;
|
|
}
|
|
subject = issuer;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool DefaultCertificateValidator::chain_is_revoked(const CertificateView& signing_cert) const
|
|
{
|
|
if (!m_revocation_lookup || !m_issuer_lookup) {
|
|
return false;
|
|
}
|
|
|
|
// C-ITS chains are AT -> AA -> RCA (depth 3). The bound is defense against IssuerLookup cycles.
|
|
// The walk stops at the self-signed root: RCA revocation is the ECTL's job, not a CRL's.
|
|
constexpr int max_chain_depth = 8;
|
|
const CertificateView* cert = &signing_cert;
|
|
for (int depth = 0; depth < max_chain_depth; ++depth) {
|
|
const auto cert_digest = cert->calculate_digest();
|
|
const auto issuer_digest = cert->issuer_digest();
|
|
if (!cert_digest || !issuer_digest) {
|
|
break;
|
|
}
|
|
if (m_revocation_lookup->is_revoked(*issuer_digest, *cert_digest)) {
|
|
return true;
|
|
}
|
|
const Certificate* issuer = m_issuer_lookup->find_issuer(*issuer_digest);
|
|
if (!issuer || issuer->issuer_is_self()) {
|
|
break;
|
|
}
|
|
cert = issuer;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool DefaultCertificateValidator::check_consistency(
|
|
const CertificateView& subject, const CertificateView& issuer, ItsAid its_aid) const
|
|
{
|
|
return check_time_consistency(subject, issuer) &&
|
|
check_permission_consistency(subject, issuer, its_aid) &&
|
|
check_assurance_consistency(subject, issuer) &&
|
|
(m_disable_region_consistency_checks || check_region_consistency(subject, issuer));
|
|
}
|
|
|
|
} // namespace v3
|
|
} // namespace security
|
|
} // namespace vanetza
|