Files
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

1067 lines
38 KiB
C++

#include <vanetza/asn1/security_profile.hpp>
#include VANETZA_ASN1_SECURITY_HEADER(Certificate.h)
#include <vanetza/security/hash_algorithm.hpp>
#include <vanetza/security/sha.hpp>
#include <vanetza/security/v3/asn1_conversions.hpp>
#include <vanetza/security/v3/certificate.hpp>
#include <vanetza/security/v3/distance.hpp>
#include <vanetza/security/v3/geometry.hpp>
#include <boost/optional/optional.hpp>
#include <cassert>
#include <cstdlib>
#include <cstring>
namespace vanetza
{
namespace security
{
namespace v3
{
namespace
{
HashAlgorithm digest_hash_algorithm(const asn1::EtsiTs103097Certificate& cert);
bool copy_curve_point(PublicKey& to, const asn1::EccP256CurvePoint& from);
bool copy_curve_point(PublicKey& to, const asn1::EccP384CurvePoint& from);
ByteBuffer fetch_octets(const OCTET_STRING_t& octets);
ByteBuffer get_x_coordinate(const asn1::EccP256CurvePoint& point);
ByteBuffer get_x_coordinate(const asn1::EccP384CurvePoint& point);
bool is_compressed(const Vanetza_Security_EccP256CurvePoint& point);
bool is_compressed(const Vanetza_Security_EccP384CurvePoint& point);
bool is_signature_x_only(const Vanetza_Security_Signature_t& sig);
bool compress(Vanetza_Security_EccP256CurvePoint&);
bool compress(Vanetza_Security_EccP384CurvePoint&);
bool contains_permission(const Vanetza_Security_SequenceOfPsidSspRange_t& permissions, ItsAid aid);
bool equal(const asn1::TwoDLocation& lhs, const asn1::TwoDLocation& rhs);
bool equal(const asn1::RectangularRegion& lhs, const asn1::RectangularRegion& rhs);
bool equal(const asn1::SequenceOfRectangularRegion& lhs, const asn1::SequenceOfRectangularRegion& rhs);
bool is_within(const asn1::GeographicRegion& inner, const asn1::CircularRegion& outer);
bool is_within(const asn1::GeographicRegion& inner, const asn1::SequenceOfRectangularRegion& outer);
bool is_within(const asn1::GeographicRegion& inner, const asn1::PolygonalRegion& outer);
bool is_within(const asn1::GeographicRegion& inner, const asn1::GeographicRegion& outer);
bool make_x_only(Vanetza_Security_EccP256CurvePoint&);
bool make_x_only(Vanetza_Security_EccP384CurvePoint&);
bool make_signature_x_only(Vanetza_Security_Signature_t& sig);
} // namespace
CertificateView::CertificateView(const asn1::EtsiTs103097Certificate* cert) :
m_cert(cert)
{
}
Certificate::Certificate() :
Wrapper(asn_DEF_Vanetza_Security_EtsiTs103097Certificate),
CertificateView { content() }
{
assert(CertificateView::m_cert == Wrapper::m_struct);
}
Certificate::Certificate(const asn1::EtsiTs103097Certificate& cert) :
Wrapper(asn_DEF_Vanetza_Security_EtsiTs103097Certificate, &cert),
CertificateView { content() }
{
assert(CertificateView::m_cert == Wrapper::m_struct);
}
Certificate::Certificate(const Certificate& other) :
Wrapper(other), CertificateView(content())
{
assert(CertificateView::m_cert == Wrapper::m_struct);
}
Certificate& Certificate::operator=(const Certificate& other)
{
Wrapper::operator=(other);
CertificateView::m_cert = content();
assert(CertificateView::m_cert == Wrapper::m_struct);
return *this;
}
Certificate::Certificate(Certificate&& other) :
Wrapper(std::move(other)), CertificateView(content())
{
assert(CertificateView::m_cert == Wrapper::m_struct);
}
Certificate& Certificate::operator=(Certificate&& other)
{
Wrapper::operator=(std::move(other));
CertificateView::m_cert = content();
assert(CertificateView::m_cert == Wrapper::m_struct);
return *this;
}
boost::optional<HashedId8> CertificateView::calculate_digest() const
{
return m_cert ? v3::calculate_digest(*m_cert) : boost::none;
}
KeyType CertificateView::get_verification_key_type() const
{
return m_cert ? v3::get_verification_key_type(*m_cert) : KeyType::Unspecified;
}
bool CertificateView::valid_at_location(const PositionFix& location, const LocationChecker* lc) const
{
return m_cert ? lc ? lc->valid_at_location(*m_cert, location) : false : false;
}
bool CertificateView::valid_at_timepoint(const Clock::time_point& timepoint) const
{
return m_cert ? v3::valid_at_timepoint(*m_cert, timepoint) : false;
}
bool valid_at_timepoint(const asn1::EtsiTs103097Certificate& cert, const Clock::time_point& timepoint)
{
const asn1::ValidityPeriod& validity = cert.toBeSigned.validityPeriod;
Clock::time_point start { std::chrono::seconds(validity.start) };
Clock::time_point end = start;
switch (validity.duration.present)
{
case Vanetza_Security_Duration_PR_microseconds:
end += std::chrono::microseconds(validity.duration.choice.microseconds);
break;
case Vanetza_Security_Duration_PR_milliseconds:
end += std::chrono::milliseconds(validity.duration.choice.milliseconds);
break;
case Vanetza_Security_Duration_PR_seconds:
end += std::chrono::seconds(validity.duration.choice.seconds);
break;
case Vanetza_Security_Duration_PR_minutes:
end += std::chrono::minutes(validity.duration.choice.minutes);
break;
case Vanetza_Security_Duration_PR_hours:
end += std::chrono::hours(validity.duration.choice.hours);
break;
case Vanetza_Security_Duration_PR_sixtyHours:
end += std::chrono::hours(60) * validity.duration.choice.sixtyHours;
break;
case Vanetza_Security_Duration_PR_years:
// one year is considered 31556952 seconds according to IEEE 1609.2
end += std::chrono::seconds(31556952) * validity.duration.choice.years;
break;
default:
// leave end at start and thus forming an invalid range
break;
}
return timepoint >= start && timepoint < end;
}
bool CertificateView::valid_for_application(ItsAid aid) const
{
return m_cert ? v3::valid_for_application(*m_cert, aid) : false;
}
bool CertificateView::is_allowed_to_issue(ItsAid aid) const
{
if (!m_cert) {
return false;
}
if (const auto* seq = m_cert->toBeSigned.certIssuePermissions) {
for (int i = 0; i < seq->list.count; ++i) {
const auto* group = seq->list.array[i];
if (!group) {
continue;
}
const auto& subject_permissions = group->subjectPermissions;
if (subject_permissions.present == Vanetza_Security_SubjectPermissions_PR_all) {
return true;
} else if (
subject_permissions.present == Vanetza_Security_SubjectPermissions_PR_explicit &&
contains_permission(subject_permissions.choice.Explicit, aid)) {
return true;
}
}
}
return false;
}
boost::optional<std::uint8_t> CertificateView::assurance_level() const
{
if (!m_cert || !m_cert->toBeSigned.assuranceLevel || m_cert->toBeSigned.assuranceLevel->size < 1) {
return boost::none;
}
return m_cert->toBeSigned.assuranceLevel->buf[0];
}
bool CertificateView::region_is_within(const CertificateView& issuer) const
{
if (!m_cert || !issuer.m_cert) {
return false;
}
const auto* issuer_region = issuer.m_cert->toBeSigned.region;
const auto* subject_region = m_cert->toBeSigned.region;
if (!issuer_region) {
return true;
} else if (!subject_region) {
return false;
} else {
return is_within(*subject_region, *issuer_region);
}
}
bool valid_for_application(const asn1::EtsiTs103097Certificate& cert, ItsAid aid)
{
const asn1::SequenceOfPsidSsp* permissions = cert.toBeSigned.appPermissions;
if (permissions) {
for (int i = 0; i < permissions->list.count; ++i) {
if (permissions->list.array[i]->psid == aid) {
return true;
}
}
}
// only explicitly allowed applications are valid
return false;
}
boost::optional<HashedId8> CertificateView::issuer_digest() const
{
boost::optional<HashedId8> digest;
if (m_cert != nullptr) {
switch (m_cert->issuer.present)
{
case Vanetza_Security_IssuerIdentifier_PR_sha256AndDigest:
digest = create_hashed_id8(m_cert->issuer.choice.sha256AndDigest);
break;
case Vanetza_Security_IssuerIdentifier_PR_sha384AndDigest:
digest = create_hashed_id8(m_cert->issuer.choice.sha384AndDigest);
break;
default:
break;
}
}
return digest;
}
bool CertificateView::issuer_is_self() const
{
return m_cert->issuer.present == Vanetza_Security_IssuerIdentifier_PR_self;
}
bool CertificateView::has_region_restriction() const
{
return m_cert ? m_cert->toBeSigned.region != nullptr : false;
}
bool CertificateView::is_ca_certificate() const
{
return m_cert && m_cert->toBeSigned.certIssuePermissions != nullptr;
}
bool CertificateView::is_at_certificate() const
{
return m_cert && m_cert->toBeSigned.certIssuePermissions == nullptr && m_cert->toBeSigned.appPermissions != nullptr;
}
bool CertificateView::is_canonical() const
{
return m_cert ? v3::is_canonical(*m_cert) : false;
}
StartAndEndValidity CertificateView::get_start_and_end_validity() const
{
StartAndEndValidity start_and_end;
start_and_end.start_validity = Time32(m_cert->toBeSigned.validityPeriod.start);
Time32 duration = 0;
switch (m_cert->toBeSigned.validityPeriod.duration.present)
{
case Vanetza_Security_Duration_PR_NOTHING:
break;
case Vanetza_Security_Duration_PR_microseconds:
duration += (int)m_cert->toBeSigned.validityPeriod.duration.choice.microseconds/1000000;
break;
case Vanetza_Security_Duration_PR_milliseconds:
duration += (int)m_cert->toBeSigned.validityPeriod.duration.choice.milliseconds/1000;
break;
case Vanetza_Security_Duration_PR_seconds:
duration += (int)m_cert->toBeSigned.validityPeriod.duration.choice.seconds;
break;
case Vanetza_Security_Duration_PR_minutes:
duration += (int)m_cert->toBeSigned.validityPeriod.duration.choice.minutes*60;
break;
case Vanetza_Security_Duration_PR_hours:
duration += (int)m_cert->toBeSigned.validityPeriod.duration.choice.hours*60*60;
break;
case Vanetza_Security_Duration_PR_sixtyHours:
duration += (int)m_cert->toBeSigned.validityPeriod.duration.choice.sixtyHours*60*60*60;
break;
case Vanetza_Security_Duration_PR_years:
duration += (int)m_cert->toBeSigned.validityPeriod.duration.choice.years*60*60*24*365;
break;
default:
break;
}
start_and_end.end_validity = start_and_end.start_validity + duration;
return start_and_end;
}
bool is_canonical(const asn1::EtsiTs103097Certificate& cert)
{
bool compressed_point = true;
const Vanetza_Security_VerificationKeyIndicator& indicator = cert.toBeSigned.verifyKeyIndicator;
if (indicator.present == Vanetza_Security_VerificationKeyIndicator_PR_verificationKey) {
const Vanetza_Security_PublicVerificationKey& pubkey = indicator.choice.verificationKey;
switch (pubkey.present) {
case Vanetza_Security_PublicVerificationKey_PR_ecdsaNistP256:
compressed_point = is_compressed(pubkey.choice.ecdsaNistP256);
break;
case Vanetza_Security_PublicVerificationKey_PR_ecdsaBrainpoolP256r1:
compressed_point = is_compressed(pubkey.choice.ecdsaBrainpoolP256r1);
break;
case Vanetza_Security_PublicVerificationKey_PR_ecdsaBrainpoolP384r1:
compressed_point = is_compressed(pubkey.choice.ecdsaBrainpoolP384r1);
break;
default:
break;
}
} else if (indicator.present == Vanetza_Security_VerificationKeyIndicator_PR_reconstructionValue) {
compressed_point = is_compressed(indicator.choice.reconstructionValue);
}
if (!compressed_point) {
return false;
} else if (cert.signature && !is_signature_x_only(*cert.signature)) {
return false;
} else {
return true;
}
}
ByteBuffer CertificateView::encode() const
{
return m_cert ? asn1::encode_oer(asn_DEF_Vanetza_Security_EtsiTs103097Certificate, m_cert) : ByteBuffer {};
}
ByteBuffer Certificate::encode() const
{
return Wrapper::encode();
}
boost::optional<Certificate> CertificateView::canonicalize() const
{
return m_cert ? v3::canonicalize(*m_cert) : boost::none;
}
static boost::optional<Certificate> canonicalize(Certificate&& canonical)
{
bool success = true;
if (canonical->toBeSigned.verifyKeyIndicator.present == Vanetza_Security_VerificationKeyIndicator_PR_verificationKey) {
Vanetza_Security_PublicVerificationKey& pubkey = canonical->toBeSigned.verifyKeyIndicator.choice.verificationKey;
switch (pubkey.present) {
case Vanetza_Security_PublicVerificationKey_PR_ecdsaNistP256:
success &= compress(pubkey.choice.ecdsaNistP256);
break;
case Vanetza_Security_PublicVerificationKey_PR_ecdsaBrainpoolP256r1:
success &= compress(pubkey.choice.ecdsaBrainpoolP256r1);
break;
case Vanetza_Security_PublicVerificationKey_PR_ecdsaBrainpoolP384r1:
success &= compress(pubkey.choice.ecdsaBrainpoolP384r1);
break;
default:
break;
}
} else if (canonical->toBeSigned.verifyKeyIndicator.present == Vanetza_Security_VerificationKeyIndicator_PR_reconstructionValue) {
success &= compress(canonical->toBeSigned.verifyKeyIndicator.choice.reconstructionValue);
}
if (canonical->toBeSigned.encryptionKey) {
Vanetza_Security_BasePublicEncryptionKey& pubkey = canonical->toBeSigned.encryptionKey->publicKey;
switch (pubkey.present) {
case Vanetza_Security_BasePublicEncryptionKey_PR_eciesNistP256:
success &= compress(pubkey.choice.eciesNistP256);
break;
case Vanetza_Security_BasePublicEncryptionKey_PR_eciesBrainpoolP256r1:
success &= compress(pubkey.choice.eciesBrainpoolP256r1);
break;
default:
break;
}
}
if (canonical->signature) {
success &= make_signature_x_only(*canonical->signature);
}
if (success) {
assert(is_canonical(*canonical));
return canonical;
} else {
return boost::none;
}
}
boost::optional<Certificate> canonicalize(const asn1::EtsiTs103097Certificate& cert)
{
try {
Certificate copy { cert };
return canonicalize(std::move(copy));
} catch (const std::exception&) {
return boost::none;
}
}
boost::optional<HashedId8> calculate_digest_internal(const asn1::EtsiTs103097Certificate& cert, HashAlgorithm hash_algo)
{
boost::optional<HashedId8> digest;
try {
ByteBuffer buffer = asn1::encode_oer(asn_DEF_Vanetza_Security_EtsiTs103097Certificate, &cert);
switch (hash_algo)
{
case HashAlgorithm::SHA256:
digest = create_hashed_id8(calculate_sha256_digest(buffer.data(), buffer.size()));
break;
case HashAlgorithm::SHA384:
digest = create_hashed_id8(calculate_sha384_digest(buffer.data(), buffer.size()));
break;
default:
break;
}
} catch (const std::exception&) {
// cannot calculate digest of non-encodable certificate
}
return digest;
}
boost::optional<HashedId8> calculate_digest(const asn1::EtsiTs103097Certificate& cert)
{
boost::optional<HashedId8> digest;
const HashAlgorithm hash_algo = digest_hash_algorithm(cert);
if (hash_algo != HashAlgorithm::Unspecified) {
if (is_canonical(cert)) {
digest = calculate_digest_internal(cert, hash_algo);
} else {
auto maybe_canonical_cert = canonicalize(cert);
if (maybe_canonical_cert) {
digest = calculate_digest_internal(*maybe_canonical_cert.value(), hash_algo);
}
}
}
return digest;
}
KeyType get_verification_key_type(const asn1::EtsiTs103097Certificate& cert)
{
KeyType key_type = KeyType::Unspecified;
if (cert.toBeSigned.verifyKeyIndicator.present == Vanetza_Security_VerificationKeyIndicator_PR_verificationKey)
{
switch (cert.toBeSigned.verifyKeyIndicator.choice.verificationKey.present)
{
case Vanetza_Security_PublicVerificationKey_PR_ecdsaNistP256:
key_type = KeyType::NistP256;
break;
case Vanetza_Security_PublicVerificationKey_PR_ecdsaBrainpoolP256r1:
key_type = KeyType::BrainpoolP256r1;
break;
case Vanetza_Security_PublicVerificationKey_PR_ecdsaBrainpoolP384r1:
key_type = KeyType::BrainpoolP384r1;
break;
default:
break;
}
}
return key_type;
}
boost::optional<PublicKey> get_public_key(const asn1::EtsiTs103097Certificate& cert)
{
asn1::VerificationKeyIndicator indicator = cert.toBeSigned.verifyKeyIndicator;
if (indicator.present != Vanetza_Security_VerificationKeyIndicator_PR_verificationKey) {
return boost::none;
}
const asn1::PublicVerificationKey& input = cert.toBeSigned.verifyKeyIndicator.choice.verificationKey;
PublicKey output;
switch (input.present) {
case Vanetza_Security_PublicVerificationKey_PR_ecdsaNistP256:
output.type = KeyType::NistP256;
if (copy_curve_point(output, input.choice.ecdsaNistP256)) {
return output;
} else {
return boost::none;
}
break;
case Vanetza_Security_PublicVerificationKey_PR_ecdsaBrainpoolP256r1:
output.type = KeyType::BrainpoolP256r1;
if (copy_curve_point(output, input.choice.ecdsaBrainpoolP256r1)) {
return output;
} else {
return boost::none;
}
break;
case Vanetza_Security_PublicVerificationKey_PR_ecdsaBrainpoolP384r1:
output.type = KeyType::BrainpoolP384r1;
if (copy_curve_point(output, input.choice.ecdsaBrainpoolP384r1)) {
return output;
} else {
return boost::none;
}
break;
default:
return boost::none;
break;
}
}
boost::optional<PublicKey> get_public_encryption_key(const asn1::EtsiTs103097Certificate& cert)
{
const asn1::PublicEncryptionKey* enc_key = cert.toBeSigned.encryptionKey;
if (!enc_key || enc_key->supportedSymmAlg != Vanetza_Security_SymmAlgorithm_aes128Ccm) {
return boost::none;
}
PublicKey output;
switch (enc_key->publicKey.present) {
case Vanetza_Security_BasePublicEncryptionKey_PR_eciesNistP256:
output.type = KeyType::NistP256;
if (copy_curve_point(output, enc_key->publicKey.choice.eciesNistP256)) {
return output;
} else {
return boost::none;
}
break;
case Vanetza_Security_BasePublicEncryptionKey_PR_eciesBrainpoolP256r1:
output.type = KeyType::BrainpoolP256r1;
if (copy_curve_point(output, enc_key->publicKey.choice.eciesBrainpoolP256r1)) {
return output;
} else {
return boost::none;
}
break;
default:
return boost::none;
break;
}
}
boost::optional<Signature> get_signature(const asn1::EtsiTs103097Certificate& cert)
{
if (!cert.signature) {
return boost::none;
}
const asn1::Signature* asn = cert.signature;
Signature sig;
switch (asn->present) {
case Vanetza_Security_Signature_PR_ecdsaNistP256Signature:
sig.type = KeyType::NistP256;
sig.r = get_x_coordinate(asn->choice.ecdsaNistP256Signature.rSig);
sig.s = fetch_octets(asn->choice.ecdsaNistP256Signature.sSig);
break;
case Vanetza_Security_Signature_PR_ecdsaBrainpoolP256r1Signature:
sig.type = KeyType::BrainpoolP256r1;
sig.r = get_x_coordinate(asn->choice.ecdsaBrainpoolP256r1Signature.rSig);
sig.s = fetch_octets(asn->choice.ecdsaBrainpoolP256r1Signature.sSig);
break;
case Vanetza_Security_Signature_PR_ecdsaBrainpoolP384r1Signature:
sig.type = KeyType::BrainpoolP384r1;
sig.r = get_x_coordinate(asn->choice.ecdsaBrainpoolP384r1Signature.rSig);
sig.s = fetch_octets(asn->choice.ecdsaBrainpoolP384r1Signature.sSig);
break;
default:
return boost::none;
}
return sig;
}
std::list<ItsAid> get_aids(const asn1::EtsiTs103097Certificate& cert)
{
std::list<ItsAid> aids;
const asn1::SequenceOfPsidSsp* seq = cert.toBeSigned.appPermissions;
if (seq) {
for (int i = 0; i < seq->list.count; ++i) {
aids.push_back(seq->list.array[i]->psid);
}
}
return aids;
}
ByteBuffer get_app_permissions(const asn1::EtsiTs103097Certificate& cert, ItsAid aid)
{
ByteBuffer perms;
const asn1::SequenceOfPsidSsp* seq = cert.toBeSigned.appPermissions;
if (seq) {
for (int i = 0; i < seq->list.count; ++i) {
if (seq->list.array[i]->psid == aid && seq->list.array[i]->ssp != nullptr) {
const asn1::ServiceSpecificPermissions& ssp = *seq->list.array[i]->ssp;
if (ssp.present == Vanetza_Security_ServiceSpecificPermissions_PR_bitmapSsp) {
const asn1::BitmapSsp& bitmap = ssp.choice.bitmapSsp;
perms.assign(bitmap.buf, bitmap.buf + bitmap.size);
break;
} else if (ssp.present == Vanetza_Security_ServiceSpecificPermissions_PR_opaque) {
const asn1::Opaque& opaque = ssp.choice.opaque;
perms.assign(opaque.buf, opaque.buf + opaque.size);
break;
}
}
}
}
return perms;
}
void add_psid_group_permission(asn1::PsidGroupPermissions* group_permission, ItsAid aid, const ByteBuffer& ssp, const ByteBuffer& bitmask)
{
auto psid_range_scr = asn1::allocate<asn1::PsidSspRange>();
psid_range_scr->psid = aid;
psid_range_scr->sspRange = asn1::allocate<asn1::SspRange>();
psid_range_scr->sspRange->present = Vanetza_Security_SspRange_PR_bitmapSspRange;
OCTET_STRING_fromBuf(
&psid_range_scr->sspRange->choice.bitmapSspRange.sspValue,
reinterpret_cast<const char*>(ssp.data()),
ssp.size()
);
OCTET_STRING_fromBuf(
&psid_range_scr->sspRange->choice.bitmapSspRange.sspBitmask,
reinterpret_cast<const char*>(bitmask.data()),
bitmask.size()
);
ASN_SEQUENCE_ADD(&group_permission->subjectPermissions.choice.Explicit, psid_range_scr);
}
void Certificate::add_app_permission(ItsAid aid, const ByteBuffer& ssp)
{
asn1::SequenceOfPsidSsp* seq = m_struct->toBeSigned.appPermissions;
if (!seq) {
seq = asn1::allocate<asn1::SequenceOfPsidSsp>();
m_struct->toBeSigned.appPermissions = seq;
}
auto psid_ptr = asn1::allocate<asn1::PsidSsp>();
psid_ptr->psid = aid;
if (!ssp.empty()) {
psid_ptr->ssp = asn1::allocate<asn1::ServiceSpecificPermissions>();
psid_ptr->ssp->present = Vanetza_Security_ServiceSpecificPermissions_PR_bitmapSsp;
OCTET_STRING_fromBuf(
&psid_ptr->ssp->choice.bitmapSsp,
reinterpret_cast<const char *>(ssp.data()),
ssp.size()
);
}
ASN_SEQUENCE_ADD(seq, psid_ptr);
}
void Certificate::add_cert_issue_permission(asn1::PsidGroupPermissions* group_permission)
{
asn1::SequenceOfPsidGroupPermissions* seq = m_struct->toBeSigned.certIssuePermissions;
if (!seq) {
seq = asn1::allocate<asn1::SequenceOfPsidGroupPermissions>();
m_struct->toBeSigned.certIssuePermissions = seq;
}
ASN_SEQUENCE_ADD(seq, group_permission);
}
void Certificate::set_signature(const SomeEcdsaSignature& signature)
{
if (!m_struct->signature) {
m_struct->signature = asn1::allocate<asn1::Signature>();
} else {
asn1::reset(m_struct->signature);
}
struct signature_visitor : public boost::static_visitor<>
{
asn1::Signature* sig;
explicit signature_visitor(asn1::Signature* s) : sig(s)
{
}
void operator()(const EcdsaSignature& signature) const
{
sig->present = Vanetza_Security_Signature_PR_ecdsaNistP256Signature;
assign(&sig->choice.ecdsaNistP256Signature.sSig, signature.s);
sig->choice.ecdsaNistP256Signature.rSig = to_asn1(signature.R);
}
void operator()(const EcdsaSignatureFuture& signature) const
{
this->operator()(signature.get());
}
};
boost::apply_visitor(signature_visitor(m_struct->signature), signature);
}
Certificate fake_certificate()
{
Certificate cert;
cert->issuer.present = Vanetza_Security_IssuerIdentifier_PR_self;
cert->toBeSigned.id.present = Vanetza_Security_CertificateId_PR_none;
std::array<char, 3> craca_id = { 0, 0, 0};
OCTET_STRING_fromBuf(&cert->toBeSigned.cracaId, craca_id.data(), craca_id.size());
cert->version = 3;
cert->toBeSigned.crlSeries = 0;
cert->toBeSigned.validityPeriod.start = 0;
cert->toBeSigned.validityPeriod.duration.present = Vanetza_Security_Duration_PR_minutes;
cert->toBeSigned.validityPeriod.duration.choice.minutes = 10080;
cert->toBeSigned.verifyKeyIndicator.present = Vanetza_Security_VerificationKeyIndicator_PR_verificationKey;
cert->toBeSigned.verifyKeyIndicator.choice.verificationKey.present = Vanetza_Security_PublicVerificationKey_PR_ecdsaNistP256;
cert->toBeSigned.verifyKeyIndicator.choice.verificationKey.choice.ecdsaNistP256.present = Vanetza_Security_EccP256CurvePoint_PR_compressed_y_0;
std::array<char, 32> dummy_r {}; // 32 octets for P256 signature
OCTET_STRING_fromBuf(
&cert->toBeSigned.verifyKeyIndicator.choice.verificationKey.choice.ecdsaNistP256.choice.x_only,
dummy_r.data(), dummy_r.size()
);
cert.add_app_permission(aid::CA, ByteBuffer({ 1, 0, 0 }));
return cert;
}
void serialize(OutputArchive& ar, const Certificate& certificate)
{
ByteBuffer buffer = certificate.encode();
ar.save_binary(buffer.data(), buffer.size());
}
namespace
{
bool contains_permission(const Vanetza_Security_SequenceOfPsidSspRange_t& permissions, ItsAid aid)
{
for (int i = 0; i < permissions.list.count; ++i) {
const auto* permission = permissions.list.array[i];
if (permission && permission->psid == aid) {
return true;
}
}
return false;
}
bool equal(const asn1::TwoDLocation& lhs, const asn1::TwoDLocation& rhs)
{
return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude;
}
bool equal(const asn1::RectangularRegion& lhs, const asn1::RectangularRegion& rhs)
{
return equal(lhs.northWest, rhs.northWest) && equal(lhs.southEast, rhs.southEast);
}
bool equal(const asn1::SequenceOfRectangularRegion& lhs, const asn1::SequenceOfRectangularRegion& rhs)
{
if (lhs.list.count != rhs.list.count) {
return false;
}
for (int i = 0; i < lhs.list.count; ++i) {
if (!lhs.list.array[i] || !rhs.list.array[i] || !equal(*lhs.list.array[i], *rhs.list.array[i])) {
return false;
}
}
return true;
}
bool is_within(const asn1::GeographicRegion& inner, const asn1::CircularRegion& outer)
{
if (inner.present != Vanetza_Security_GeographicRegion_PR_circularRegion) {
return false;
}
const auto& circle = inner.choice.circularRegion;
if (!is_valid(circle.center) || !is_valid(outer.center) || circle.radius < 0 || outer.radius < 0) {
return false;
}
PositionFix inner_center;
inner_center.latitude = convert_latitude(circle.center.latitude);
inner_center.longitude = convert_longitude(circle.center.longitude);
return distance(inner_center, outer.center) + circle.radius * units::si::meter <= outer.radius * units::si::meter;
}
bool is_within(const asn1::GeographicRegion& inner, const asn1::SequenceOfRectangularRegion& outer)
{
return inner.present == Vanetza_Security_GeographicRegion_PR_rectangularRegion &&
equal(inner.choice.rectangularRegion, outer);
}
bool is_within(const asn1::GeographicRegion&, const asn1::PolygonalRegion&)
{
return false;
}
bool is_within(const asn1::GeographicRegion& inner, const asn1::GeographicRegion& outer)
{
switch (outer.present) {
case Vanetza_Security_GeographicRegion_PR_circularRegion:
return is_within(inner, outer.choice.circularRegion);
case Vanetza_Security_GeographicRegion_PR_rectangularRegion:
return is_within(inner, outer.choice.rectangularRegion);
case Vanetza_Security_GeographicRegion_PR_polygonalRegion:
return is_within(inner, outer.choice.polygonalRegion);
case Vanetza_Security_GeographicRegion_PR_NOTHING:
return true;
default:
return false;
}
}
bool copy_curve_point(PublicKey& to, const asn1::EccP256CurvePoint& from)
{
bool copied = true;
switch (from.present) {
case Vanetza_Security_EccP256CurvePoint_PR_compressed_y_0:
to.compression = KeyCompression::Y0;
to.x = fetch_octets(from.choice.compressed_y_0);
break;
case Vanetza_Security_EccP256CurvePoint_PR_compressed_y_1:
to.compression = KeyCompression::Y1;
to.x = fetch_octets(from.choice.compressed_y_1);
break;
case Vanetza_Security_EccP256CurvePoint_PR_uncompressedP256:
to.compression = KeyCompression::NoCompression;
to.x = fetch_octets(from.choice.uncompressedP256.x);
to.y = fetch_octets(from.choice.uncompressedP256.y);
break;
default:
copied = false;
break;
}
return copied;
}
bool copy_curve_point(PublicKey& to, const asn1::EccP384CurvePoint& from)
{
bool copied = true;
switch (from.present) {
case Vanetza_Security_EccP384CurvePoint_PR_compressed_y_0:
to.compression = KeyCompression::Y0;
to.x = fetch_octets(from.choice.compressed_y_0);
break;
case Vanetza_Security_EccP384CurvePoint_PR_compressed_y_1:
to.compression = KeyCompression::Y1;
to.x = fetch_octets(from.choice.compressed_y_1);
break;
case Vanetza_Security_EccP384CurvePoint_PR_uncompressedP384:
to.compression = KeyCompression::NoCompression;
to.x = fetch_octets(from.choice.uncompressedP384.x);
to.y = fetch_octets(from.choice.uncompressedP384.y);
break;
default:
copied = false;
break;
}
return copied;
}
HashAlgorithm digest_hash_algorithm(const asn1::EtsiTs103097Certificate& cert)
{
// IEEE 1609.2 clause 6.4.3: hash algorithm depends on the verification key indicator;
// a reconstruction value (implicit certificate) is always an EccP256CurvePoint and
// clause 5.3.2 mandates SHA-256 for implicit certificates
const asn1::VerificationKeyIndicator& indicator = cert.toBeSigned.verifyKeyIndicator;
switch (indicator.present) {
case Vanetza_Security_VerificationKeyIndicator_PR_verificationKey:
switch (indicator.choice.verificationKey.present) {
case Vanetza_Security_PublicVerificationKey_PR_ecdsaNistP256:
case Vanetza_Security_PublicVerificationKey_PR_ecdsaBrainpoolP256r1:
return HashAlgorithm::SHA256;
case Vanetza_Security_PublicVerificationKey_PR_ecdsaBrainpoolP384r1:
return HashAlgorithm::SHA384;
default:
return HashAlgorithm::Unspecified;
}
case Vanetza_Security_VerificationKeyIndicator_PR_reconstructionValue:
return HashAlgorithm::SHA256;
default:
return HashAlgorithm::Unspecified;
}
}
ByteBuffer fetch_octets(const OCTET_STRING_t& octets)
{
ByteBuffer buffer(octets.size);
std::memcpy(buffer.data(), octets.buf, octets.size);
return buffer;
}
ByteBuffer get_x_coordinate(const asn1::EccP256CurvePoint& point)
{
switch (point.present) {
case Vanetza_Security_EccP256CurvePoint_PR_compressed_y_0:
return fetch_octets(point.choice.compressed_y_0);
case Vanetza_Security_EccP256CurvePoint_PR_compressed_y_1:
return fetch_octets(point.choice.compressed_y_1);
case Vanetza_Security_EccP256CurvePoint_PR_x_only:
return fetch_octets(point.choice.x_only);
case Vanetza_Security_EccP256CurvePoint_PR_uncompressedP256:
return fetch_octets(point.choice.uncompressedP256.x);
default:
return ByteBuffer {};
}
}
ByteBuffer get_x_coordinate(const asn1::EccP384CurvePoint& point)
{
switch (point.present) {
case Vanetza_Security_EccP384CurvePoint_PR_compressed_y_0:
return fetch_octets(point.choice.compressed_y_0);
case Vanetza_Security_EccP384CurvePoint_PR_compressed_y_1:
return fetch_octets(point.choice.compressed_y_1);
case Vanetza_Security_EccP384CurvePoint_PR_x_only:
return fetch_octets(point.choice.x_only);
case Vanetza_Security_EccP384CurvePoint_PR_uncompressedP384:
return fetch_octets(point.choice.uncompressedP384.x);
default:
return ByteBuffer {};
}
}
bool is_compressed(const Vanetza_Security_EccP256CurvePoint& point)
{
switch (point.present) {
case Vanetza_Security_EccP256CurvePoint_PR_compressed_y_0:
case Vanetza_Security_EccP256CurvePoint_PR_compressed_y_1:
return true;
default:
return false;
}
}
bool is_compressed(const Vanetza_Security_EccP384CurvePoint& point)
{
switch (point.present) {
case Vanetza_Security_EccP384CurvePoint_PR_compressed_y_0:
case Vanetza_Security_EccP384CurvePoint_PR_compressed_y_1:
return true;
default:
return false;
}
}
bool is_signature_x_only(const Vanetza_Security_Signature_t& sig)
{
switch (sig.present) {
case Vanetza_Security_Signature_PR_ecdsaNistP256Signature:
return sig.choice.ecdsaNistP256Signature.rSig.present == Vanetza_Security_EccP256CurvePoint_PR_x_only;
case Vanetza_Security_Signature_PR_ecdsaBrainpoolP256r1Signature:
return sig.choice.ecdsaBrainpoolP256r1Signature.rSig.present == Vanetza_Security_EccP256CurvePoint_PR_x_only;
case Vanetza_Security_Signature_PR_ecdsaBrainpoolP384r1Signature:
return sig.choice.ecdsaBrainpoolP384r1Signature.rSig.present == Vanetza_Security_EccP384CurvePoint_PR_x_only;
default:
return true; // not an ECDSA signature at all
}
}
bool compress(Vanetza_Security_EccP256CurvePoint& point)
{
if (point.present == Vanetza_Security_EccP256CurvePoint_PR_uncompressedP256) {
auto& unc = point.choice.uncompressedP256;
const bool y_odd = unc.y.size > 0 && unc.y.buf[unc.y.size - 1] & 0x01;
asn1::reset(&unc.y);
std::free(unc._asn_ctx.ptr);
unc._asn_ctx.ptr = nullptr;
if (y_odd) {
assert(&point.choice.uncompressedP256.x == &point.choice.compressed_y_1);
point.present = Vanetza_Security_EccP256CurvePoint_PR_compressed_y_1;
} else {
assert(&point.choice.uncompressedP256.x == &point.choice.compressed_y_0);
point.present = Vanetza_Security_EccP256CurvePoint_PR_compressed_y_0;
}
return true;
} else if (point.present == Vanetza_Security_EccP256CurvePoint_PR_compressed_y_0 || point.present == Vanetza_Security_EccP256CurvePoint_PR_compressed_y_1) {
return true;
} else {
return false;
}
}
bool compress(Vanetza_Security_EccP384CurvePoint& point)
{
if (point.present == Vanetza_Security_EccP384CurvePoint_PR_uncompressedP384) {
auto& unc = point.choice.uncompressedP384;
const bool y_odd = unc.y.size > 0 && unc.y.buf[unc.y.size - 1] & 0x01;
asn1::reset(&unc.y);
std::free(unc._asn_ctx.ptr);
unc._asn_ctx.ptr = nullptr;
if (y_odd) {
assert(&point.choice.uncompressedP384.x == &point.choice.compressed_y_1);
point.present = Vanetza_Security_EccP384CurvePoint_PR_compressed_y_1;
} else {
assert(&point.choice.uncompressedP384.x == &point.choice.compressed_y_0);
point.present = Vanetza_Security_EccP384CurvePoint_PR_compressed_y_0;
}
return true;
} else if (point.present == Vanetza_Security_EccP384CurvePoint_PR_compressed_y_0 || point.present == Vanetza_Security_EccP384CurvePoint_PR_compressed_y_1) {
return true;
} else {
return false;
}
}
bool make_x_only(Vanetza_Security_EccP256CurvePoint& point)
{
if (point.present == Vanetza_Security_EccP256CurvePoint_PR_uncompressedP256) {
auto& unc = point.choice.uncompressedP256;
asn1::reset(&unc.y);
std::free(unc._asn_ctx.ptr);
unc._asn_ctx.ptr = nullptr;
assert(&unc.x == &point.choice.x_only);
point.present = Vanetza_Security_EccP256CurvePoint_PR_x_only;
return true;
} else if (point.present == Vanetza_Security_EccP256CurvePoint_PR_x_only) {
return true;
} else {
return false;
}
}
bool make_x_only(Vanetza_Security_EccP384CurvePoint& point)
{
if (point.present == Vanetza_Security_EccP384CurvePoint_PR_uncompressedP384) {
auto& unc = point.choice.uncompressedP384;
asn1::reset(&unc.y);
std::free(unc._asn_ctx.ptr);
unc._asn_ctx.ptr = nullptr;
assert(&unc.x == &point.choice.x_only);
point.present = Vanetza_Security_EccP384CurvePoint_PR_x_only;
return true;
} else if (point.present == Vanetza_Security_EccP384CurvePoint_PR_x_only) {
return true;
} else {
return false;
}
}
bool make_signature_x_only(Vanetza_Security_Signature& sig)
{
switch (sig.present) {
case Vanetza_Security_Signature_PR_ecdsaNistP256Signature:
return make_x_only(sig.choice.ecdsaNistP256Signature.rSig);
break;
case Vanetza_Security_Signature_PR_ecdsaBrainpoolP256r1Signature:
return make_x_only(sig.choice.ecdsaBrainpoolP256r1Signature.rSig);
break;
case Vanetza_Security_Signature_PR_ecdsaBrainpoolP384r1Signature:
return make_x_only(sig.choice.ecdsaBrainpoolP384r1Signature.rSig);
break;
default:
return false;
break;
}
}
} // namespace
} // namespace v3
} // namespace security
} // namespace vanetza