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.
This commit is contained in:
Ashin Walpola
2026-09-24 10:56:05 +02:00
parent 2f60623e18
commit d107534eb2
9781 changed files with 1560475 additions and 17 deletions
@@ -0,0 +1,27 @@
if(NOT TARGET Boost::program_options)
message(STATUS "Skip build of certify-pqc because Boost::program_options is unavailable")
return()
endif()
if(NOT TARGET CryptoPP::CryptoPP)
message(STATUS "Skip build of certify-pqc because CryptoPP is unavailable")
return()
endif()
add_executable(certify-pqc
certificate_builder.cpp
files.cpp
main.cpp
)
target_include_directories(certify-pqc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(certify-pqc PRIVATE Boost::program_options vanetza)
install(TARGETS certify-pqc RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
if(BUILD_TESTS AND TARGET certify)
add_test(NAME CertifyPqcWorkflow
COMMAND ${CMAKE_COMMAND}
-DCERTIFY=$<TARGET_FILE:certify>
-DCERTIFY_PQC=$<TARGET_FILE:certify-pqc>
-DWORK_DIRECTORY=${PROJECT_BINARY_DIR}/tests/certify-pqc-workflow
-P ${CMAKE_CURRENT_SOURCE_DIR}/test_workflow.cmake)
endif()
@@ -0,0 +1,337 @@
#include "certificate_builder.hpp"
#include <vanetza/asn1/security_profile.hpp>
#include VANETZA_ASN1_SECURITY_HEADER(Certificate.h)
#include VANETZA_ASN1_SECURITY_HEADER(PsidGroupPermissions.h)
#include <vanetza/security/backend.hpp>
#include <vanetza/security/ecc_point.hpp>
#include <vanetza/security/pqc/hybrid_certificate.hpp>
#include <vanetza/security/private_key.hpp>
#include <vanetza/security/v2/basic_elements.hpp>
#include <vanetza/security/v3/asn1_conversions.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <array>
#include <limits>
#include <stdexcept>
#include <utility>
namespace vanetza
{
namespace security
{
namespace pqc
{
namespace certificate_builder
{
namespace
{
void assign_octets(OCTET_STRING_t& destination, const void* source, std::size_t size)
{
if (size > static_cast<std::size_t>(std::numeric_limits<int>::max()) ||
OCTET_STRING_fromBuf(
&destination, static_cast<const char*>(source), static_cast<int>(size)) != 0) {
throw std::runtime_error("cannot allocate ASN.1 octet string");
}
}
::vanetza::security::PrivateKey convert_private_key(const ecdsa256::PrivateKey& input)
{
::vanetza::security::PrivateKey output;
output.type = KeyType::NistP256;
output.key.assign(input.key.begin(), input.key.end());
return output;
}
void set_verification_key(v3::Certificate& certificate, const ecdsa256::PublicKey& public_key)
{
auto& indicator = certificate->toBeSigned.verifyKeyIndicator;
indicator.present = Vanetza_Security_VerificationKeyIndicator_PR_verificationKey;
auto& verification_key = indicator.choice.verificationKey;
verification_key.present = Vanetza_Security_PublicVerificationKey_PR_ecdsaNistP256;
verification_key.choice.ecdsaNistP256 =
v3::to_asn1(compress_public_key(public_key));
}
void set_encryption_key(v3::Certificate& certificate, const ecdsa256::PublicKey& public_key)
{
certificate->toBeSigned.encryptionKey =
vanetza::asn1::allocate<v3::asn1::PublicEncryptionKey>();
auto& encryption_key = certificate->toBeSigned.encryptionKey->publicKey;
encryption_key.present = Vanetza_Security_BasePublicEncryptionKey_PR_eciesNistP256;
encryption_key.choice.eciesNistP256 =
v3::to_asn1(compress_public_key(public_key));
}
void set_issuer(v3::Certificate& certificate, const v3::Certificate* issuer)
{
if (!issuer) {
certificate->issuer.present = Vanetza_Security_IssuerIdentifier_PR_self;
certificate->issuer.choice.self = Vanetza_Security_HashAlgorithm_sha256;
return;
}
const auto digest = issuer->calculate_digest();
if (!digest) {
throw std::invalid_argument("issuer certificate has no canonical digest");
}
certificate->issuer.present = Vanetza_Security_IssuerIdentifier_PR_sha256AndDigest;
assign_octets(
certificate->issuer.choice.sha256AndDigest,
digest->data(), digest->size());
}
void set_subject_name(v3::Certificate& certificate, const std::string& name)
{
if (name.empty()) {
throw std::invalid_argument("CA subject name must not be empty");
}
certificate->toBeSigned.id.present = Vanetza_Security_CertificateId_PR_name;
assign_octets(certificate->toBeSigned.id.choice.name, name.data(), name.size());
}
void set_validity(
v3::Certificate& certificate, Clock::time_point now, int validity_days)
{
constexpr int maximum_days = std::numeric_limits<unsigned short>::max() / 24;
if (validity_days < 1 || validity_days > maximum_days) {
throw std::invalid_argument("validity must be between 1 and 2730 days");
}
certificate->toBeSigned.validityPeriod.start =
v2::convert_time32(now - std::chrono::hours(1));
certificate->toBeSigned.validityPeriod.duration.present =
Vanetza_Security_Duration_PR_hours;
certificate->toBeSigned.validityPeriod.duration.choice.hours = validity_days * 24;
}
void initialize_certificate(
v3::Certificate& certificate, const ecdsa256::PublicKey& subject_key,
const v3::Certificate* issuer, int validity_days, Clock::time_point now)
{
certificate->version = 3;
certificate->type = Vanetza_Security_CertificateType_explicit;
set_issuer(certificate, issuer);
static const std::array<char, 3> craca_id {{ 0, 0, 0 }};
assign_octets(certificate->toBeSigned.cracaId, craca_id.data(), craca_id.size());
certificate->toBeSigned.crlSeries = 0;
set_validity(certificate, now, validity_days);
set_verification_key(certificate, subject_key);
}
std::vector<ItsAid> effective_issue_aids(const std::vector<ItsAid>& aids)
{
if (!aids.empty()) {
return aids;
}
return { aid::CA, aid::DEN, aid::CP, aid::GN_MGMT, aid::IPV6_ROUTING };
}
std::vector<ItsAid> effective_application_aids(const std::vector<ItsAid>& aids)
{
return aids.empty() ? std::vector<ItsAid> { aid::CA, aid::DEN } : aids;
}
void add_issue_permission_for_aid(
v3::asn1::PsidGroupPermissions* group, ItsAid application_id)
{
switch (application_id) {
case aid::CA:
v3::add_psid_group_permission(
group, application_id, { 0x01, 0xff, 0xfc }, { 0xff, 0x00, 0x03 });
break;
case aid::DEN:
v3::add_psid_group_permission(
group, application_id,
{ 0x01, 0xff, 0xff, 0xff }, { 0xff, 0x00, 0x00, 0x00 });
break;
case aid::CP:
v3::add_psid_group_permission(group, application_id, { 0x00 }, { 0xff });
break;
case aid::TLM:
v3::add_psid_group_permission(group, application_id, { 0x01, 0xe0 }, { 0xff, 0x1f });
break;
case aid::RLT:
v3::add_psid_group_permission(group, application_id, { 0x01, 0xc0 }, { 0xff, 0x3f });
break;
case aid::IVI:
v3::add_psid_group_permission(
group, application_id,
{ 0x01, 0xff, 0xff, 0xff, 0xff, 0xf8 },
{ 0xff, 0x00, 0x00, 0x00, 0x00, 0x07 });
break;
case aid::TLC_R:
v3::add_psid_group_permission(
group, application_id,
{ 0x02, 0xff, 0xff, 0xe0 }, { 0xff, 0x00, 0x00, 0x1f });
break;
case aid::GN_MGMT:
case aid::IPV6_ROUTING:
default:
v3::add_psid_group_permission(group, application_id, { 0x00 }, { 0xff });
break;
}
}
void add_issue_permissions(v3::Certificate& certificate, const std::vector<ItsAid>& aids)
{
auto* group = vanetza::asn1::allocate<v3::asn1::PsidGroupPermissions>();
group->subjectPermissions.present = Vanetza_Security_SubjectPermissions_PR_explicit;
for (ItsAid application_id : effective_issue_aids(aids)) {
add_issue_permission_for_aid(group, application_id);
}
certificate.add_cert_issue_permission(group);
}
void add_application_permissions(v3::Certificate& certificate, const std::vector<ItsAid>& aids)
{
for (ItsAid application_id : effective_application_aids(aids)) {
if (application_id == aid::CA) {
certificate.add_app_permission(application_id, ByteBuffer { 1, 0, 0 });
} else if (application_id == aid::DEN) {
certificate.add_app_permission(application_id, ByteBuffer { 1, 0, 0, 0 });
} else {
certificate.add_app_permission(application_id, {});
}
}
}
v3::Certificate canonical_certificate(v3::Certificate certificate)
{
auto canonical = certificate.canonicalize();
if (!canonical) {
throw std::runtime_error("generated certificate cannot be canonicalized");
}
std::string error;
if (!canonical->validate(error)) {
throw std::runtime_error("generated certificate violates ASN.1 constraints: " + error);
}
return std::move(*canonical);
}
} // namespace
v3::Certificate build_ecc_root_certificate(
::vanetza::security::Backend& ecc_backend, const ecdsa256::KeyPair& subject_key,
const CertificateParameters& parameters, Clock::time_point now)
{
v3::Certificate certificate;
initialize_certificate(
certificate, subject_key.public_key, nullptr, parameters.validity_days, now);
set_subject_name(certificate, parameters.subject_name);
add_issue_permissions(certificate, parameters.application_ids);
certificate.add_app_permission(aid::CRL, ByteBuffer { 1 });
certificate.add_app_permission(aid::CTL, ByteBuffer { 0x18 });
sign_primary_certificate(
certificate, nullptr, ecc_backend, convert_private_key(subject_key.private_key));
return canonical_certificate(std::move(certificate));
}
v3::Certificate build_hybrid_root_certificate(
::vanetza::security::Backend& ecc_backend, Backend& pqc_backend,
const ecdsa256::KeyPair& subject_key, const KeyPair& subject_pqc_key,
const CertificateParameters& parameters, Clock::time_point now)
{
v3::Certificate certificate;
initialize_certificate(
certificate, subject_key.public_key, nullptr, parameters.validity_days, now);
set_subject_name(certificate, parameters.subject_name);
add_issue_permissions(certificate, parameters.application_ids);
certificate.add_app_permission(aid::CRL, ByteBuffer { 1 });
certificate.add_app_permission(aid::CTL, ByteBuffer { 0x18 });
set_alternative_public_key(certificate, subject_pqc_key.public_key);
sign_alternative_certificate(
certificate, nullptr, ecc_backend, pqc_backend, subject_pqc_key.private_key);
sign_primary_certificate(
certificate, nullptr, ecc_backend, convert_private_key(subject_key.private_key));
return canonical_certificate(std::move(certificate));
}
v3::Certificate build_ecc_authorization_authority_certificate(
::vanetza::security::Backend& ecc_backend, const ecdsa256::KeyPair& issuer_key,
const v3::Certificate& issuer_certificate,
const ecdsa256::PublicKey& subject_key,
const CertificateParameters& parameters, Clock::time_point now)
{
v3::Certificate certificate;
initialize_certificate(
certificate, subject_key, &issuer_certificate, parameters.validity_days, now);
set_subject_name(certificate, parameters.subject_name);
add_issue_permissions(certificate, parameters.application_ids);
set_encryption_key(certificate, subject_key);
sign_primary_certificate(
certificate, &issuer_certificate, ecc_backend,
convert_private_key(issuer_key.private_key));
return canonical_certificate(std::move(certificate));
}
v3::Certificate build_hybrid_authorization_authority_certificate(
::vanetza::security::Backend& ecc_backend, Backend& pqc_backend,
const ecdsa256::KeyPair& issuer_key, const PrivateKey& issuer_pqc_key,
const v3::Certificate& issuer_certificate,
const ecdsa256::PublicKey& subject_key, const PublicKey& subject_pqc_key,
const CertificateParameters& parameters, Clock::time_point now)
{
v3::Certificate certificate;
initialize_certificate(
certificate, subject_key, &issuer_certificate, parameters.validity_days, now);
set_subject_name(certificate, parameters.subject_name);
add_issue_permissions(certificate, parameters.application_ids);
set_encryption_key(certificate, subject_key);
set_alternative_public_key(certificate, subject_pqc_key);
sign_alternative_certificate(
certificate, &issuer_certificate, ecc_backend, pqc_backend, issuer_pqc_key);
sign_primary_certificate(
certificate, &issuer_certificate, ecc_backend,
convert_private_key(issuer_key.private_key));
return canonical_certificate(std::move(certificate));
}
v3::Certificate build_ecc_authorization_ticket(
::vanetza::security::Backend& ecc_backend, const ecdsa256::KeyPair& issuer_key,
const v3::Certificate& issuer_certificate,
const ecdsa256::PublicKey& subject_key,
const CertificateParameters& parameters, Clock::time_point now)
{
v3::Certificate certificate;
initialize_certificate(
certificate, subject_key, &issuer_certificate, parameters.validity_days, now);
certificate->toBeSigned.id.present = Vanetza_Security_CertificateId_PR_none;
add_application_permissions(certificate, parameters.application_ids);
sign_primary_certificate(
certificate, &issuer_certificate, ecc_backend,
convert_private_key(issuer_key.private_key));
return canonical_certificate(std::move(certificate));
}
v3::Certificate build_hybrid_authorization_ticket(
::vanetza::security::Backend& ecc_backend, Backend& pqc_backend,
const ecdsa256::KeyPair& issuer_key, const PrivateKey& issuer_pqc_key,
const v3::Certificate& issuer_certificate,
const ecdsa256::PublicKey& subject_key,
const CertificateParameters& parameters, Clock::time_point now)
{
v3::Certificate certificate;
initialize_certificate(
certificate, subject_key, &issuer_certificate, parameters.validity_days, now);
certificate->toBeSigned.id.present = Vanetza_Security_CertificateId_PR_none;
add_application_permissions(certificate, parameters.application_ids);
sign_alternative_certificate(
certificate, &issuer_certificate, ecc_backend, pqc_backend, issuer_pqc_key);
sign_primary_certificate(
certificate, &issuer_certificate, ecc_backend,
convert_private_key(issuer_key.private_key));
return canonical_certificate(std::move(certificate));
}
} // namespace certificate_builder
} // namespace pqc
} // namespace security
} // namespace vanetza
@@ -0,0 +1,67 @@
#pragma once
#include <vanetza/common/clock.hpp>
#include <vanetza/common/its_aid.hpp>
#include <vanetza/security/ecdsa256.hpp>
#include <vanetza/security/pqc/fndsa512.hpp>
#include <vanetza/security/v3/certificate.hpp>
#include <string>
#include <vector>
namespace vanetza
{
namespace security
{
class Backend;
namespace pqc
{
namespace certificate_builder
{
struct CertificateParameters
{
std::string subject_name;
int validity_days = 1;
std::vector<ItsAid> application_ids;
};
v3::Certificate build_ecc_root_certificate(
::vanetza::security::Backend&, const ecdsa256::KeyPair&,
const CertificateParameters&, Clock::time_point now);
v3::Certificate build_hybrid_root_certificate(
::vanetza::security::Backend&, Backend&, const ecdsa256::KeyPair&, const KeyPair&,
const CertificateParameters&, Clock::time_point now);
v3::Certificate build_ecc_authorization_authority_certificate(
::vanetza::security::Backend&, const ecdsa256::KeyPair& issuer_key,
const v3::Certificate& issuer_certificate,
const ecdsa256::PublicKey& subject_key,
const CertificateParameters&, Clock::time_point now);
v3::Certificate build_hybrid_authorization_authority_certificate(
::vanetza::security::Backend&, Backend&,
const ecdsa256::KeyPair& issuer_key, const PrivateKey& issuer_pqc_key,
const v3::Certificate& issuer_certificate,
const ecdsa256::PublicKey& subject_key, const PublicKey& subject_pqc_key,
const CertificateParameters&, Clock::time_point now);
v3::Certificate build_ecc_authorization_ticket(
::vanetza::security::Backend&, const ecdsa256::KeyPair& issuer_key,
const v3::Certificate& issuer_certificate,
const ecdsa256::PublicKey& subject_key,
const CertificateParameters&, Clock::time_point now);
v3::Certificate build_hybrid_authorization_ticket(
::vanetza::security::Backend&, Backend&,
const ecdsa256::KeyPair& issuer_key, const PrivateKey& issuer_pqc_key,
const v3::Certificate& issuer_certificate,
const ecdsa256::PublicKey& subject_key,
const CertificateParameters&, Clock::time_point now);
} // namespace certificate_builder
} // namespace pqc
} // namespace security
} // namespace vanetza
@@ -0,0 +1,103 @@
#include "files.hpp"
#include <vanetza/common/byte_buffer.hpp>
#include <vanetza/security/v2/persistence.hpp>
#include <fstream>
#include <iterator>
#include <stdexcept>
#include <sys/stat.h>
namespace
{
using vanetza::ByteBuffer;
ByteBuffer read_file(const std::string& path)
{
std::ifstream stream(path, std::ios::binary);
if (!stream) {
throw std::runtime_error("cannot open file for reading: " + path);
}
return ByteBuffer(std::istreambuf_iterator<char>(stream), {});
}
void write_file(const std::string& path, const ByteBuffer& data)
{
std::ofstream stream(path, std::ios::binary | std::ios::trunc);
if (!stream || !stream.write(
reinterpret_cast<const char*>(data.data()), data.size())) {
throw std::runtime_error("cannot write file: " + path);
}
}
template<typename T>
T load_exact(const std::string& path, std::size_t expected_size, const char* description)
{
T output { read_file(path) };
if (output.bytes.size() != expected_size) {
throw std::runtime_error(
std::string(description) + " has an invalid size in: " + path);
}
return output;
}
} // namespace
vanetza::security::ecdsa256::KeyPair load_ecc_key_pair(const std::string& path)
{
return vanetza::security::v2::load_private_key_from_file(path);
}
vanetza::security::pqc::KeyPair load_pqc_key_pair(const std::string& base_path)
{
using namespace vanetza::security::pqc;
KeyPair key_pair;
key_pair.private_key = load_exact<PrivateKey>(
base_path + ".pqc.key", fndsa512_private_key_size, "FN-DSA-512 private key");
key_pair.public_key = load_exact<PublicKey>(
base_path + ".pqc.pub", fndsa512_public_key_size, "FN-DSA-512 public key");
return key_pair;
}
vanetza::security::pqc::PrivateKey load_pqc_private_key(const std::string& base_path)
{
using namespace vanetza::security::pqc;
return load_exact<PrivateKey>(
base_path + ".pqc.key", fndsa512_private_key_size, "FN-DSA-512 private key");
}
void save_pqc_key_pair(
const std::string& base_path, const vanetza::security::pqc::KeyPair& key_pair)
{
using namespace vanetza::security::pqc;
if (key_pair.private_key.bytes.size() != fndsa512_private_key_size ||
key_pair.public_key.bytes.size() != fndsa512_public_key_size) {
throw std::invalid_argument("cannot save malformed FN-DSA-512 key pair");
}
const std::string private_path = base_path + ".pqc.key";
write_file(private_path, key_pair.private_key.bytes);
if (::chmod(private_path.c_str(), S_IRUSR | S_IWUSR) != 0) {
throw std::runtime_error("cannot restrict private-key permissions: " + private_path);
}
write_file(base_path + ".pqc.pub", key_pair.public_key.bytes);
}
vanetza::security::v3::Certificate load_v3_certificate(const std::string& path)
{
vanetza::security::v3::Certificate certificate;
const auto encoded = read_file(path);
if (encoded.empty() || !certificate.decode(encoded)) {
throw std::runtime_error("cannot decode V3 certificate: " + path);
}
return certificate;
}
void save_v3_certificate(
const std::string& path, const vanetza::security::v3::Certificate& certificate)
{
std::string error;
if (!certificate.validate(error)) {
throw std::runtime_error("refusing to save invalid V3 certificate: " + error);
}
write_file(path, certificate.encode());
}
@@ -0,0 +1,17 @@
#pragma once
#include <vanetza/security/ecdsa256.hpp>
#include <vanetza/security/pqc/fndsa512.hpp>
#include <vanetza/security/v3/certificate.hpp>
#include <string>
vanetza::security::ecdsa256::KeyPair load_ecc_key_pair(const std::string& path);
vanetza::security::pqc::KeyPair load_pqc_key_pair(const std::string& base_path);
vanetza::security::pqc::PrivateKey load_pqc_private_key(const std::string& base_path);
void save_pqc_key_pair(
const std::string& base_path, const vanetza::security::pqc::KeyPair&);
vanetza::security::v3::Certificate load_v3_certificate(const std::string& path);
void save_v3_certificate(
const std::string& path, const vanetza::security::v3::Certificate&);
@@ -0,0 +1,423 @@
#include "certificate_builder.hpp"
#include "files.hpp"
#include <vanetza/common/manual_runtime.hpp>
#include <vanetza/security/backend.hpp>
#include <vanetza/security/pqc/fndsa512.hpp>
#include <vanetza/security/pqc/hybrid_certificate.hpp>
#include <vanetza/security/pqc/hybrid_certificate_validator.hpp>
#include <vanetza/security/v3/issuer_memory_lookup.hpp>
#include <vanetza/security/v3/trust_store.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/program_options.hpp>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
namespace po = boost::program_options;
using namespace vanetza;
using namespace vanetza::security;
using namespace vanetza::security::v3;
namespace builder = vanetza::security::pqc::certificate_builder;
namespace
{
enum class CertificateProfile
{
Ecc,
Hybrid
};
CertificateProfile parse_profile(const std::string& profile)
{
if (profile == "ecc") {
return CertificateProfile::Ecc;
}
if (profile == "hybrid") {
return CertificateProfile::Hybrid;
}
throw std::invalid_argument("unknown certificate profile: " + profile);
}
void validate_pqc_argument(
CertificateProfile profile, const std::string& value, const char* option)
{
if (profile == CertificateProfile::Hybrid && value.empty()) {
throw std::invalid_argument(std::string(option) + " is required for the hybrid profile");
}
if (profile == CertificateProfile::Ecc && !value.empty()) {
throw std::invalid_argument(std::string(option) + " is not valid for the ECC profile");
}
}
Clock::time_point now()
{
return Clock::at(boost::posix_time::microsec_clock::universal_time());
}
std::vector<ItsAid> convert_aids(const std::vector<unsigned>& input)
{
return std::vector<ItsAid>(input.begin(), input.end());
}
std::vector<std::string> command_arguments(int argc, const char* argv[])
{
return std::vector<std::string>(argv + 2, argv + argc);
}
po::variables_map parse(
const std::vector<std::string>& arguments, const po::options_description& options,
const po::positional_options_description& positional = {})
{
po::variables_map variables;
po::store(po::command_line_parser(arguments)
.options(options).positional(positional).run(), variables);
if (!variables.count("help")) {
po::notify(variables);
}
return variables;
}
int generate_key(const std::vector<std::string>& arguments)
{
std::string output;
po::options_description options("Generate an FN-DSA-512 key pair");
options.add_options()
("help,h", "Show this help")
("output", po::value<std::string>(&output)->required(),
"Output base path; .pqc.key and .pqc.pub are appended");
po::positional_options_description positional;
positional.add("output", 1);
const auto variables = parse(arguments, options, positional);
if (variables.count("help")) {
std::cout << options << '\n';
return 0;
}
auto backend = pqc::create_fndsa512_backend();
save_pqc_key_pair(output, backend->generate_key_pair());
std::cout << "Wrote " << output << ".pqc.key and " << output << ".pqc.pub\n";
return 0;
}
builder::CertificateParameters certificate_parameters(
const std::string& name, int days, const std::vector<unsigned>& aids)
{
builder::CertificateParameters parameters;
parameters.subject_name = name;
parameters.validity_days = days;
parameters.application_ids = convert_aids(aids);
return parameters;
}
int generate_root(const std::vector<std::string>& arguments)
{
std::string output;
std::string subject_key;
std::string subject_pqc_key;
std::string profile_name = "hybrid";
std::string subject_name = "Hello World Root-CA";
int days = 365;
std::vector<unsigned> aids;
po::options_description options("Generate a V3 Root CA certificate");
options.add_options()
("help,h", "Show this help")
("output,o", po::value<std::string>(&output)->required(), "Output certificate")
("subject-key", po::value<std::string>(&subject_key)->required(), "ECC private key")
("profile", po::value<std::string>(&profile_name)->default_value("hybrid"),
"Certificate profile: ecc or hybrid")
("subject-pqc-key", po::value<std::string>(&subject_pqc_key),
"FN-DSA key base path")
("subject-name", po::value<std::string>(&subject_name), "Certificate subject name")
("days", po::value<int>(&days), "Validity in days")
("aid", po::value<std::vector<unsigned>>(&aids)->multitoken(), "Permitted ITS-AIDs");
const auto variables = parse(arguments, options);
if (variables.count("help")) {
std::cout << options << '\n';
return 0;
}
const auto profile = parse_profile(profile_name);
validate_pqc_argument(profile, subject_pqc_key, "--subject-pqc-key");
auto ecc_backend = create_backend_or_throw("default");
const auto subject_ecc = load_ecc_key_pair(subject_key);
Certificate certificate;
if (profile == CertificateProfile::Hybrid) {
auto pqc_backend = pqc::create_fndsa512_backend();
certificate = builder::build_hybrid_root_certificate(
*ecc_backend, *pqc_backend, subject_ecc, load_pqc_key_pair(subject_pqc_key),
certificate_parameters(subject_name, days, aids), now());
} else {
certificate = builder::build_ecc_root_certificate(
*ecc_backend, subject_ecc,
certificate_parameters(subject_name, days, aids), now());
}
save_v3_certificate(output, certificate);
std::cout << "Wrote " << profile_name << " V3 Root CA certificate " << output << '\n';
return 0;
}
int generate_aa(const std::vector<std::string>& arguments)
{
std::string output;
std::string sign_key;
std::string sign_cert;
std::string sign_pqc_key;
std::string subject_key;
std::string subject_pqc_key;
std::string profile_name = "hybrid";
std::string subject_name = "Hello World Auth-CA";
int days = 180;
std::vector<unsigned> aids;
po::options_description options("Generate a V3 Authorization Authority certificate");
options.add_options()
("help,h", "Show this help")
("output,o", po::value<std::string>(&output)->required(), "Output certificate")
("sign-key", po::value<std::string>(&sign_key)->required(), "Issuer ECC private key")
("sign-cert", po::value<std::string>(&sign_cert)->required(), "Issuer certificate")
("profile", po::value<std::string>(&profile_name)->default_value("hybrid"),
"Certificate profile: ecc or hybrid")
("sign-pqc-key", po::value<std::string>(&sign_pqc_key),
"Issuer FN-DSA key base path")
("subject-key", po::value<std::string>(&subject_key)->required(), "Subject ECC private key")
("subject-pqc-key", po::value<std::string>(&subject_pqc_key),
"Subject FN-DSA key base path")
("subject-name", po::value<std::string>(&subject_name), "Certificate subject name")
("days", po::value<int>(&days), "Validity in days")
("aid", po::value<std::vector<unsigned>>(&aids)->multitoken(), "Permitted ITS-AIDs");
const auto variables = parse(arguments, options);
if (variables.count("help")) {
std::cout << options << '\n';
return 0;
}
const auto profile = parse_profile(profile_name);
validate_pqc_argument(profile, sign_pqc_key, "--sign-pqc-key");
validate_pqc_argument(profile, subject_pqc_key, "--subject-pqc-key");
auto ecc_backend = create_backend_or_throw("default");
const auto subject_ecc = load_ecc_key_pair(subject_key);
const auto issuer_ecc = load_ecc_key_pair(sign_key);
const auto issuer_certificate = load_v3_certificate(sign_cert);
Certificate certificate;
if (profile == CertificateProfile::Hybrid) {
auto pqc_backend = pqc::create_fndsa512_backend();
const auto subject_pqc = load_pqc_key_pair(subject_pqc_key);
certificate = builder::build_hybrid_authorization_authority_certificate(
*ecc_backend, *pqc_backend, issuer_ecc,
load_pqc_private_key(sign_pqc_key), issuer_certificate,
subject_ecc.public_key, subject_pqc.public_key,
certificate_parameters(subject_name, days, aids), now());
} else {
certificate = builder::build_ecc_authorization_authority_certificate(
*ecc_backend, issuer_ecc, issuer_certificate, subject_ecc.public_key,
certificate_parameters(subject_name, days, aids), now());
}
save_v3_certificate(output, certificate);
std::cout << "Wrote " << profile_name
<< " V3 Authorization Authority certificate " << output << '\n';
return 0;
}
int generate_ticket(const std::vector<std::string>& arguments)
{
std::string output;
std::string sign_key;
std::string sign_cert;
std::string sign_pqc_key;
std::string subject_key;
std::string profile_name = "hybrid";
int days = 7;
std::vector<unsigned> aids;
po::options_description options("Generate a V3 Authorization Ticket");
options.add_options()
("help,h", "Show this help")
("output,o", po::value<std::string>(&output)->required(), "Output certificate")
("sign-key", po::value<std::string>(&sign_key)->required(), "Issuer ECC private key")
("sign-cert", po::value<std::string>(&sign_cert)->required(), "Issuer certificate")
("profile", po::value<std::string>(&profile_name)->default_value("hybrid"),
"Certificate profile: ecc or hybrid")
("sign-pqc-key", po::value<std::string>(&sign_pqc_key),
"Issuer FN-DSA key base path")
("subject-key", po::value<std::string>(&subject_key)->required(), "Subject ECC private key")
("days", po::value<int>(&days), "Validity in days")
("aid", po::value<std::vector<unsigned>>(&aids)->multitoken(), "Permitted ITS-AIDs");
const auto variables = parse(arguments, options);
if (variables.count("help")) {
std::cout << options << '\n';
return 0;
}
const auto profile = parse_profile(profile_name);
validate_pqc_argument(profile, sign_pqc_key, "--sign-pqc-key");
auto ecc_backend = create_backend_or_throw("default");
const auto issuer_ecc = load_ecc_key_pair(sign_key);
const auto issuer_certificate = load_v3_certificate(sign_cert);
const auto subject_ecc = load_ecc_key_pair(subject_key);
Certificate certificate;
if (profile == CertificateProfile::Hybrid) {
auto pqc_backend = pqc::create_fndsa512_backend();
certificate = builder::build_hybrid_authorization_ticket(
*ecc_backend, *pqc_backend, issuer_ecc,
load_pqc_private_key(sign_pqc_key), issuer_certificate,
subject_ecc.public_key, certificate_parameters({}, days, aids), now());
} else {
certificate = builder::build_ecc_authorization_ticket(
*ecc_backend, issuer_ecc, issuer_certificate, subject_ecc.public_key,
certificate_parameters({}, days, aids), now());
}
save_v3_certificate(output, certificate);
std::cout << "Wrote " << profile_name << " V3 Authorization Ticket " << output << '\n';
return 0;
}
const char* material_state(pqc::MaterialState state)
{
switch (state) {
case pqc::MaterialState::None: return "none";
case pqc::MaterialState::Authority: return "authority (key + signature)";
case pqc::MaterialState::EndEntity: return "end entity (signature only)";
case pqc::MaterialState::Inconsistent: return "inconsistent";
}
return "unknown";
}
int show_certificate(const std::vector<std::string>& arguments)
{
std::string input;
po::options_description options("Show V3 certificate information");
options.add_options()
("help,h", "Show this help")
("certificate", po::value<std::string>(&input)->required(), "Certificate file");
po::positional_options_description positional;
positional.add("certificate", 1);
const auto variables = parse(arguments, options, positional);
if (variables.count("help")) {
std::cout << options << '\n';
return 0;
}
const auto certificate = load_v3_certificate(input);
const auto key = pqc::get_alternative_public_key(certificate);
const auto signature = pqc::get_alternative_signature(certificate);
std::cout << "Encoded size: " << certificate.encode().size() << " bytes\n"
<< "Alternative material: "
<< material_state(pqc::alternative_material_state(certificate)) << "\n"
<< "FN-DSA-512 public key: " << (key ? key->bytes.size() : 0) << " bytes\n"
<< "FN-DSA-512 signature: " << (signature ? signature->bytes.size() : 0)
<< " bytes\n";
return 0;
}
int verify_chain(const std::vector<std::string>& arguments)
{
std::string root_path;
std::string aa_path;
std::string ticket_path;
std::string profile_name = "hybrid";
unsigned application_id = aid::CA;
po::options_description options("Verify a V3 Root -> AA -> AT chain");
options.add_options()
("help,h", "Show this help")
("root", po::value<std::string>(&root_path)->required(), "Trusted Root certificate")
("aa", po::value<std::string>(&aa_path)->required(), "Authorization Authority certificate")
("ticket", po::value<std::string>(&ticket_path)->required(), "Authorization Ticket certificate")
("profile", po::value<std::string>(&profile_name)->default_value("hybrid"),
"Expected profile: ecc or hybrid")
("aid", po::value<unsigned>(&application_id), "ITS-AID to validate (default: 36)");
const auto variables = parse(arguments, options);
if (variables.count("help")) {
std::cout << options << '\n';
return 0;
}
const auto root = load_v3_certificate(root_path);
const auto aa = load_v3_certificate(aa_path);
const auto ticket = load_v3_certificate(ticket_path);
const auto profile = parse_profile(profile_name);
if (profile == CertificateProfile::Ecc &&
(pqc::alternative_material_state(root) != pqc::MaterialState::None ||
pqc::alternative_material_state(aa) != pqc::MaterialState::None ||
pqc::alternative_material_state(ticket) != pqc::MaterialState::None)) {
throw std::invalid_argument("ECC profile chain contains alternative PQC material");
}
auto ecc_backend = create_backend_or_throw("default");
auto pqc_backend = pqc::create_fndsa512_backend();
TrustStore trust_store;
trust_store.insert(root);
IssuerMemoryLookup issuer_lookup;
if (!issuer_lookup.insert(root) || !issuer_lookup.insert(aa)) {
throw std::runtime_error("Root or AA cannot be used as an issuer certificate");
}
ManualRuntime runtime(now());
pqc::HybridCertificateValidator validator;
validator.use_runtime(&runtime);
validator.disable_location_checks(true);
validator.use_issuer_lookup(&issuer_lookup);
validator.use_trust_store(&trust_store);
validator.use_backends(ecc_backend.get(), pqc_backend.get());
validator.use_verification_policy(profile == CertificateProfile::Hybrid ?
pqc::HybridCertificateValidator::VerificationPolicy::HybridRequired :
pqc::HybridCertificateValidator::VerificationPolicy::HybridIfPresent);
const auto verdict = validator.valid_for_signing(ticket, application_id);
if (verdict != CertificateValidator::Verdict::Valid) {
std::cerr << profile_name << " chain verification failed (verdict "
<< static_cast<int>(verdict) << ")\n";
return 1;
}
std::cout << profile_name << " V3 Root -> AA -> AT chain is valid\n";
return 0;
}
void print_usage(const char* executable)
{
std::cout << "Usage: " << executable << " COMMAND [OPTIONS]\n\n"
<< "Commands:\n"
<< " generate-key Generate an FN-DSA-512 key pair\n"
<< " generate-root Generate a V3 Root CA\n"
<< " generate-aa Generate a V3 Authorization Authority\n"
<< " generate-ticket Generate a V3 Authorization Ticket\n"
<< " show Show V3 certificate material\n"
<< " verify-chain Verify a Root -> AA -> AT chain\n";
}
} // namespace
int main(int argc, const char* argv[])
{
try {
if (argc < 2 || std::string(argv[1]) == "--help" || std::string(argv[1]) == "-h") {
print_usage(argv[0]);
return argc < 2 ? 1 : 0;
}
const std::string command = argv[1];
const auto arguments = command_arguments(argc, argv);
if (command == "generate-key") {
return generate_key(arguments);
}
if (command == "generate-root") {
return generate_root(arguments);
}
if (command == "generate-aa") {
return generate_aa(arguments);
}
if (command == "generate-ticket") {
return generate_ticket(arguments);
}
if (command == "show") {
return show_certificate(arguments);
}
if (command == "verify-chain") {
return verify_chain(arguments);
}
throw std::invalid_argument("unknown command: " + command);
} catch (const std::exception& error) {
std::cerr << "Error: " << error.what() << '\n';
return 1;
}
}
@@ -0,0 +1,105 @@
if(NOT CERTIFY OR NOT CERTIFY_PQC OR NOT WORK_DIRECTORY)
message(FATAL_ERROR "CERTIFY, CERTIFY_PQC and WORK_DIRECTORY are required")
endif()
file(REMOVE_RECURSE "${WORK_DIRECTORY}")
file(MAKE_DIRECTORY "${WORK_DIRECTORY}")
function(run)
execute_process(
COMMAND ${ARGV}
WORKING_DIRECTORY "${WORK_DIRECTORY}"
RESULT_VARIABLE result
OUTPUT_VARIABLE output
ERROR_VARIABLE error)
if(NOT result EQUAL 0)
string(JOIN " " command ${ARGV})
message(FATAL_ERROR "Command failed (${result}): ${command}\n${output}${error}")
endif()
endfunction()
function(run_fails)
execute_process(
COMMAND ${ARGV}
WORKING_DIRECTORY "${WORK_DIRECTORY}"
RESULT_VARIABLE result
OUTPUT_VARIABLE output
ERROR_VARIABLE error)
if(result EQUAL 0)
string(JOIN " " command ${ARGV})
message(FATAL_ERROR "Command unexpectedly succeeded: ${command}\n${output}${error}")
endif()
endfunction()
function(check_show certificate material public_key_size signature_size)
execute_process(
COMMAND "${CERTIFY_PQC}" show "${certificate}"
WORKING_DIRECTORY "${WORK_DIRECTORY}"
RESULT_VARIABLE result
OUTPUT_VARIABLE output
ERROR_VARIABLE error)
if(NOT result EQUAL 0)
message(FATAL_ERROR "Cannot inspect ${certificate}:\n${output}${error}")
endif()
set(expectations
"Alternative material: ${material}"
"FN-DSA-512 public key: ${public_key_size} bytes"
"FN-DSA-512 signature: ${signature_size} bytes")
foreach(expectation IN LISTS expectations)
string(FIND "${output}" "${expectation}" position)
if(position EQUAL -1)
message(FATAL_ERROR
"Unexpected material in ${certificate}: missing '${expectation}'\n${output}")
endif()
endforeach()
endfunction()
run("${CERTIFY}" generate-key root.key)
run("${CERTIFY}" generate-key aa.key)
run("${CERTIFY}" generate-key ticket.key)
run("${CERTIFY_PQC}" generate-key root)
run("${CERTIFY_PQC}" generate-key aa)
run("${CERTIFY_PQC}" generate-root
--profile ecc --output ecc-root.cert --subject-key root.key --days 365
--aid 36 141)
run("${CERTIFY_PQC}" generate-aa
--profile ecc --output ecc-aa.cert
--sign-key root.key --sign-cert ecc-root.cert
--subject-key aa.key --days 180
--aid 36 141)
run("${CERTIFY_PQC}" generate-ticket
--profile ecc --output ecc-ticket.cert
--sign-key aa.key --sign-cert ecc-aa.cert
--subject-key ticket.key --days 7
--aid 141)
run("${CERTIFY_PQC}" verify-chain --profile ecc
--root ecc-root.cert --aa ecc-aa.cert --ticket ecc-ticket.cert --aid 141)
check_show(ecc-root.cert "none" 0 0)
check_show(ecc-aa.cert "none" 0 0)
check_show(ecc-ticket.cert "none" 0 0)
run("${CERTIFY_PQC}" generate-root
--output root.cert --subject-key root.key --subject-pqc-key root --days 365
--aid 36 141)
run("${CERTIFY_PQC}" generate-aa
--output aa.cert
--sign-key root.key --sign-cert root.cert --sign-pqc-key root
--subject-key aa.key --subject-pqc-key aa --days 180
--aid 36 141)
run("${CERTIFY_PQC}" generate-ticket
--output ticket.cert
--sign-key aa.key --sign-cert aa.cert --sign-pqc-key aa
--subject-key ticket.key --days 7
--aid 141)
run("${CERTIFY_PQC}" verify-chain
--root root.cert --aa aa.cert --ticket ticket.cert --aid 141)
check_show(root.cert "authority (key + signature)" 897 666)
check_show(aa.cert "authority (key + signature)" 897 666)
check_show(ticket.cert "end entity (signature only)" 0 666)
run_fails("${CERTIFY_PQC}" verify-chain --profile hybrid
--root ecc-root.cert --aa ecc-aa.cert --ticket ecc-ticket.cert --aid 141)
run_fails("${CERTIFY_PQC}" verify-chain --profile ecc
--root root.cert --aa aa.cert --ticket ticket.cert --aid 141)