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.
338 lines
13 KiB
C++
338 lines
13 KiB
C++
#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
|