Files
MicrOBU/obu-firmware/external/vanetza-idf/tools/certify/commands/show-certificate.cpp
T
Ashin Walpola d107534eb2 Keep vanetza-idf in obu-firmware, so a plain clone builds the firmware
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.
2026-09-24 10:56:05 +02:00

315 lines
13 KiB
C++

#include "show-certificate.hpp"
#include <boost/algorithm/hex.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/program_options.hpp>
#include <boost/variant.hpp>
#include <fstream>
#include <iostream>
#include <vanetza/common/its_aid.hpp>
#include <vanetza/security/cam_ssp.hpp>
#include <vanetza/security/v2/certificate.hpp>
#include <vanetza/security/v2/ecc_point.hpp>
#include <vanetza/security/v2/persistence.hpp>
#include <vanetza/security/v2/public_key.hpp>
namespace po = boost::program_options;
using namespace vanetza;
using namespace vanetza::security;
namespace {
std::string hex_buffer(const ByteBuffer& buffer)
{
std::string bytes(buffer.begin(), buffer.end());
return boost::algorithm::hex(bytes);
}
void print_ecc_point(const EccPoint& point, const std::string& indent)
{
switch (v2::get_type(point)) {
case v2::EccPointType::X_Coordinate_Only:
std::cout << indent << "X: " << hex_buffer(boost::get<X_Coordinate_Only>(point).x)
<< " (x-coordinate only)" << std::endl;
break;
case v2::EccPointType::Compressed_Lsb_Y_0:
std::cout << indent << "X: " << hex_buffer(boost::get<Compressed_Lsb_Y_0>(point).x)
<< " (compressed, y LSB 0)" << std::endl;
break;
case v2::EccPointType::Compressed_Lsb_Y_1:
std::cout << indent << "X: " << hex_buffer(boost::get<Compressed_Lsb_Y_1>(point).x)
<< " (compressed, y LSB 1)" << std::endl;
break;
case v2::EccPointType::Uncompressed: {
const Uncompressed& uncompressed = boost::get<Uncompressed>(point);
std::cout << indent << "X: " << hex_buffer(uncompressed.x) << std::endl;
std::cout << indent << "Y: " << hex_buffer(uncompressed.y) << " (uncompressed)" << std::endl;
break;
}
}
}
void print_public_key(const v2::PublicKey& key, const std::string& indent)
{
switch (v2::get_type(key)) {
case v2::PublicKeyAlgorithm::ECDSA_NISTP256_With_SHA256:
std::cout << indent << "Algorithm: ECDSA NISTP256 with SHA-256" << std::endl;
print_ecc_point(boost::get<v2::ecdsa_nistp256_with_sha256>(key).public_key, indent);
break;
case v2::PublicKeyAlgorithm::ECIES_NISTP256:
std::cout << indent << "Algorithm: ECIES NISTP256" << std::endl;
print_ecc_point(boost::get<v2::ecies_nistp256>(key).public_key, indent);
break;
}
}
} // namespace
bool ShowCertificateCommand::parse(const std::vector<std::string>& opts)
{
po::options_description desc("Available options");
desc.add_options()
("help", "Print out available options.")
("certificate", po::value<std::string>(&certificate_path)->required(), "Certificate to show.")
;
po::positional_options_description pos;
pos.add("certificate", 1);
po::variables_map vm;
po::store(po::command_line_parser(opts).options(desc).positional(pos).run(), vm);
if (vm.count("help")) {
std::cerr << desc << std::endl;
return false;
}
try {
po::notify(vm);
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl << std::endl << desc << std::endl;
return false;
}
return true;
}
int ShowCertificateCommand::execute()
{
v2::Certificate cert = v2::load_certificate_from_file(certificate_path);
// subject info
std::cout << "Subject: ";
if (cert.subject_info.subject_type == v2::SubjectType::Enrollment_Credential) {
std::cout << "Enrollment Credential";
} else if (cert.subject_info.subject_type == v2::SubjectType::Authorization_Ticket) {
std::cout << "Authorization Ticket";
} else if (cert.subject_info.subject_type == v2::SubjectType::Authorization_Authority) {
std::cout << "Authorization Authority";
} else if (cert.subject_info.subject_type == v2::SubjectType::Enrollment_Authority) {
std::cout << "Enrollment Authority";
} else if (cert.subject_info.subject_type == v2::SubjectType::Root_CA) {
std::cout << "Root Authority";
} else if (cert.subject_info.subject_type == v2::SubjectType::CRL_Signer) {
std::cout << "CRL Signer";
}
if (cert.subject_info.subject_name.size() > 0) {
std::string subject_name(reinterpret_cast<const char*>(&cert.subject_info.subject_name[0]), cert.subject_info.subject_name.size());
std::cout << " (" << subject_name << ")";
}
std::cout << std::endl;
{
HashedId8 cert_id = calculate_hash(cert);
std::string cert_id_string(reinterpret_cast<const char*>(&cert_id[0]), cert_id.size());
std::cout << "Digest: " << boost::algorithm::hex(cert_id_string) << " (SHA-256)" << std::endl;
}
// signer info
std::cout << "Signer: ";
v2::SignerInfoType signer_type = get_type(cert.signer_info);
if (signer_type == v2::SignerInfoType::Self) {
std::cout << "Self-Signed";
} else if (signer_type == v2::SignerInfoType::Certificate_Digest_With_SHA256) {
HashedId8 signer = boost::get<HashedId8>(cert.signer_info);
std::string signer_id(reinterpret_cast<const char*>(&signer[0]), signer.size());
std::cout << boost::algorithm::hex(signer_id) << " (SHA-256)";
} else {
std::cout << "Unknown (" << static_cast<int>(signer_type) << ")";
}
std::cout << std::endl;
// subject attributes
std::cout << std::endl;
unsigned certificate_application_ids = 0;
for (auto& subject_attr : cert.subject_attributes) {
v2::SubjectAttributeType attr_type = get_type(subject_attr);
if (attr_type == v2::SubjectAttributeType::Verification_Key) {
std::cout << "Verification Key:" << std::endl;
print_public_key(boost::get<v2::VerificationKey>(subject_attr).key, " - ");
std::cout << std::endl;
} else if (attr_type == v2::SubjectAttributeType::Encryption_Key) {
std::cout << "Encryption Key:" << std::endl;
print_public_key(boost::get<v2::EncryptionKey>(subject_attr).key, " - ");
std::cout << std::endl;
} else if (attr_type == v2::SubjectAttributeType::Reconstruction_Value) {
std::cout << "Reconstruction Value:" << std::endl;
print_ecc_point(boost::get<EccPoint>(subject_attr), " - ");
std::cout << std::endl;
} else if (attr_type == v2::SubjectAttributeType::Assurance_Level) {
v2::SubjectAssurance assurance = boost::get<v2::SubjectAssurance>(subject_attr);
std::cout << "Assurance: " << (assurance.raw & assurance.assurance_mask);
std::cout << " with a confidence of " << (assurance.raw & assurance.confidence_mask);
std::cout << std::endl << std::endl;
} else if (attr_type == v2::SubjectAttributeType::ITS_AID_List) {
std::list<v2::IntX> its_application_ids = boost::get<std::list<v2::IntX>>(subject_attr);
std::cout << "ITS Application IDs:" << std::endl;
if (its_application_ids.size() == 0) {
std::cout << "None";
} else {
for (auto& its_application_id : its_application_ids) {
certificate_application_ids++;
std::cout << " - ";
if (its_application_id == aid::CA) {
std::cout << "36 (CA-Basic service)";
} else if (its_application_id == aid::DEN) {
std::cout << "37 (DEN-Basic service)";
} else {
std::cout << its_application_id.get();
}
std::cout << std::endl;
}
}
std::cout << std::endl;
} else if (attr_type == v2::SubjectAttributeType::ITS_AID_SSP_List) {
std::list<v2::ItsAidSsp> its_service_specific_permissions = boost::get<std::list<v2::ItsAidSsp>>(subject_attr);
for (auto& its_ssp : its_service_specific_permissions) {
if (its_ssp.its_aid == aid::CA) {
std::cout << "CA - ITS Service Specific Permissions:" << std::endl;
ByteBuffer& ssp = its_ssp.service_specific_permissions;
if (ssp.size() == 0) {
std::cerr << "Invalid service specific permissions for CA" << std::endl;
continue;
}
// See final draft ETSI EN 302 637-2 V1.3.1 (2014-09)
if (ssp[0] == 0) {
if (ssp.size() != 1) {
std::cout << " - Warning: Length of SSP is expected to be 1, but was " << ssp.size() << std::endl;
} else {
std::cout << " - No version, shall be used only for testing." << std::endl;
}
} else if (ssp[0] == 1) {
if (ssp.size() != 3) {
std::cout << " - Warning: Length of SSP is expected to be 3, but was " << ssp.size() << std::endl;
} else {
CamPermissions ssp_decoded = CamPermissions::decode(ssp);
for (auto permission : ssp_decoded.permissions()) {
std::cout << " - " << stringify(permission) << "\n";
}
}
} else {
std::cout << " - Reserved for future usage and not implemented." << std::endl;
}
std::cout << std::endl;
}
}
}
}
if (certificate_application_ids == 0) {
std::cout << "Warning: Certificate doesn't contain any application IDs." << std::endl << std::endl;
}
// validity restrictions
const boost::posix_time::ptime epoch {
boost::gregorian::date(2004, 1, 1),
boost::posix_time::milliseconds(0)
};
unsigned certificate_time_constraints = 0;
for (auto& validity_restriction : cert.validity_restriction) {
v2::ValidityRestrictionType restriction_type = get_type(validity_restriction);
if (restriction_type == v2::ValidityRestrictionType::Time_End) {
certificate_time_constraints++;
boost::posix_time::ptime time_end = epoch + boost::posix_time::seconds(boost::get<v2::EndValidity>(validity_restriction));
std::cout << "Validity ends " << time_end << std::endl;
} else if (restriction_type == v2::ValidityRestrictionType::Time_Start_And_End) {
certificate_time_constraints++;
v2::StartAndEndValidity start_and_end = boost::get<v2::StartAndEndValidity>(validity_restriction);
boost::posix_time::ptime time_start = epoch + boost::posix_time::seconds(start_and_end.start_validity);
boost::posix_time::ptime time_end = epoch + boost::posix_time::seconds(start_and_end.end_validity);
std::cout << "Validity starts " << time_start << " and ends " << time_end << std::endl;
} else if (restriction_type == v2::ValidityRestrictionType::Time_Start_And_Duration) {
certificate_time_constraints++;
v2::StartAndDurationValidity start_and_duration = boost::get<v2::StartAndDurationValidity>(validity_restriction);
boost::posix_time::ptime time_start = epoch + boost::posix_time::seconds(start_and_duration.start_validity);
boost::posix_time::ptime time_end = epoch + boost::posix_time::seconds(start_and_duration.duration.to_seconds().count());
std::cout << "Validity starts " << time_start << " and ends " << time_end << std::endl;
}
}
if (certificate_time_constraints == 0) {
std::cout << "Warning: Certificate doesn't have any time based validity restriction." << std::endl;
} else if (certificate_time_constraints > 1) {
std::cout << "Warning: Certificate has multiple time based validity restrictions." << std::endl;
}
std::cout << std::endl;
bool certificate_region_constraints = false;
for (auto& validity_restriction : cert.validity_restriction) {
v2::ValidityRestrictionType restriction_type = get_type(validity_restriction);
if (restriction_type == v2::ValidityRestrictionType::Region) {
certificate_region_constraints = true;
v2::GeographicRegion region = boost::get<v2::GeographicRegion>(validity_restriction);
std::cout << "This certificate is regionally restricted by ";
v2::RegionType region_type = get_type(region);
if (region_type == v2::RegionType::None) {
std::cout << "nothing";
} else if (region_type == v2::RegionType::Circle) {
std::cout << "a circle";
} else if (region_type == v2::RegionType::Rectangle) {
std::cout << "a set of rectangles";
} else if (region_type == v2::RegionType::Polygon) {
std::cout << "a polygon";
} else if (region_type == v2::RegionType::ID) {
std::cout << "an identified region";
}
std::cout << "." << std::endl;
}
}
if (!certificate_region_constraints) {
std::cout << "This certificate doesn't have any regional restriction." << std::endl;
}
return 0;
}