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,85 @@
#include <vanetza/asn1/security_profile.hpp>
#include VANETZA_ASN1_SECURITY_HEADER(EccP256CurvePoint.h)
#include <vanetza/security/v3/asn1_conversions.hpp>
#include <boost/variant/static_visitor.hpp>
#include <algorithm>
#include <cstring>
namespace vanetza
{
namespace security
{
namespace v3
{
HashedId8 convert(const Vanetza_Security_HashedId8_t& in)
{
HashedId8 out;
std::memcpy(out.data(), in.buf, std::min(out.size(), in.size));
return out;
}
void assign(OCTET_STRING_t* dst, const ByteBuffer& src)
{
OCTET_STRING_fromBuf(dst, reinterpret_cast<const char*>(src.data()), src.size());
}
asn1::EccP256CurvePoint to_asn1(const EccPoint& point)
{
struct visitor : public boost::static_visitor<asn1::EccP256CurvePoint>
{
asn1::EccP256CurvePoint operator()(const X_Coordinate_Only& x_only) const
{
asn1::EccP256CurvePoint result = {};
result.present = Vanetza_Security_EccP256CurvePoint_PR_x_only;
assign(&result.choice.x_only, x_only.x);
return result;
}
asn1::EccP256CurvePoint operator()(const Compressed_Lsb_Y_0& y0) const
{
asn1::EccP256CurvePoint result = {};
result.present = Vanetza_Security_EccP256CurvePoint_PR_compressed_y_0;
assign(&result.choice.compressed_y_0, y0.x);
return result;
}
asn1::EccP256CurvePoint operator()(const Compressed_Lsb_Y_1& y1) const
{
asn1::EccP256CurvePoint result = {};
result.present = Vanetza_Security_EccP256CurvePoint_PR_compressed_y_1;
assign(&result.choice.compressed_y_1, y1.x);
return result;
}
asn1::EccP256CurvePoint operator()(const Uncompressed& unc) const
{
asn1::EccP256CurvePoint result = {};
result.present = Vanetza_Security_EccP256CurvePoint_PR_uncompressedP256;
assign(&result.choice.uncompressedP256.x, unc.x);
assign(&result.choice.uncompressedP256.y, unc.y);
return result;
}
};
return boost::apply_visitor(visitor(), point);
}
} // namespace v3
HashedId8 create_hashed_id8(const Vanetza_Security_HashedId8_t& in)
{
HashedId8 out;
std::memcpy(out.data(), in.buf, std::min(out.size(), in.size));
return out;
}
HashedId3 create_hashed_id3(const Vanetza_Security_HashedId3_t& in)
{
HashedId3 out;
std::memcpy(out.data(), in.buf, std::min(out.size(), in.size));
return out;
}
} // namespace security
} // namespace vanetza
@@ -0,0 +1,39 @@
#pragma once
#include <vanetza/asn1/security_profile.hpp>
#include VANETZA_ASN1_SECURITY_HEADER(HashedId3.h)
#include VANETZA_ASN1_SECURITY_HEADER(HashedId8.h)
#include <vanetza/security/ecc_point.hpp>
#include <vanetza/security/hashed_id.hpp>
#include <vanetza/security/v3/asn1_types.hpp>
namespace vanetza
{
namespace security
{
HashedId8 create_hashed_id8(const Vanetza_Security_HashedId8_t&);
HashedId3 create_hashed_id3(const Vanetza_Security_HashedId3_t&);
namespace v3
{
HashedId8 convert(const Vanetza_Security_HashedId8_t&);
/**
* Assign ByteBuffer content to an ASN.1 OCTET_STRING
* \param dst destination OCTET_STRING
* \param src source byte buffer
*/
void assign(OCTET_STRING_t* dst, const ByteBuffer& src);
/**
* Convert an EccPoint to its ASN.1 EccP256CurvePoint representation
* \param point ECC point to convert
* \return ASN.1 EccP256CurvePoint
*/
asn1::EccP256CurvePoint to_asn1(const EccPoint& point);
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,124 @@
#pragma once
#include <vanetza/asn1/type_traits.hpp>
// forward declarations of base types
using OCTET_STRING_t = struct OCTET_STRING;
using INTEGER_t = struct ASN__PRIMITIVE_TYPE_s;
using asn_TYPE_descriptor_t = struct asn_TYPE_descriptor_s;
using Vanetza_Security_Uint64_t = INTEGER_t;
#define ASN1_TYPE_ALIAS(name) Vanetza_Security_ ## name ## _t
#define ASN1_TYPE_NAME(name) Vanetza_Security_ ## name
#define ASN1_TYPE_DESC(name) asn_DEF_Vanetza_Security_ ## name
#define FWD_ALIAS(name, base) \
using ASN1_TYPE_ALIAS(name) = ASN1_TYPE_ALIAS(base); \
namespace vanetza { namespace security { namespace v3 { namespace asn1 { \
using name = ::ASN1_TYPE_ALIAS(name); \
}}}}
#define FWD_OCTET_STRING(name) \
using ASN1_TYPE_ALIAS(name) = OCTET_STRING_t; \
namespace vanetza { namespace security { namespace v3 { namespace asn1 { \
using name = ::ASN1_TYPE_ALIAS(name); \
}}}}
#define FWD_STRUCT(name) \
typedef struct ASN1_TYPE_NAME(name) ASN1_TYPE_ALIAS(name); \
namespace vanetza { namespace security { namespace v3 { namespace asn1 { \
using name = ::ASN1_TYPE_ALIAS(name); \
}}}} \
extern "C" { extern asn_TYPE_descriptor_t ASN1_TYPE_DESC(name); } \
namespace vanetza { namespace asn1 { \
template<> struct asn1_type_traits<::ASN1_TYPE_NAME(name)> { \
static asn_TYPE_descriptor_t& descriptor() { return ::ASN1_TYPE_DESC(name); } \
}; \
}}
#define FWD_NATIVE_INTEGER(name) \
using ASN1_TYPE_ALIAS(name) = long; \
namespace vanetza { namespace security { namespace v3 { namespace asn1 { \
using name = ::ASN1_TYPE_ALIAS(name); \
}}}}
FWD_OCTET_STRING(BitmapSsp)
FWD_OCTET_STRING(HashedId8)
FWD_OCTET_STRING(Opaque)
FWD_NATIVE_INTEGER(Latitude)
FWD_NATIVE_INTEGER(Longitude)
#ifdef VANETZA_WITH_PQC
FWD_STRUCT(One28BitCcmCiphertext)
namespace vanetza { namespace security { namespace v3 { namespace asn1 {
using AesCcmCiphertext = One28BitCcmCiphertext;
}}}}
#else
FWD_STRUCT(AesCcmCiphertext)
#endif
FWD_STRUCT(CertificateBase)
FWD_STRUCT(CircularRegion)
FWD_STRUCT(EccP256CurvePoint)
FWD_STRUCT(EccP384CurvePoint)
FWD_STRUCT(EciesP256EncryptedKey)
FWD_STRUCT(EncryptedDataEncryptionKey)
FWD_STRUCT(GeographicRegion)
FWD_STRUCT(HashedData)
FWD_STRUCT(HeaderInfo)
FWD_STRUCT(Ieee1609Dot2Content)
FWD_STRUCT(Ieee1609Dot2Data)
FWD_STRUCT(PKRecipientInfo)
FWD_STRUCT(PsidGroupPermissions)
FWD_STRUCT(PsidSsp)
FWD_STRUCT(PsidSspRange)
FWD_STRUCT(PolygonalRegion)
FWD_STRUCT(PublicEncryptionKey)
FWD_STRUCT(PublicVerificationKey)
FWD_STRUCT(RecipientInfo)
FWD_STRUCT(RectangularRegion)
FWD_STRUCT(SequenceOfCertificate)
FWD_STRUCT(SequenceOfHashedId3)
FWD_STRUCT(SequenceOfPsidGroupPermissions)
FWD_STRUCT(SequenceOfPsidSsp)
FWD_STRUCT(SequenceOfRectangularRegion)
FWD_STRUCT(ServiceSpecificPermissions)
FWD_STRUCT(Signature)
FWD_STRUCT(SignedData)
FWD_STRUCT(SignedDataPayload)
FWD_STRUCT(SignerIdentifier)
FWD_STRUCT(SspRange)
FWD_STRUCT(SymmetricCiphertext)
FWD_STRUCT(SymmetricEncryptionKey)
FWD_STRUCT(ThreeDLocation)
FWD_STRUCT(ToBeSignedData)
FWD_STRUCT(TwoDLocation)
FWD_STRUCT(ValidityPeriod)
FWD_STRUCT(VerificationKeyIndicator)
FWD_ALIAS(Certificate, CertificateBase)
FWD_ALIAS(EtsiTs103097Certificate, CertificateBase)
FWD_ALIAS(EtsiTs103097Data, Ieee1609Dot2Data)
FWD_ALIAS(Time64, Uint64)
#undef ASN1_TYPE_ALIAS
#undef ASN1_TYPE_NAME
#undef FWD_ALIAS
#undef FWD_NATIVE_INTEGER
#undef FWD_OCTET_STRING
#undef FWD_STRUCT
namespace vanetza
{
namespace security
{
namespace v3
{
namespace asn1
{
using namespace vanetza::asn1;
} // namespace asn1
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,46 @@
#include <vanetza/security/v3/basic_elements.hpp>
#include <vanetza/asn1/support/OCTET_STRING.h>
#include <vanetza/common/byte_buffer.hpp>
#include <algorithm>
#include <cassert>
#include <chrono>
namespace vanetza
{
namespace security
{
namespace v3
{
Time32 convert_time32(const Clock::time_point& tp)
{
using std::chrono::duration_cast;
using seconds = std::chrono::duration<Time32>;
return duration_cast<seconds>(tp.time_since_epoch()).count();
}
Clock::time_point convert_time_point(const Time32& t)
{
using std::chrono::duration_cast;
using seconds = std::chrono::duration<Time32>;
return Clock::time_point { duration_cast<Clock::duration>(seconds(t)) };
}
Clock::time_point convert_time_point(const Time64& t)
{
using std::chrono::duration_cast;
using microseconds = std::chrono::duration<Time64, std::micro>;
return Clock::time_point { duration_cast<Clock::duration>(microseconds(t)) };
}
Time64 convert_time64(const Clock::time_point& tp)
{
using std::chrono::duration_cast;
using microseconds = std::chrono::duration<Time64, std::micro>;
return duration_cast<microseconds>(tp.time_since_epoch()).count();
}
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,48 @@
#pragma once
#include <vanetza/common/clock.hpp>
#include <vanetza/asn1/support/OCTET_STRING.h>
#include <vanetza/common/byte_buffer.hpp>
#include <array>
#include <cstdint>
namespace vanetza
{
namespace security
{
namespace v3
{
using Time64 = uint64_t;
using Time32 = uint32_t;
/**
* Convert time point to time stamp
* \param tp time point
* \return time stamp with second accuracy
*/
Time32 convert_time32(const Clock::time_point& tp);
/**
* Convert time stamp to time point
* \param t time stamp with second accuracy
* \return time point
*/
Clock::time_point convert_time_point(const Time32& t);
/**
* Convert time point to time stamp
* \param tp time point
* \return time stamp with microsecond accuracy
*/
Time64 convert_time64(const Clock::time_point& tp);
/**
* Convert time stamp to time point
* \param t time stamp with microsecond accuracy
* \return time point
*/
Clock::time_point convert_time_point(const Time64& t);
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,87 @@
#include <vanetza/asn1/security_profile.hpp>
#include VANETZA_ASN1_SECURITY_HEADER(PolygonalRegion.h)
#include VANETZA_ASN1_SECURITY_HEADER(TwoDLocation.h)
#include <vanetza/common/position_fix.hpp>
#include <vanetza/security/v3/boost_geometry.hpp>
namespace vanetza
{
namespace security
{
namespace v3
{
TwoDLocationModel make_model(const asn1::TwoDLocation& location)
{
return TwoDLocationModel(location.longitude * 1e-7, location.latitude * 1e-7);
}
TwoDLocationModel make_model(const PositionFix& fix)
{
return TwoDLocationModel(fix.longitude / units::degree, fix.latitude / units::degree);
}
TwoDLocationIterator::TwoDLocationIterator(const asn1::PolygonalRegion& region, std::size_t index) :
m_region(&region), m_index(index)
{
}
void TwoDLocationIterator::increment()
{
++m_index;
}
void TwoDLocationIterator::decrement()
{
--m_index;
}
void TwoDLocationIterator::advance(std::size_t n)
{
m_index += n;
}
std::ptrdiff_t TwoDLocationIterator::distance_to(const TwoDLocationIterator& other) const
{
return other.m_index - m_index;
}
bool TwoDLocationIterator::equal(const TwoDLocationIterator& other) const
{
return m_region == other.m_region && m_index == other.m_index;
}
TwoDLocationModel TwoDLocationIterator::dereference() const
{
assert(m_region != nullptr);
assert(m_region->list.array != nullptr);
assert(m_region->list.count > static_cast<decltype(m_region->list.count)>(m_index));
return make_model(*m_region->list.array[m_index]);
}
PolygonalRegionRingAdapter::PolygonalRegionRingAdapter(const asn1::PolygonalRegion& region) :
m_region(region)
{
assert(m_region.list.array != nullptr);
}
PolygonalRegionRingAdapter::iterator PolygonalRegionRingAdapter::begin() const
{
return TwoDLocationIterator(m_region, 0);
}
PolygonalRegionRingAdapter::iterator PolygonalRegionRingAdapter::end() const
{
return TwoDLocationIterator(m_region, m_region.list.count);
}
std::size_t PolygonalRegionRingAdapter::size() const
{
return m_region.list.count;
}
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,94 @@
#pragma once
#include <vanetza/security/v3/asn1_types.hpp>
#include <boost/geometry/core/closure.hpp>
#include <boost/geometry/core/cs.hpp>
#include <boost/geometry/geometries/point.hpp>
#include <boost/iterator/iterator_facade.hpp>
namespace vanetza
{
// forward declaration
struct PositionFix;
namespace security
{
namespace v3
{
/**
* TwoDLocationModel is a Boost.Geometry point model representing a 2D geographic location.
*/
using TwoDLocationModel = boost::geometry::model::point<double, 2, boost::geometry::cs::geographic<boost::geometry::degree>>;
/**
* \brief Create a TwoDLocationModel from an ASN.1 TwoDLocation.
*
* \param location The ASN.1 TwoDLocation_t structure to convert.
* \return A TwoDLocationModel representing the geographic location.
*/
TwoDLocationModel make_model(const asn1::TwoDLocation& location);
TwoDLocationModel make_model(const PositionFix&);
/**
* TwoDLocationIterator allows traversal of ASN.1 PolygonalRegion providing TwoDLocationModel objects.
*/
class TwoDLocationIterator :
public boost::iterator_facade<TwoDLocationIterator,
TwoDLocationModel,
boost::random_access_traversal_tag,
TwoDLocationModel>
{
public:
TwoDLocationIterator() = default;
explicit TwoDLocationIterator(const asn1::PolygonalRegion& region, std::size_t index);
private:
friend class boost::iterator_core_access;
void increment();
void decrement();
void advance(std::size_t n);
std::ptrdiff_t distance_to(const TwoDLocationIterator& other) const;
bool equal(const TwoDLocationIterator& other) const;
TwoDLocationModel dereference() const;
const asn1::PolygonalRegion* m_region = nullptr;
std::size_t m_index = 0;
};
/**
* \brief Adapt ASN.1 PolygonalRegion to a Boost.Geometry ring.
*/
class PolygonalRegionRingAdapter
{
public:
using iterator = TwoDLocationIterator;
using const_iterator = TwoDLocationIterator;
PolygonalRegionRingAdapter(const asn1::PolygonalRegion& region);
iterator begin() const;
iterator end() const;
std::size_t size() const;
private:
const asn1::PolygonalRegion& m_region;
};
} // namespace v3
} // namespace security
} // namespace vanetza
namespace boost
{
namespace geometry
{
namespace traits
{
template<> struct tag<vanetza::security::v3::PolygonalRegionRingAdapter> { using type = ring_tag; };
template<> struct closure<vanetza::security::v3::PolygonalRegionRingAdapter> { static const closure_selector value = open; };
} // namespace traits
} // namespace gemoetry
} // namespace boost
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,286 @@
#pragma once
#include <vanetza/asn1/asn1c_wrapper.hpp>
#include <vanetza/asn1/security_profile.hpp>
#include VANETZA_ASN1_SECURITY_HEADER(EtsiTs103097Certificate.h)
#include <vanetza/common/clock.hpp>
#include <vanetza/common/its_aid.hpp>
#include <vanetza/common/position_fix.hpp>
#include <vanetza/net/packet_variant.hpp>
#include <vanetza/security/hashed_id.hpp>
#include <vanetza/security/key_type.hpp>
#include <vanetza/security/public_key.hpp>
#include <vanetza/security/signature.hpp>
#include <vanetza/security/v3/asn1_types.hpp>
#include <vanetza/security/v3/location_checker.hpp>
#include <vanetza/security/v3/validity_restriction.hpp>
#include <boost/optional/optional_fwd.hpp>
#include <cstdint>
#include <list>
namespace vanetza
{
namespace security
{
namespace v3
{
// forward declaration
class Certificate;
/**
* Read-only view on a certificate
*
* In contrast to Certificate, a view does not own the certificate data.
* A view can be created with low overhead as no heavy copying is required.
*/
class CertificateView
{
public:
explicit CertificateView(const asn1::EtsiTs103097Certificate* cert);
/**
* Calculate digest of certificate
* \return digest if possible
*/
boost::optional<HashedId8> calculate_digest() const;
/**
* Get start and end validity
* \return certificate start and end validity
*/
StartAndEndValidity get_start_and_end_validity() const;
/**
* Get verification key type
* \return verification key type if possible; otherwise unspecified
*/
KeyType get_verification_key_type() const;
/**
* Get issuer digest (if any)
* \return issuer digest
*/
boost::optional<HashedId8> issuer_digest() const;
/**
* Check if certificate is self-signed
* \return true if certificate is self-signed
*/
bool issuer_is_self() const;
/**
* Check if certificate is a Certification Authority certificate
* \return true if certificate is a CA certificate
*/
bool is_ca_certificate() const;
/**
* Check if certificate is an Authorization Ticket certificate
* \return true if certificate is an AT certificate
*/
bool is_at_certificate() const;
/**
* Check if certificate has an region restriction
* \return true if certificate is only valid within a specific region
*/
bool has_region_restriction() const;
/**
* Check if certificate is valid at given location
*
* \param location location to be checked
* \return true if certificate is valid at location
*/
bool valid_at_location(const PositionFix& location, const LocationChecker* lc) const;
/**
* Check if certificate is valid at given time point
*
* \param time_point time point to be checked
* \return true if certificate is valid at time point
*/
bool valid_at_timepoint(const Clock::time_point& time_point) const;
/**
* Check if certificate is valid for given application
*
* \param aid application to be checked
* \return true if certificate is valid for application
*/
bool valid_for_application(ItsAid aid) const;
/**
* Check if certificate issue permissions allow issuing a given application.
*
* \param aid application to be checked
* \return true if certificate may issue certificates for application
*/
bool is_allowed_to_issue(ItsAid aid) const;
/**
* Get subject assurance level encoded in this certificate.
*
* \return raw assurance level byte if present
*/
boost::optional<std::uint8_t> assurance_level() const;
/**
* Check if this certificate's region restriction is within issuer's region restriction.
*
* If issuer has no region restriction, any subject region is accepted.
* Currently supports circular regions and exact rectangular-region equality;
* unsupported region combinations are rejected conservatively.
*
* \param issuer issuing certificate
* \return true if this certificate's region is contained in issuer's region
*/
bool region_is_within(const CertificateView& issuer) const;
/**
* Check if certificate has a canonical format
* \return true if certificate is in canonical format
*/
bool is_canonical() const;
/**
* Convert certificate into its canonical format if possible.
* \return canonical certificate (or none if conversion failed)
*/
boost::optional<Certificate> canonicalize() const;
/**
* Encode certificate.
* \return encoded certificate
*/
ByteBuffer encode() const;
protected:
const asn1::EtsiTs103097Certificate* m_cert = nullptr;
};
struct Certificate : public asn1::asn1c_oer_wrapper<asn1::EtsiTs103097Certificate>, public CertificateView
{
using Wrapper = asn1::asn1c_oer_wrapper<asn1::EtsiTs103097Certificate>;
Certificate();
explicit Certificate(const asn1::EtsiTs103097Certificate&);
Certificate(const Certificate&);
Certificate& operator=(const Certificate&);
Certificate(Certificate&&);
Certificate& operator=(Certificate&&);
// resolve ambiguity
ByteBuffer encode() const;
/**
* \brief add application permissions as bitmap
*
* \param aid application identifier
* \param ssp permission bitmap
*/
void add_app_permission(ItsAid aid, const ByteBuffer& ssp);
/**
* \brief add cert issuing permission
*
* \param group_permission to be added permission
*/
void add_cert_issue_permission(asn1::PsidGroupPermissions* group_permission);
void set_signature(const SomeEcdsaSignature& signature);
};
/**
* Calculate digest of v3 certificate
* \param cert certificate
* \return digest if possible
*/
boost::optional<HashedId8> calculate_digest(const asn1::EtsiTs103097Certificate& cert);
/**
* Check if certificate is in canonical format suitable for digest calculation.
* \param cert certificate
* \return true if certificate is in canonical format
*/
bool is_canonical(const asn1::EtsiTs103097Certificate& cert);
/**
* Convert certificate into its canonical format if possible.
* \param cert certificate
* \return canonical certificate (or none if conversion failed)
*/
boost::optional<Certificate> canonicalize(const asn1::EtsiTs103097Certificate& cert);
/**
* Check if certificate is valid at given time point
*
* \param cert certificate to be checked
* \param time_point time point to be checked
* \return true if certificate is valid at time point
*/
bool valid_at_timepoint(const asn1::EtsiTs103097Certificate& cert, const Clock::time_point& time_point);
/**
* Check if certificate is valid for given application
*
* \param cert certificate to be checked
* \param aid application to be checked
* \return true if certificate is valid for application
*/
bool valid_for_application(const asn1::EtsiTs103097Certificate& cert, ItsAid aid);
/**
* Extract the public key out of a certificate
* \param cert certificate
* \return public key if possible
*/
boost::optional<PublicKey> get_public_key(const asn1::EtsiTs103097Certificate& cert);
/**
* Get verification key type
* \param cert certificate
* \return verification key type (maybe unspecified)
*/
KeyType get_verification_key_type(const asn1::EtsiTs103097Certificate& cert);
/**
* Extract the public key for encrypting out of a certificate
* \param cert certificate
* \return encryption key if possible
*/
boost::optional<PublicKey> get_public_encryption_key(const asn1::EtsiTs103097Certificate& cert);
/**
* Extract the signature out of a certificate
* \param cert certificate
* \return signature if possible
*/
boost::optional<Signature> get_signature(const asn1::EtsiTs103097Certificate& cert);
/**
* Get list of ITS AID permissions from certificate
* \param cert certificate
* \return list of ITS AIDs
*/
std::list<ItsAid> get_aids(const asn1::EtsiTs103097Certificate& cert);
/**
* Get application permissions (SSP = service specific permissions)
* \param cert certificate containing application permissions
* \param aid look up permissions for this application identifier
* \return SSP bitmap or empty buffer
*/
ByteBuffer get_app_permissions(const asn1::EtsiTs103097Certificate& cert, ItsAid aid);
void add_psid_group_permission(asn1::PsidGroupPermissions* group_permission, ItsAid aid, const ByteBuffer& ssp, const ByteBuffer& bitmask);
void serialize(OutputArchive& ar, const Certificate& certificate);
Certificate fake_certificate();
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,59 @@
#include <vanetza/security/v3/certificate_cache.hpp>
#include <boost/optional/optional.hpp>
namespace vanetza
{
namespace security
{
namespace v3
{
const Certificate* CertificateCache::lookup(const HashedId8& digest) const
{
auto found = m_storage.find(digest);
if (found != m_storage.end()) {
return &found->second;
} else {
return nullptr;
}
}
const Certificate* CertificateCache::lookup(const HashedId3& digest) const
{
auto found = m_short_digests.find(digest);
if (found != m_short_digests.end()) {
return &found->second->second;
} else {
return nullptr;
}
}
void CertificateCache::store(Certificate cert)
{
auto maybe_hash = cert.calculate_digest();
if (maybe_hash) {
CertificateMap::iterator it;
bool inserted;
std::tie(it, inserted) = m_storage.emplace(*maybe_hash, std::move(cert));
if (inserted) {
m_short_digests.emplace(truncate(*maybe_hash), it);
m_digests.insert(*maybe_hash);
}
}
}
bool CertificateCache::announce(const HashedId8& digest)
{
bool inserted = false;
std::tie(std::ignore, inserted) = m_digests.insert(digest);
return inserted;
}
bool CertificateCache::is_known(const HashedId8& digest) const
{
return m_digests.find(digest) != m_digests.end();
}
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,64 @@
#pragma once
#include <vanetza/security/hashed_id.hpp>
#include <vanetza/security/v3/certificate.hpp>
#include <unordered_map>
#include <unordered_set>
namespace vanetza
{
namespace security
{
namespace v3
{
/**
* CertificateCache stores validated v1.3.1 certificates for later lookup.
* Required for checking messages' signatures containing only a certificate digest.
*/
class CertificateCache
{
public:
/**
* Lookup certificate based on given digest
* \param digest certificate digest
* \return certificate matching digest
*/
const Certificate* lookup(const HashedId8& digest) const;
const Certificate* lookup(const HashedId3& digest) const;
/**
* Store a (pre-validated) certificate in cache
* \param cert certificate
*/
void store(Certificate cert);
size_t size() const { return m_storage.size(); }
/**
* Announce a station with a given certificate digest.
* \param digest certificate digest
* \return true if digest was not known before
*/
bool announce(const HashedId8& digest);
/**
* Test if a certificate digest is already known, i.e. either
* its certificate is stored or at least the digest has been announced.
* \param digest certificate digest
* \return true if digest is known
*/
bool is_known(const HashedId8& digest) const;
private:
using CertificateMap = std::unordered_map<HashedId8, Certificate>;
using ShortDigestMap = std::unordered_map<HashedId3, CertificateMap::iterator>;
// TODO add bounded capacity and automatic removal of expired certificates
CertificateMap m_storage;
ShortDigestMap m_short_digests;
std::unordered_set<HashedId8> m_digests;
};
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,50 @@
#pragma once
#include <vanetza/security/private_key.hpp>
#include <vanetza/security/v3/certificate.hpp>
#include <vanetza/security/v3/certificate_cache.hpp>
namespace vanetza
{
namespace security
{
namespace v3
{
class CertificateProvider
{
public:
/**
* Get own certificate to use for signing
* \return own certificate
*/
virtual const Certificate& own_certificate() = 0;
/**
* Get private key associated with own certificate
* \return private key
*/
virtual const PrivateKey& own_private_key() = 0;
/**
* Get certificate cache
* \return certificate cache
*/
virtual CertificateCache& cache() = 0;
virtual const CertificateCache& cache() const = 0;
virtual ~CertificateProvider() = default;
};
class BaseCertificateProvider : public CertificateProvider
{
public:
const CertificateCache& cache() const override { return m_cache; }
CertificateCache& cache() override { return m_cache; }
private:
CertificateCache m_cache;
};
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,261 @@
#include <vanetza/common/position_provider.hpp>
#include <vanetza/common/position_fix.hpp>
#include <vanetza/common/runtime.hpp>
#include <vanetza/security/v3/certificate.hpp>
#include <vanetza/security/v3/certificate_validator.hpp>
#include <vanetza/security/v3/issuer_lookup.hpp>
#include <vanetza/security/v3/revocation_lookup.hpp>
#include <vanetza/security/v3/trust_store.hpp>
#include <cstdint>
namespace vanetza
{
namespace security
{
namespace v3
{
namespace
{
bool check_time_consistency(const CertificateView& subject, const CertificateView& issuer)
{
const auto subject_time = subject.get_start_and_end_validity();
const auto issuer_time = issuer.get_start_and_end_validity();
return issuer_time.start_validity <= subject_time.start_validity &&
issuer_time.end_validity >= subject_time.end_validity;
}
bool check_permission_consistency(const CertificateView& subject, const CertificateView& issuer, ItsAid its_aid)
{
if (subject.is_ca_certificate() && !subject.is_allowed_to_issue(its_aid)) {
return false;
} else if (subject.is_at_certificate() && !subject.valid_for_application(its_aid)) {
return false;
}
return issuer.is_allowed_to_issue(its_aid);
}
bool check_assurance_consistency(const CertificateView& subject, const CertificateView& issuer)
{
const auto subject_assurance = subject.assurance_level();
const auto issuer_assurance = issuer.assurance_level();
if (!subject_assurance) {
return true;
} else if (!issuer_assurance) {
return false;
}
const auto subject_value = *subject_assurance;
const auto issuer_value = *issuer_assurance;
const std::uint8_t subject_level = (subject_value >> 5) & 0x07;
const std::uint8_t issuer_level = (issuer_value >> 5) & 0x07;
const std::uint8_t subject_confidence = (subject_value >> 2) & 0x07;
const std::uint8_t issuer_confidence = (issuer_value >> 2) & 0x07;
return subject_level < issuer_level ||
(subject_level == issuer_level && subject_confidence <= issuer_confidence);
}
bool check_region_consistency(const CertificateView& subject, const CertificateView& issuer)
{
return subject.region_is_within(issuer);
}
} // namespace
auto DefaultCertificateValidator::valid_for_signing(const CertificateView& signing_cert, ItsAid its_aid) -> Verdict
{
if (!m_disable_time_checks && !m_runtime) {
return Verdict::Misconfiguration;
} else if (!m_disable_location_checks && (!m_position_provider || !m_location_checker)) {
return Verdict::Misconfiguration;
} else if (!signing_cert.valid_for_application(its_aid)) {
return Verdict::InsufficientPermission;
} else if (m_runtime && !signing_cert.valid_at_timepoint(m_runtime->now())) {
return Verdict::Expired;
} else if (!is_chain_anchored(signing_cert)) {
return Verdict::Untrusted;
} else if (!chain_is_consistent(signing_cert, its_aid)) {
return Verdict::InconsistentChain;
} else if (chain_is_revoked(signing_cert)) {
return Verdict::Revoked;
} else {
Verdict verdict = Verdict::Valid;
if (!m_disable_location_checks) {
if (m_position_provider) {
auto location = m_position_provider->position_fix();
if (signing_cert.has_region_restriction()) {
if (!signing_cert.valid_at_location(location, m_location_checker)) {
verdict = Verdict::OutsideRegion;
}
} else {
auto issuing_cert = find_issuer_certificate(signing_cert);
if (issuing_cert && !issuing_cert->valid_at_location(location, m_location_checker)) {
verdict = Verdict::OutsideRegion;
}
}
}
}
return verdict;
}
}
void DefaultCertificateValidator::use_runtime(const Runtime* runtime)
{
m_runtime = runtime;
}
void DefaultCertificateValidator::use_position_provider(PositionProvider* pp)
{
m_position_provider = pp;
}
void DefaultCertificateValidator::use_issuer_lookup(const IssuerLookup* lookup)
{
m_issuer_lookup = lookup;
}
void DefaultCertificateValidator::use_location_checker(const LocationChecker* checker)
{
m_location_checker = checker;
}
void DefaultCertificateValidator::use_revocation_lookup(const RevocationLookup* lookup)
{
m_revocation_lookup = lookup;
}
void DefaultCertificateValidator::use_trust_store(const TrustStore* store)
{
m_trust_store = store;
}
void DefaultCertificateValidator::disable_time_checks(bool flag)
{
m_disable_time_checks = flag;
}
void DefaultCertificateValidator::disable_location_checks(bool flag)
{
m_disable_location_checks = flag;
}
void DefaultCertificateValidator::disable_chain_consistency_checks(bool flag)
{
m_disable_chain_consistency_checks = flag;
}
void DefaultCertificateValidator::disable_region_consistency_checks(bool flag)
{
m_disable_region_consistency_checks = flag;
}
const Certificate* DefaultCertificateValidator::find_issuer_certificate(const CertificateView& at_cert) const
{
if (m_issuer_lookup) {
auto maybe_issuer_digest = at_cert.issuer_digest();
if (maybe_issuer_digest) {
return m_issuer_lookup->find_issuer(*maybe_issuer_digest);
}
}
return nullptr;
}
bool DefaultCertificateValidator::is_chain_anchored(const CertificateView& signing_cert) const
{
if (!m_trust_store || !m_issuer_lookup) {
// No anchoring policy configured: fail open, matching the existing optional-injection pattern.
return true;
}
constexpr int max_chain_depth = 8;
const CertificateView* cert = &signing_cert;
for (int depth = 0; depth < max_chain_depth; ++depth) {
if (cert->issuer_is_self()) {
// Reached a self-signed cert: anchored iff it's in the trust store.
const auto cert_digest = cert->calculate_digest();
return cert_digest && !m_trust_store->lookup(*cert_digest).empty();
}
const auto issuer_digest = cert->issuer_digest();
if (!issuer_digest) {
return false;
}
const Certificate* issuer = m_issuer_lookup->find_issuer(*issuer_digest);
if (!issuer) {
return false;
}
cert = issuer;
}
return false;
}
bool DefaultCertificateValidator::chain_is_consistent(const CertificateView& signing_cert, ItsAid its_aid) const
{
if (m_disable_chain_consistency_checks) {
return true;
}
if (!m_issuer_lookup) {
return true;
}
constexpr int max_chain_depth = 8;
const CertificateView* subject = &signing_cert;
for (int depth = 0; depth < max_chain_depth && !subject->issuer_is_self(); ++depth) {
const Certificate* issuer = find_issuer_certificate(*subject);
if (!issuer) {
return true;
}
if (!check_consistency(*subject, *issuer, its_aid)) {
return false;
}
subject = issuer;
}
return true;
}
bool DefaultCertificateValidator::chain_is_revoked(const CertificateView& signing_cert) const
{
if (!m_revocation_lookup || !m_issuer_lookup) {
return false;
}
// C-ITS chains are AT -> AA -> RCA (depth 3). The bound is defense against IssuerLookup cycles.
// The walk stops at the self-signed root: RCA revocation is the ECTL's job, not a CRL's.
constexpr int max_chain_depth = 8;
const CertificateView* cert = &signing_cert;
for (int depth = 0; depth < max_chain_depth; ++depth) {
const auto cert_digest = cert->calculate_digest();
const auto issuer_digest = cert->issuer_digest();
if (!cert_digest || !issuer_digest) {
break;
}
if (m_revocation_lookup->is_revoked(*issuer_digest, *cert_digest)) {
return true;
}
const Certificate* issuer = m_issuer_lookup->find_issuer(*issuer_digest);
if (!issuer || issuer->issuer_is_self()) {
break;
}
cert = issuer;
}
return false;
}
bool DefaultCertificateValidator::check_consistency(
const CertificateView& subject, const CertificateView& issuer, ItsAid its_aid) const
{
return check_time_consistency(subject, issuer) &&
check_permission_consistency(subject, issuer, its_aid) &&
check_assurance_consistency(subject, issuer) &&
(m_disable_region_consistency_checks || check_region_consistency(subject, issuer));
}
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,97 @@
#pragma once
#include <vanetza/common/its_aid.hpp>
#include <vanetza/security/v3/location_checker.hpp>
namespace vanetza
{
// forward declarations
class PositionProvider;
class Runtime;
namespace security
{
namespace v3
{
// forward declarations
class Certificate;
class CertificateView;
class IssuerLookup;
class RevocationLookup;
class TrustStore;
class CertificateValidator
{
public:
enum class Verdict
{
Unknown,
Valid,
Expired,
Revoked,
Untrusted,
InconsistentChain,
OutsideRegion,
InsufficientPermission,
Misconfiguration,
};
/**
* Check if a certificate can be used for signing a message
* \param certificate pre-validated AT certificate
* \param app ITS-AID of the message to be signed
*/
virtual Verdict valid_for_signing(const CertificateView& certificate, ItsAid app) = 0;
virtual ~CertificateValidator() = default;
};
class DefaultCertificateValidator : public CertificateValidator
{
public:
Verdict valid_for_signing(const CertificateView&, ItsAid) override;
void use_runtime(const Runtime* runtime);
void use_position_provider(PositionProvider* provider);
void use_issuer_lookup(const IssuerLookup* lookup);
void use_location_checker(const LocationChecker* checker);
void use_revocation_lookup(const RevocationLookup* lookup);
void use_trust_store(const TrustStore* store);
void disable_time_checks(bool flag);
void disable_location_checks(bool flag);
void disable_chain_consistency_checks(bool flag);
void disable_region_consistency_checks(bool flag);
private:
const Certificate* find_issuer_certificate(const CertificateView& certificate) const;
bool chain_is_consistent(const CertificateView& signing_cert, ItsAid its_aid) const;
bool chain_is_revoked(const CertificateView& signing_cert) const;
bool check_consistency(const CertificateView& subject, const CertificateView& issuer, ItsAid its_aid) const;
bool is_chain_anchored(const CertificateView& signing_cert) const;
const Runtime* m_runtime = nullptr;
PositionProvider* m_position_provider = nullptr;
const IssuerLookup* m_issuer_lookup = nullptr;
const LocationChecker* m_location_checker = nullptr;
const RevocationLookup* m_revocation_lookup = nullptr;
const TrustStore* m_trust_store = nullptr;
bool m_disable_time_checks = false;
bool m_disable_location_checks = false;
bool m_disable_chain_consistency_checks = false;
bool m_disable_region_consistency_checks = false;
};
class NullCertificateValidator : public CertificateValidator
{
public:
Verdict valid_for_signing(const CertificateView&, ItsAid) override
{
return Verdict::Valid;
}
};
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,53 @@
#include <vanetza/common/position_fix.hpp>
#include <vanetza/asn1/security_profile.hpp>
#include VANETZA_ASN1_SECURITY_HEADER(Latitude.h)
#include VANETZA_ASN1_SECURITY_HEADER(Longitude.h)
#include VANETZA_ASN1_SECURITY_HEADER(TwoDLocation.h)
#include <vanetza/security/v3/distance.hpp>
#include <boost/units/cmath.hpp>
namespace vanetza
{
namespace security
{
namespace v3
{
units::Length distance(const PositionFix& one, const asn1::TwoDLocation& other)
{
static const auto earth_radius = 6371000.0 * units::si::meter;
const auto other_lat = convert_latitude(other.latitude);
const auto other_lon = convert_longitude(other.longitude);
const units::Angle delta_phi { one.latitude - other_lat };
const units::Angle delta_lambda { one.longitude - other_lon };
const auto sin_delta_phi = sin(delta_phi / 2.0);
const auto sin_delta_lambda = sin(delta_lambda / 2.0);
const auto a = sin_delta_phi * sin_delta_phi +
cos(one.latitude) * cos(other_lat) * sin_delta_lambda * sin_delta_lambda;
const auto c = 2.0 * atan2(sqrt(a), sqrt(1 - a));
return earth_radius * c;
}
units::GeoAngle convert_latitude(const asn1::Latitude& in)
{
static constexpr long latitude_scale = Vanetza_Security_NinetyDegreeInt_max;
if (in >= Vanetza_Security_NinetyDegreeInt_min && in <= Vanetza_Security_NinetyDegreeInt_max) {
return in * 90.0 / latitude_scale * units::degree;
} else {
return units::GeoAngle::from_value(std::numeric_limits<double>::quiet_NaN());
}
}
units::GeoAngle convert_longitude(const asn1::Longitude& in)
{
static constexpr long longitude_scale = Vanetza_Security_OneEightyDegreeInt_max;
if (in >= Vanetza_Security_OneEightyDegreeInt_min && in <= Vanetza_Security_OneEightyDegreeInt_max) {
return in * 180.0 / longitude_scale * units::degree;
} else {
return units::GeoAngle::from_value(std::numeric_limits<double>::quiet_NaN());
}
}
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,44 @@
#pragma once
#include <vanetza/security/v3/asn1_types.hpp>
#include <vanetza/units/angle.hpp>
#include <vanetza/units/length.hpp>
namespace vanetza
{
// forward declaration
struct PositionFix;
namespace security
{
namespace v3
{
/**
* Calculate distance between two positions using Haversine formula
*
* \param one a local position fix
* \param other a received location
* \return distance in meters
*/
units::Length distance(const PositionFix& one, const asn1::TwoDLocation& other);
/**
* Convert ASN.1 latitude to GeoAngle
*
* \param in ASN.1 security latitude
* \return GeoAngle
*/
units::GeoAngle convert_latitude(const asn1::Latitude& in);
/**
* Convert ASN.1 longitude to GeoAngle
*
* \param in ASN.1 security longitude
* \return GeoAngle
*/
units::GeoAngle convert_longitude(const asn1::Longitude& in);
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,103 @@
#include <vanetza/asn1/security_profile.hpp>
#include VANETZA_ASN1_SECURITY_HEADER(CircularRegion.h)
#include VANETZA_ASN1_SECURITY_HEADER(PolygonalRegion.h)
#include VANETZA_ASN1_SECURITY_HEADER(RectangularRegion.h)
#include VANETZA_ASN1_SECURITY_HEADER(SequenceOfRectangularRegion.h)
#include VANETZA_ASN1_SECURITY_HEADER(TwoDLocation.h)
#include <vanetza/common/position_fix.hpp>
#include <vanetza/security/v3/boost_geometry.hpp>
#include <vanetza/security/v3/distance.hpp>
#include <vanetza/security/v3/geometry.hpp>
#include <boost/geometry/algorithms/within.hpp>
#include <boost/units/cmath.hpp>
namespace vanetza
{
namespace security
{
namespace v3
{
bool is_valid(const asn1::TwoDLocation& location)
{
return location.latitude >= Vanetza_Security_NinetyDegreeInt_min &&
location.latitude <= Vanetza_Security_NinetyDegreeInt_max &&
location.longitude >= Vanetza_Security_OneEightyDegreeInt_min &&
location.longitude <= Vanetza_Security_OneEightyDegreeInt_max;
}
bool is_inside(const PositionFix& location, const asn1::CircularRegion& region)
{
if (is_valid(region.center) && region.radius >= 0) {
return distance(location, region.center) <= region.radius * units::si::meter;
} else {
return false;
}
}
bool is_inside(const PositionFix& location, const asn1::SequenceOfRectangularRegion& regions)
{
for (int i = 0; i < regions.list.count; ++i) {
if (regions.list.array[i] != nullptr && is_inside(location, *regions.list.array[i])) {
return true;
}
}
return false;
}
bool is_inside(const PositionFix& location, const asn1::RectangularRegion& region)
{
const bool location_valid = isfinite(location.latitude) && isfinite(location.longitude);
const bool region_valid = is_valid(region.northWest) && is_valid(region.southEast);
if (location_valid && region_valid) {
if (region.northWest.latitude <= region.southEast.latitude) {
// north-west is equal or south of south-east: invalid region
return false;
} else if (region.northWest.longitude == region.southEast.longitude) {
// equal longitudes are invalid
return false;
} else {
static constexpr long scale_90deg = Vanetza_Security_NinetyDegreeInt_max / 90;
static constexpr long scale_180deg = Vanetza_Security_OneEightyDegreeInt_max / 180;
Vanetza_Security_NinetyDegreeInt_t loc_lat = location.latitude / units::degree * scale_90deg;
Vanetza_Security_OneEightyDegreeInt_t loc_lon = location.longitude / units::degree * scale_180deg;
if (loc_lat >= region.southEast.latitude && loc_lat <= region.northWest.latitude) {
if (region.northWest.longitude < region.southEast.longitude) {
return loc_lon >= region.northWest.longitude && loc_lon <= region.southEast.longitude;
} else {
return loc_lon >= region.northWest.longitude || loc_lon <= region.southEast.longitude;
}
}
}
}
return false;
}
bool is_inside(const asn1::TwoDLocation* location, const asn1::PolygonalRegion* region)
{
if (region && location) {
PolygonalRegionRingAdapter polygonal_region(*region);
auto point = make_model(*location);
return boost::geometry::within(point, polygonal_region);
}
return false;
}
bool is_inside(const PositionFix& location, const asn1::PolygonalRegion& region)
{
if (isfinite(location.latitude) && isfinite(location.longitude)) {
PolygonalRegionRingAdapter polygonal_region(region);
auto point = make_model(location);
return boost::geometry::within(point, polygonal_region);
}
return false;
}
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,63 @@
#pragma once
#include <vanetza/security/v3/asn1_types.hpp>
namespace vanetza
{
// forward declaration
struct PositionFix;
namespace security
{
namespace v3
{
/**
* Check if a TwoDLocation is valid
*
* \param location TwoDLocation to be checked
* \return true if latitude and longitude are within valid range
*/
bool is_valid(const asn1::TwoDLocation& location);
/**
* Check if position is inside a circular region
*
* \param pos position to be checked
* \param region circular region
* \return true if position is inside region
*/
bool is_inside(const PositionFix& pos, const asn1::CircularRegion& region);
/**
* Check if position is inside at least one of the given regions
*
* \param pos position to be checked
* \param regions sequence of rectangular regions
* \return true if position is inside at least one region
*/
bool is_inside(const PositionFix& pos, const asn1::SequenceOfRectangularRegion& region);
/**
* Check if position is inside a rectangular region
*
* \param pos position to be checked
* \param region rectangular region
* \return true if position is inside region
*/
bool is_inside(const PositionFix& pos, const asn1::RectangularRegion& region);
/**
* \brief Check if a location is inside a polygonal region.
*
* \param location The location to check.
* \param region The polygonal region to check against.
* \return true if the location is inside the region, false otherwise.
*/
bool is_inside(const PositionFix& location, const asn1::PolygonalRegion& region);
bool is_inside(const asn1::TwoDLocation* location, const asn1::PolygonalRegion* region);
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,48 @@
#include <vanetza/security/backend.hpp>
#include <vanetza/security/v3/certificate.hpp>
#include <vanetza/security/v3/hash.hpp>
namespace vanetza
{
namespace security
{
namespace v3
{
ByteBuffer calculate_message_hash(Backend& backend, HashAlgorithm hash_algo, const ByteBuffer& payload, const CertificateView& signing_cert)
{
ByteBuffer encoded_cert;
if (signing_cert.is_canonical()) {
encoded_cert = signing_cert.encode();
} else {
auto canonical_signing_cert = signing_cert.canonicalize();
if (canonical_signing_cert) {
encoded_cert = canonical_signing_cert->encode();
}
}
ByteBuffer data_hash = backend.calculate_hash(hash_algo, payload);
ByteBuffer cert_hash = backend.calculate_hash(hash_algo, encoded_cert);
ByteBuffer concat_hash;
concat_hash.reserve(data_hash.size() + cert_hash.size());
concat_hash.insert(concat_hash.end(), data_hash.begin(), data_hash.end());
concat_hash.insert(concat_hash.end(), cert_hash.begin(), cert_hash.end());
return backend.calculate_hash(hash_algo, concat_hash);
}
HashAlgorithm specified_hash_algorithm(KeyType key_type)
{
switch (key_type) {
case KeyType::NistP256:
case KeyType::BrainpoolP256r1:
return HashAlgorithm::SHA256;
case KeyType::BrainpoolP384r1:
return HashAlgorithm::SHA384;
default:
return HashAlgorithm::Unspecified;
}
}
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,44 @@
#pragma once
#include <vanetza/common/byte_buffer.hpp>
#include <vanetza/security/hash_algorithm.hpp>
#include <vanetza/security/key_type.hpp>
namespace vanetza
{
namespace security
{
// forward declarations
class Backend;
namespace v3
{
// forward declarations
class CertificateView;
/**
* Calculate message hash (combination of hashes).
*
* This function creates the message hash according to IEEE 1609.2 cause 5.3.1.2.2
* for verification type "certificate", i.e. not "self-signed" messages.
*
* \param backend backend for cryptographic operations
* \param algo hash algorithm
* \param data message payload (data to be signed)
* \param signing certificate used for signing
* \return message digest
*/
ByteBuffer calculate_message_hash(Backend&, HashAlgorithm, const ByteBuffer& data, const CertificateView& signing);
/**
* Determine the hash algorithm for a given key type.
* \see IEEE 1609.2 clause 5.3.1.2.2 rule a)
* \param key_type key type
* \return suitable hash algorithm
*/
HashAlgorithm specified_hash_algorithm(KeyType key_type);
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,31 @@
#pragma once
#include <vanetza/security/hashed_id.hpp>
namespace vanetza
{
namespace security
{
namespace v3
{
class Certificate;
/**
* Resolves a signing certificate's issuer by digest, typically backed by
* PKI-managed trust material rather than the peer-AT CertificateCache.
*/
class IssuerLookup
{
public:
virtual ~IssuerLookup() = default;
/**
* \param digest issuer's HashedId8
* \return pointer to the issuer certificate, or nullptr if unknown.
*/
virtual const Certificate* find_issuer(const HashedId8& digest) const = 0;
};
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,36 @@
#include <vanetza/security/v3/issuer_memory_lookup.hpp>
namespace vanetza
{
namespace security
{
namespace v3
{
bool IssuerMemoryLookup::insert(const Certificate& cert)
{
if (!cert.is_ca_certificate()) {
return false;
}
auto digest = cert.calculate_digest();
if (!digest) {
return false;
}
auto it = m_certificates.find(*digest);
if (it != m_certificates.end()) {
it->second = cert;
} else {
m_certificates.emplace(*digest, cert);
}
return true;
}
const Certificate* IssuerMemoryLookup::find_issuer(const HashedId8& digest) const
{
auto it = m_certificates.find(digest);
return it == m_certificates.end() ? nullptr : &it->second;
}
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,34 @@
#pragma once
#include <vanetza/security/v3/certificate.hpp>
#include <vanetza/security/v3/issuer_lookup.hpp>
#include <unordered_map>
namespace vanetza
{
namespace security
{
namespace v3
{
/**
* In-memory IssuerLookup populated programmatically with CA certificates.
*/
class IssuerMemoryLookup : public IssuerLookup
{
public:
/**
* Insert or replace a CA certificate, keyed by its own HashedId8 digest.
* \param cert CA certificate
* \return true if stored, false if rejected (not a CA cert or digest unavailable)
*/
bool insert(const Certificate& cert);
const Certificate* find_issuer(const HashedId8& digest) const override;
private:
std::unordered_map<HashedId8, Certificate> m_certificates;
};
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,86 @@
#include <vanetza/common/position_fix.hpp>
#include <vanetza/geodesy/country_database.hpp>
#include <vanetza/geodesy/m49_code.hpp>
#include <vanetza/asn1/security_profile.hpp>
#include VANETZA_ASN1_SECURITY_HEADER(IdentifiedRegion.h)
#include VANETZA_ASN1_SECURITY_HEADER(SequenceOfIdentifiedRegion.h)
#include <vanetza/security/v3/asn1_types.hpp>
#include <vanetza/security/v3/distance.hpp>
#include <vanetza/security/v3/geometry.hpp>
#include <vanetza/security/v3/location_checker.hpp>
namespace vanetza
{
namespace security
{
namespace v3
{
bool AllowLocationChecker::valid_at_location(const asn1::EtsiTs103097Certificate&, const PositionFix&) const
{
return true;
}
bool DenyLocationChecker::valid_at_location(const asn1::EtsiTs103097Certificate&, const PositionFix&) const
{
return false;
}
bool DefaultLocationChecker::valid_at_location(const asn1::EtsiTs103097Certificate& cert, const PositionFix& location) const
{
const asn1::GeographicRegion* region = cert.toBeSigned.region;
if (region) {
switch (region->present) {
case Vanetza_Security_GeographicRegion_PR_circularRegion:
return is_inside(location, region->choice.circularRegion);
case Vanetza_Security_GeographicRegion_PR_rectangularRegion:
return is_inside(location, region->choice.rectangularRegion);
case Vanetza_Security_GeographicRegion_PR_polygonalRegion:
return is_inside(location, region->choice.polygonalRegion);
case Vanetza_Security_GeographicRegion_PR_identifiedRegion:
return check_identified_region(location, region->choice.identifiedRegion);
default:
// unknown or future region restriction type — reject conservatively
return false;
}
} else {
// no region restriction applies
return true;
}
}
bool DefaultLocationChecker::check_identified_region(const PositionFix& location, const Vanetza_Security_SequenceOfIdentifiedRegion& seq) const
{
if (!country_db_ || country_db_->empty())
{
// lacking database for country checks
return permissive_identified_region_;
}
// reject any region by default
bool accepted = false;
for (int i = 0; i < seq.list.count; ++i) {
const auto* entry = seq.list.array[i];
if (!entry) {
continue;
}
if (entry->present == Vanetza_Security_IdentifiedRegion_PR_countryOnly) {
geodesy::M49Code code(static_cast<uint16_t>(entry->choice.countryOnly));
geodesy::GeodeticPosition pos(location.latitude, location.longitude);
if (country_db_->is_inside(code, pos)) {
return true;
}
} else {
// may accept because of unsupported identified region
accepted = permissive_identified_region_;
}
}
return accepted;
}
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,102 @@
#pragma once
#include <vanetza/asn1/security_profile.hpp>
#include VANETZA_ASN1_SECURITY_HEADER(Certificate.h)
#include <vanetza/security/v3/asn1_types.hpp>
// forward declaration
struct Vanetza_Security_SequenceOfIdentifiedRegion;
namespace vanetza
{
// forward declarations
struct PositionFix;
namespace geodesy { class CountryDatabase; }
namespace security
{
namespace v3
{
/**
* LocationChecker Interface
* Abstract base class defining the interface for location validation.
*/
class LocationChecker
{
public:
// Returns true if the given PositionFix lies within the specified GeographicRegion.
virtual bool valid_at_location(const asn1::EtsiTs103097Certificate& cert, const PositionFix& location) const = 0;
virtual ~LocationChecker() = default;
};
/**
* Always allow all the requests
*/
class AllowLocationChecker : public LocationChecker
{
public:
bool valid_at_location(const asn1::EtsiTs103097Certificate& cert, const PositionFix& location) const override;
};
/**
* Always deny all the requests
*/
class DenyLocationChecker : public LocationChecker
{
public:
bool valid_at_location(const asn1::EtsiTs103097Certificate& cert, const PositionFix& location) const override;
};
/**
* Default implementation that checks whether a position lies within a certificate's GeographicRegion.
* Supports: no restriction, CircularRegion, RectangularRegion, PolygonalRegion, IdentifiedRegion
* (only ISO 3166/M49 country codes).
* For unsupported IdentifiedRegion variants, the permissive flag controls their acceptance or rejection.
* IdentifiedRegion (ISO 3166 country codes): unsupported; returns false (conservative) unless
* Entirely unknown region types are always rejected conservatively.
*/
class DefaultLocationChecker : public LocationChecker
{
public:
bool valid_at_location(const asn1::EtsiTs103097Certificate& cert, const PositionFix& location) const override;
/**
* Change permissive behaviour regarding unsupported IdentifiedRegion types.
*
* When set to true, unsupported IdentifiedRegion (country and region, country and sub-regions)
* are not checked but still accepted. These regions are only defined for the United States, i.e.
* they have no meaning for European C-ITS deployments.
* Default behaviour is conservative rejection (false), user must explicitly opt in.
*/
void set_permissive_identified_region(bool permissive)
{
permissive_identified_region_ = permissive;
}
bool permissive_identified_region() const
{
return permissive_identified_region_;
}
/**
* Register country database for IdentifiedRegion validations.
*
* If a database is registered, validation of countryOnly IdentifiedRegion takes place.
* If database is missing, validation falls back to the defined permissive behaviour.
*/
void use_country_database(const geodesy::CountryDatabase* db)
{
country_db_ = db;
}
private:
bool check_identified_region(const PositionFix& location, const Vanetza_Security_SequenceOfIdentifiedRegion& seq) const;
bool permissive_identified_region_ = false;
const geodesy::CountryDatabase* country_db_ = nullptr;
};
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,305 @@
#include <vanetza/common/its_aid.hpp>
#include <vanetza/security/v3/naive_certificate_provider.hpp>
#include <array>
namespace vanetza
{
namespace security
{
namespace v3
{
namespace
{
struct assign_compressed_ecc_point : public boost::static_visitor<>
{
assign_compressed_ecc_point(Vanetza_Security_EccP256CurvePoint* point) : point(point)
{
}
void operator()(const Compressed_Lsb_Y_0& y0) const
{
point->present = Vanetza_Security_EccP256CurvePoint_PR_compressed_y_0;
OCTET_STRING_fromBuf(&point->choice.compressed_y_0, reinterpret_cast<const char*>(y0.x.data()), y0.x.size());
}
void operator()(const Compressed_Lsb_Y_1& y1) const
{
point->present = Vanetza_Security_EccP256CurvePoint_PR_compressed_y_1;
OCTET_STRING_fromBuf(&point->choice.compressed_y_1, reinterpret_cast<const char*>(y1.x.data()), y1.x.size());
}
template<typename T>
void operator()(const T&) const
{
point->present = Vanetza_Security_EccP256CurvePoint_PR_NOTHING;
}
Vanetza_Security_EccP256CurvePoint* point = nullptr;
};
} // namespace
NaiveCertificateProvider::NaiveCertificateProvider(const Runtime& rt) :
m_crypto_backend(create_backend("default")),
m_runtime(rt),
m_own_key_pair(m_crypto_backend->generate_key_pair()),
m_own_certificate(generate_authorization_ticket()) { }
const Certificate& NaiveCertificateProvider::own_certificate()
{
// Implement the renewal
return m_own_certificate;
}
const PrivateKey& NaiveCertificateProvider::own_private_key()
{
static PrivateKey private_key;
private_key.type = KeyType::NistP256;
private_key.key.resize(m_own_key_pair.private_key.key.size());
std::copy(m_own_key_pair.private_key.key.begin(), m_own_key_pair.private_key.key.end(), private_key.key.data());
return private_key;
}
const ecdsa256::KeyPair& NaiveCertificateProvider::aa_key_pair()
{
static const ecdsa256::KeyPair aa_key_pair = m_crypto_backend->generate_key_pair();
return aa_key_pair;
}
const ecdsa256::KeyPair& NaiveCertificateProvider::root_key_pair()
{
static const ecdsa256::KeyPair root_key_pair = m_crypto_backend->generate_key_pair();
return root_key_pair;
}
const Certificate& NaiveCertificateProvider::aa_certificate()
{
static const std::string aa_subject("Naive Authorization CA");
static const Certificate aa_certificate = generate_aa_certificate(aa_subject);
return aa_certificate;
}
const Certificate& NaiveCertificateProvider::root_certificate()
{
static const std::string root_subject("Naive Root CA");
static const Certificate root_certificate = generate_root_certificate(root_subject);
return root_certificate;
}
Certificate NaiveCertificateProvider::generate_authorization_ticket()
{
// create certificate
Certificate certificate;
const Certificate& aa_certificate = this->aa_certificate();
// section 6 in TS 103 097 v2.1.1
certificate->issuer.present= Vanetza_Security_IssuerIdentifier_PR_sha256AndDigest;
auto maybe_aa_cert_digest = aa_certificate.calculate_digest();
if (maybe_aa_cert_digest) {
const HashedId8& aa_cert_digest = *maybe_aa_cert_digest;
OCTET_STRING_fromBuf(
&(certificate->issuer.choice.sha256AndDigest),
reinterpret_cast<const char *>(aa_cert_digest.data()),
aa_cert_digest.size()
);
}
// section 6 in TS 103 097 v2.1.1
certificate->toBeSigned.id.present = Vanetza_Security_CertificateId_PR_none;
std::vector<uint8_t> craciId(3, 0);
OCTET_STRING_fromBuf(
&certificate->toBeSigned.cracaId,
reinterpret_cast<const char*>(craciId.data()),
craciId.size()
);
certificate->version = 3;
certificate->toBeSigned.crlSeries = 0;
// section 7.2.1 in TS 103 097 v2.1.1
certificate.add_app_permission(aid::CA, ByteBuffer({ 1, 0, 0 }));
certificate.add_app_permission(aid::DEN, ByteBuffer({ 1, 0xff, 0xff, 0xff}));
certificate.add_app_permission(aid::GN_MGMT, ByteBuffer({})); // required for beacons
certificate.add_app_permission(aid::IPV6_ROUTING, ByteBuffer({})); // required for routing tests
// section 6 in TS 103 097 v2.1.1
// set subject attributes
// set the verification_key
Uncompressed coordinates;
coordinates.x.assign(m_own_key_pair.public_key.x.begin(), m_own_key_pair.public_key.x.end());
coordinates.y.assign(m_own_key_pair.public_key.y.begin(), m_own_key_pair.public_key.y.end());
certificate->toBeSigned.verifyKeyIndicator.present = Vanetza_Security_VerificationKeyIndicator_PR_verificationKey;
certificate->toBeSigned.verifyKeyIndicator.choice.verificationKey.present = Vanetza_Security_PublicVerificationKey_PR_ecdsaNistP256;
certificate->toBeSigned.verifyKeyIndicator.choice.verificationKey.choice.ecdsaNistP256.present = Vanetza_Security_EccP256CurvePoint_PR_uncompressedP256;
OCTET_STRING_fromBuf(
&certificate->toBeSigned.verifyKeyIndicator.choice.verificationKey.choice.ecdsaNistP256.choice.uncompressedP256.x,
reinterpret_cast<const char*>(coordinates.x.data()),
coordinates.x.size()
);
OCTET_STRING_fromBuf(
&certificate->toBeSigned.verifyKeyIndicator.choice.verificationKey.choice.ecdsaNistP256.choice.uncompressedP256.y,
reinterpret_cast<const char*>(coordinates.y.data()),
coordinates.y.size()
);
// section 6 in TS 103 097 v2.1.1
// No constraint
// set validity restriction
certificate->toBeSigned.validityPeriod.start = v2::convert_time32(m_runtime.now() - std::chrono::hours(1));;
certificate->toBeSigned.validityPeriod.duration.present = Vanetza_Security_Duration_PR_hours;
certificate->toBeSigned.validityPeriod.duration.choice.hours = 23;
sign_authorization_ticket(certificate);
return certificate;
}
void NaiveCertificateProvider::sign_authorization_ticket(Certificate& certificate)
{
ByteBuffer data_buffer = certificate.encode();
// TODO build to-be-signed data buffer correctly
certificate.set_signature(m_crypto_backend->sign_data(aa_key_pair().private_key, data_buffer));
}
Certificate NaiveCertificateProvider::generate_aa_certificate(const std::string& name)
{
Certificate aa_certificate;
//section 7.2.4 in TS 103 097 v2.1.1
Certificate root_cert = this->root_certificate();
aa_certificate->issuer.present= Vanetza_Security_IssuerIdentifier_PR_sha256AndDigest;
auto maybe_root_cert_digest = root_cert.calculate_digest();
if (maybe_root_cert_digest) {
const HashedId8& root_cert_digest = *maybe_root_cert_digest;
OCTET_STRING_fromBuf(
&(aa_certificate->issuer.choice.sha256AndDigest),
reinterpret_cast<const char *>(root_cert_digest.data()),
root_cert_digest.size()
);
}
aa_certificate->toBeSigned.id.present = Vanetza_Security_CertificateId_PR_name;
OCTET_STRING_fromBuf(&aa_certificate->toBeSigned.id.choice.name, name.data(), name.size());
// section 6 in TS 103 097 v2.1.1
static const std::array<char, 3> craciId { 0, 0, 0 };
OCTET_STRING_fromBuf(&aa_certificate->toBeSigned.cracaId, craciId.data(), craciId.size());
aa_certificate->version = 3;
aa_certificate->toBeSigned.crlSeries = 0;
// section 7.2.4 in TS 103 097 v2.1.1
// certIssuePermissions shall be used to indicate issuing permissions
// See https://cpoc.jrc.ec.europa.eu/data/documents/e01941_CPOC_Protocol_v3.0_20240206.pdf for detailled cert_permissions
// I.3.8. certIssuePermissions with predefined values
asn1::PsidGroupPermissions* cert_permission_message = asn1::allocate<asn1::PsidGroupPermissions>();
cert_permission_message->subjectPermissions.present = Vanetza_Security_SubjectPermissions_PR_explicit;
add_psid_group_permission(cert_permission_message,aid::CA, {0x01, 0xff, 0xfc}, {0xff, 0x00, 0x03});
add_psid_group_permission(cert_permission_message,aid::DEN, {0x01, 0xff, 0xff, 0xff}, {0xff, 0x00, 0x00, 0x00});
add_psid_group_permission(cert_permission_message,aid::TLM, {0x01, 0xe0}, {0xff, 0x1f});
add_psid_group_permission(cert_permission_message,aid::RLT, {0x01, 0xc0}, {0xff, 0x3f});
add_psid_group_permission(cert_permission_message,aid::IVI, {0x01, 0xff, 0xff, 0xff, 0xff, 0xf8}, {0xff, 0x00, 0x00, 0x00, 0x00, 0x07});
add_psid_group_permission(cert_permission_message,aid::TLC_R, {0x02, 0xff, 0xff, 0xe0}, {0xff, 0x00, 0x00, 0x1f});
add_psid_group_permission(cert_permission_message,aid::GN_MGMT, {0x00}, {0xff});
aa_certificate.add_cert_issue_permission(cert_permission_message);
// section 6 in TS 103 097 v2.1.1
// set subject attributes
// set the verification_key
auto coordinates = compress_public_key(m_own_key_pair.public_key);
aa_certificate->toBeSigned.verifyKeyIndicator.present = Vanetza_Security_VerificationKeyIndicator_PR_verificationKey;
aa_certificate->toBeSigned.verifyKeyIndicator.choice.verificationKey.present = Vanetza_Security_PublicVerificationKey_PR_ecdsaNistP256;
boost::apply_visitor(assign_compressed_ecc_point(&aa_certificate->toBeSigned.verifyKeyIndicator.choice.verificationKey.choice.ecdsaNistP256), coordinates);
aa_certificate->toBeSigned.validityPeriod.start = v2::convert_time32(m_runtime.now() - std::chrono::hours(1));;
aa_certificate->toBeSigned.validityPeriod.duration.present = Vanetza_Security_Duration_PR_years;
aa_certificate->toBeSigned.validityPeriod.duration.choice.hours = 4;
Uncompressed encryption_key;
encryption_key.x.assign(m_own_key_pair.public_key.x.begin(), m_own_key_pair.public_key.x.end());
encryption_key.y.assign(m_own_key_pair.public_key.y.begin(), m_own_key_pair.public_key.y.end());
aa_certificate->toBeSigned.encryptionKey = asn1::allocate<asn1::PublicEncryptionKey>();
aa_certificate->toBeSigned.encryptionKey->publicKey.present = Vanetza_Security_BasePublicEncryptionKey_PR_eciesNistP256;
aa_certificate->toBeSigned.encryptionKey->publicKey.choice.eciesNistP256.present = Vanetza_Security_EccP256CurvePoint_PR_uncompressedP256;
OCTET_STRING_fromBuf(
&aa_certificate->toBeSigned.encryptionKey->publicKey.choice.eciesNistP256.choice.uncompressedP256.x,
reinterpret_cast<const char*>(encryption_key.x.data()),
encryption_key.x.size()
);
OCTET_STRING_fromBuf(
&aa_certificate->toBeSigned.encryptionKey->publicKey.choice.eciesNistP256.choice.uncompressedP256.y,
reinterpret_cast<const char*>(encryption_key.y.data()),
encryption_key.y.size()
);
sign_authorization_ticket(aa_certificate);
return aa_certificate;
}
Certificate NaiveCertificateProvider::generate_root_certificate(const std::string& name)
{
Certificate root_certificate;
//section 7.2.3 in TS 103 097 v2.1.1
root_certificate->issuer.present = Vanetza_Security_IssuerIdentifier_PR_self;
root_certificate->toBeSigned.id.present = Vanetza_Security_CertificateId_PR_name;
OCTET_STRING_fromBuf(&root_certificate->toBeSigned.id.choice.name, name.data(), name.size());
// section 6 in TS 103 097 v2.1.1
static const std::array<char, 3> craciId = { 0, 0, 0 };
OCTET_STRING_fromBuf(&root_certificate->toBeSigned.cracaId, craciId.data(), craciId.size());
root_certificate->version = 3;
root_certificate->toBeSigned.crlSeries = 0;
// section 7.2.3 in TS 103 097 v2.1.1
root_certificate.add_app_permission(aid::CRL, ByteBuffer({0x01}));
root_certificate.add_app_permission(aid::CTL, ByteBuffer({0x018}));
// section 7.2.3 in TS 103 097 v2.1.1
// certIssuePermissions shall be used to indicate issuing permissions
// See https://cpoc.jrc.ec.europa.eu/data/documents/e01941_CPOC_Protocol_v3.0_20240206.pdf for detailled cert_permissions
// I.3.8. certIssuePermissions with predefined values
auto cert_permission = asn1::allocate<asn1::PsidGroupPermissions>();
cert_permission->subjectPermissions.present = Vanetza_Security_SubjectPermissions_PR_explicit;
add_psid_group_permission(cert_permission, aid::SCR, {0x01, 0x3e}, {0xff, 0xc1});
root_certificate.add_cert_issue_permission(cert_permission);
auto cert_permission_message = asn1::allocate<asn1::PsidGroupPermissions>();
cert_permission_message->subjectPermissions.present = Vanetza_Security_SubjectPermissions_PR_explicit;
add_psid_group_permission(cert_permission_message,aid::CA, {0x01, 0xff, 0xfc}, {0xff, 0x00, 0x03});
add_psid_group_permission(cert_permission_message,aid::DEN, {0x01, 0xff, 0xff, 0xff}, {0xff, 0x00, 0x00, 0x00});
add_psid_group_permission(cert_permission_message,aid::TLM, {0x01, 0xe0}, {0xff, 0x1f});
add_psid_group_permission(cert_permission_message,aid::RLT, {0x01, 0xc0}, {0xff,0x3f});
add_psid_group_permission(cert_permission_message,aid::IVI, {0x01, 0xff, 0xff,0xff,0xff,0xf8}, {0xff, 0x00, 0x00, 0x00, 0x00, 0x07});
add_psid_group_permission(cert_permission_message,aid::TLC_R, {0x02, 0xff, 0xff,0xe0}, {0xff, 0x00, 0x00, 0x1f});
add_psid_group_permission(cert_permission_message,aid::GN_MGMT, {0x00}, {0xff});
root_certificate.add_cert_issue_permission(cert_permission_message);
// section 6 in TS 103 097 v2.1.1
// set subject attributes
// set the verification_key
EccPoint coordinates = compress_public_key(m_own_key_pair.public_key);
root_certificate->toBeSigned.verifyKeyIndicator.present = Vanetza_Security_VerificationKeyIndicator_PR_verificationKey;
root_certificate->toBeSigned.verifyKeyIndicator.choice.verificationKey.present = Vanetza_Security_PublicVerificationKey_PR_ecdsaNistP256;
boost::apply_visitor(assign_compressed_ecc_point(&root_certificate->toBeSigned.verifyKeyIndicator.choice.verificationKey.choice.ecdsaNistP256), coordinates);
root_certificate->toBeSigned.validityPeriod.start = v2::convert_time32(m_runtime.now() - std::chrono::hours(1));;
root_certificate->toBeSigned.validityPeriod.duration.present = Vanetza_Security_Duration_PR_years;
root_certificate->toBeSigned.validityPeriod.duration.choice.hours = 4;
sign_authorization_ticket(root_certificate);
return root_certificate;
}
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,102 @@
#pragma once
#include <memory>
#include <string>
#include <vanetza/common/runtime.hpp>
#include <vanetza/security/backend.hpp>
#include <vanetza/security/v2/basic_elements.hpp>
#include <vanetza/security/v3/certificate.hpp>
#include <vanetza/security/v3/certificate_provider.hpp>
namespace vanetza
{
namespace security
{
namespace v3
{
/**
* \brief A very simplistic certificate provider
*
* This certificate provider signs its certificates with a randomly generated root certificate. This means the
* signatures produced based on this certificate provider can't be verified by other parties.
*
* It's intended for experimenting with secured messages without validating signatures.
*/
class NaiveCertificateProvider : public BaseCertificateProvider
{
public:
NaiveCertificateProvider(const Runtime&);
/**
* \brief get own certificate for signing
* \return own certificate
*/
const Certificate& own_certificate() override;
/**
* \brief get own private key
* \return private key
*/
const PrivateKey& own_private_key() override;
/**
* \brief get ticket signer certificate (same for all instances)
* \return signing authorization authority certificate
*/
const Certificate& aa_certificate();
/**
* \brief get root certificate (same for all instances)
* \return signing root certificate
*/
const Certificate& root_certificate();
/**
* \brief generate an authorization ticket
* \return generated certificate
*/
Certificate generate_authorization_ticket();
/**
* \brief sign an authorization ticket
* \param certificate certificate to sign
*/
void sign_authorization_ticket(Certificate& certificate);
private:
/**
* \brief get root key (same for all instances)
* \return root key
*/
const ecdsa256::KeyPair& aa_key_pair();
/**
* \brief get root key (same for all instances)
* \return root key
*/
const ecdsa256::KeyPair& root_key_pair();
/**
* \brief generate a authorization authority certificate
*
* \return generated certificate
*/
Certificate generate_aa_certificate(const std::string& subject_name);
/**
* \brief generate a root certificate
*
* \return generated certificate
*/
Certificate generate_root_certificate(const std::string& subject_name);
std::unique_ptr<Backend> m_crypto_backend;
const Runtime& m_runtime;
const ecdsa256::KeyPair m_own_key_pair;
Certificate m_own_certificate;
};
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,36 @@
#include <vanetza/common/serialization.hpp>
#include <vanetza/security/v3/persistence.hpp>
#include <fstream>
#include <iterator>
namespace vanetza
{
namespace security
{
namespace v3
{
Certificate load_certificate_from_file(const std::string& certificate_path)
{
Certificate certificate;
std::ifstream certificate_src;
certificate_src.open(certificate_path, std::ios::in | std::ios::binary);
vanetza::ByteBuffer buffer(std::istreambuf_iterator<char>(certificate_src), {});
certificate.decode(buffer);
return certificate;
}
void save_certificate_to_file(const std::string& certificate_path, const Certificate& certificate)
{
std::ofstream dest;
dest.open(certificate_path.c_str(), std::ios::out | std::ios::binary);
OutputArchive archive(dest);
serialize(archive, certificate);
}
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,28 @@
#pragma once
#include <vanetza/security/v3/certificate.hpp>
#include <string>
namespace vanetza
{
namespace security
{
namespace v3
{
/**
* \brief Loads a certificate from a file
* \param certificate_path file to load the certificate from
* \return loaded certificate
*/
Certificate load_certificate_from_file(const std::string& certificate_path);
/**
* \brief Saves a certificate to a file
* \param certificate_path file to save the certificate to
* \param certificate certificate to save
*/
void save_certificate_to_file(const std::string& certificate_path, const Certificate& certificate);
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,31 @@
#include <vanetza/security/v3/revocation_lookup.hpp>
namespace vanetza
{
namespace security
{
namespace v3
{
void RevocationMemoryLookup::revoke(const HashedId8& issuer, const HashedId8& cert)
{
m_revoked[issuer].insert(cert);
}
void RevocationMemoryLookup::clear(const HashedId8& issuer)
{
m_revoked.erase(issuer);
}
bool RevocationMemoryLookup::is_revoked(const HashedId8& issuer, const HashedId8& cert) const
{
auto it = m_revoked.find(issuer);
if (it == m_revoked.end()) {
return false;
}
return it->second.count(cert) != 0;
}
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,55 @@
#pragma once
#include <vanetza/security/hashed_id.hpp>
#include <unordered_map>
#include <unordered_set>
namespace vanetza
{
namespace security
{
namespace v3
{
/**
* Lookup for HashedId8-style certificate revocation.
*
* Backs the chain-walk performed by DefaultCertificateValidator: for every
* non-root certificate the validator encounters while walking up the chain,
* it asks whether that certificate has been revoked by a CRL signed by its
* issuer.
*
* Linkage-value CRLs (TS 102 941 ToBeSignedLinkageValueCrl) are out of scope:
* the European C-ITS Certificate Policy does not revoke ATs, and revoking CAs
* only ever needs HashedId8 entries.
*/
class RevocationLookup
{
public:
virtual ~RevocationLookup() = default;
/**
* \param issuer HashedId8 of the CA whose CRL is consulted
* \param cert HashedId8 of the certificate being checked
* \return true iff the CRL signed by \p issuer lists \p cert as revoked
*/
virtual bool is_revoked(const HashedId8& issuer, const HashedId8& cert) const = 0;
};
/**
* In-memory RevocationLookup, indexed by issuer HashedId8.
*/
class RevocationMemoryLookup : public RevocationLookup
{
public:
void revoke(const HashedId8& issuer, const HashedId8& cert);
void clear(const HashedId8& issuer);
bool is_revoked(const HashedId8& issuer, const HashedId8& cert) const override;
private:
std::unordered_map<HashedId8, std::unordered_set<HashedId8>> m_revoked;
};
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,747 @@
#include <vanetza/asn1/asn1c_wrapper.hpp>
#include <vanetza/asn1/security_profile.hpp>
#include VANETZA_ASN1_SECURITY_HEADER(Certificate.h)
#include VANETZA_ASN1_SECURITY_HEADER(EtsiTs103097Data.h)
#include <vanetza/common/byte_buffer.hpp>
#include <vanetza/common/byte_buffer_sink.hpp>
#include <vanetza/net/packet.hpp>
#include <vanetza/security/backend.hpp>
#include <vanetza/security/v3/asn1_conversions.hpp>
#include <vanetza/security/v3/secured_message.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/optional/optional.hpp>
#include <boost/variant/static_visitor.hpp>
// asn1c quirk
struct Vanetza_Security_Certificate : public Vanetza_Security_CertificateBase {};
namespace vanetza
{
namespace security
{
namespace v3
{
namespace
{
const asn1::SignedData* get_signed_data(const asn1::EtsiTs103097Data* data)
{
if (data && data->content && data->content->present == Vanetza_Security_Ieee1609Dot2Content_PR_signedData) {
return data->content->choice.signedData;
} else {
return nullptr;
}
}
const asn1::HeaderInfo* get_header_info(const asn1::EtsiTs103097Data* data)
{
const asn1::SignedData* signed_data = get_signed_data(data);
if (signed_data) {
return &signed_data->tbsData->headerInfo;
} else {
return nullptr;
}
}
HashedId8 make_hashed_id8(const asn1::HashedId8& asn)
{
HashedId8 result;
std::copy_n(asn.buf, std::min(asn.size, result.size()), result.data());
return result;
}
ByteBuffer copy_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 copy_octets(point.choice.compressed_y_0);
break;
case Vanetza_Security_EccP256CurvePoint_PR_compressed_y_1:
return copy_octets(point.choice.compressed_y_1);
break;
case Vanetza_Security_EccP256CurvePoint_PR_x_only:
return copy_octets(point.choice.x_only);
break;
case Vanetza_Security_EccP256CurvePoint_PR_uncompressedP256:
return copy_octets(point.choice.uncompressedP256.x);
break;
default:
return ByteBuffer {};
break;
}
}
ByteBuffer get_x_coordinate(const asn1::EccP384CurvePoint& point)
{
switch (point.present) {
case Vanetza_Security_EccP384CurvePoint_PR_compressed_y_0:
return copy_octets(point.choice.compressed_y_0);
break;
case Vanetza_Security_EccP384CurvePoint_PR_compressed_y_1:
return copy_octets(point.choice.compressed_y_1);
break;
case Vanetza_Security_EccP384CurvePoint_PR_x_only:
return copy_octets(point.choice.x_only);
break;
case Vanetza_Security_EccP384CurvePoint_PR_uncompressedP384:
return copy_octets(point.choice.uncompressedP384.x);
break;
default:
return ByteBuffer {};
break;
}
}
} // namespace
SecuredMessage SecuredMessage::with_signed_data()
{
SecuredMessage secured_message;
secured_message->protocolVersion = 3;
secured_message->content = asn1::allocate<asn1::Ieee1609Dot2Content>();
secured_message->content->present = Vanetza_Security_Ieee1609Dot2Content_PR_signedData;
secured_message->content->choice.signedData = asn1::allocate<asn1::SignedData>();
secured_message->content->choice.signedData->tbsData = asn1::allocate<asn1::ToBeSignedData>();
secured_message->content->choice.signedData->tbsData->payload = asn1::allocate<asn1::SignedDataPayload>();
secured_message->content->choice.signedData->tbsData->payload->data = asn1::allocate<asn1::Ieee1609Dot2Data>();
secured_message->content->choice.signedData->tbsData->payload->data->protocolVersion = 3;
secured_message->content->choice.signedData->tbsData->payload->data->content = asn1::allocate<asn1::Ieee1609Dot2Content>();
secured_message->content->choice.signedData->tbsData->payload->data->content->present = Vanetza_Security_Ieee1609Dot2Content_PR_unsecuredData;
return secured_message;
}
SecuredMessage SecuredMessage::with_signed_data_hash()
{
SecuredMessage secured_message;
secured_message->protocolVersion = 3;
secured_message->content = asn1::allocate<asn1::Ieee1609Dot2Content>();
secured_message->content->present = Vanetza_Security_Ieee1609Dot2Content_PR_signedData;
secured_message->content->choice.signedData = asn1::allocate<asn1::SignedData>();
secured_message->content->choice.signedData->tbsData = asn1::allocate<asn1::ToBeSignedData>();
secured_message->content->choice.signedData->tbsData->payload = asn1::allocate<asn1::SignedDataPayload>();
secured_message->content->choice.signedData->tbsData->payload->extDataHash = asn1::allocate<asn1::HashedData>();
secured_message->content->choice.signedData->tbsData->payload->extDataHash->present = Vanetza_Security_HashedData_PR_sha256HashedData;
return secured_message;
}
SecuredMessage SecuredMessage::with_encrypted_data()
{
SecuredMessage secured_message;
secured_message->protocolVersion = 3;
secured_message->content = asn1::allocate<asn1::Ieee1609Dot2Content>();
secured_message->content->present = Vanetza_Security_Ieee1609Dot2Content_PR_encryptedData;
return secured_message;
}
SecuredMessage::SecuredMessage() :
asn1::asn1c_oer_wrapper<asn1::EtsiTs103097Data>(asn_DEF_Vanetza_Security_EtsiTs103097Data)
{
}
uint8_t SecuredMessage::protocol_version() const
{
return m_struct->protocolVersion;
}
boost::optional<HashedId8> SecuredMessage::certificate_id() const
{
return get_certificate_id(signer_identifier());
}
ItsAid SecuredMessage::its_aid() const
{
ItsAid aid = 0;
const asn1::SignedData* signed_data = get_signed_data(m_struct);
if (signed_data && signed_data->tbsData) {
aid = signed_data->tbsData->headerInfo.psid;
}
return aid;
}
void SecuredMessage::set_its_aid(ItsAid its_aid)
{
if (m_struct->content->present == Vanetza_Security_Ieee1609Dot2Content_PR_signedData) {
asn1::SignedData* signed_data = m_struct->content->choice.signedData;
if (signed_data && signed_data->tbsData) {
signed_data->tbsData->headerInfo.psid = its_aid;
}
}
}
void SecuredMessage::set_generation_time(Time64 time)
{
if (m_struct->content->present == Vanetza_Security_Ieee1609Dot2Content_PR_signedData) {
if (m_struct->content->choice.signedData->tbsData->headerInfo.generationTime == nullptr) {
m_struct->content->choice.signedData->tbsData->headerInfo.generationTime = asn1::allocate<asn1::Time64>();
}
asn_uint642INTEGER(m_struct->content->choice.signedData->tbsData->headerInfo.generationTime, time);
}
}
void SecuredMessage::set_generation_location(const asn1::ThreeDLocation& location)
{
if (m_struct->content->present == Vanetza_Security_Ieee1609Dot2Content_PR_signedData) {
if (m_struct->content->choice.signedData->tbsData->headerInfo.generationLocation == nullptr) {
m_struct->content->choice.signedData->tbsData->headerInfo.generationLocation = asn1::allocate<asn1::ThreeDLocation>();
}
m_struct->content->choice.signedData->tbsData->headerInfo.generationLocation->latitude = location.latitude;
m_struct->content->choice.signedData->tbsData->headerInfo.generationLocation->longitude = location.longitude;
m_struct->content->choice.signedData->tbsData->headerInfo.generationLocation->elevation = location.elevation;
}
}
std::list<HashedId3> SecuredMessage::get_inline_p2pcd_request() const
{
std::list<HashedId3> requests;
if (m_struct->content->present == Vanetza_Security_Ieee1609Dot2Content_PR_signedData) {
const asn1::SequenceOfHashedId3* inline_p2pcd_request = m_struct->content->choice.signedData->tbsData->headerInfo.inlineP2pcdRequest;
if (inline_p2pcd_request) {
for (int i = 0; i < inline_p2pcd_request->list.count; i++) {
// vanetza-idf: the element is a 3-octet HashedId3; widening it through the
// 8-octet conversion and truncating kept the wrong end (zero padding).
requests.push_back(create_hashed_id3(*inline_p2pcd_request->list.array[i]));
}
}
}
return requests;
}
void SecuredMessage::set_inline_p2pcd_request(std::list<HashedId3> requests)
{
if (m_struct->content->present == Vanetza_Security_Ieee1609Dot2Content_PR_signedData) {
assert(m_struct->content->choice.signedData);
assert(m_struct->content->choice.signedData->tbsData);
if (m_struct->content->choice.signedData->tbsData->headerInfo.inlineP2pcdRequest) {
asn1::reset(m_struct->content->choice.signedData->tbsData->headerInfo.inlineP2pcdRequest);
}
for (HashedId3 request : requests) {
this->add_inline_p2pcd_request(request);
}
}
}
void SecuredMessage::add_inline_p2pcd_request(HashedId3 unknown_certificate_digest)
{
if (m_struct->content->present == Vanetza_Security_Ieee1609Dot2Content_PR_signedData) {
if (m_struct->content->choice.signedData->tbsData->headerInfo.inlineP2pcdRequest == nullptr) {
m_struct->content->choice.signedData->tbsData->headerInfo.inlineP2pcdRequest = asn1::allocate<asn1::SequenceOfHashedId3>();
}
Vanetza_Security_HashedId3_t* asn_digest = OCTET_STRING_new_fromBuf(&asn_DEF_Vanetza_Security_HashedId3,
reinterpret_cast<char*>(unknown_certificate_digest.data()), unknown_certificate_digest.size());
ASN_SEQUENCE_ADD(m_struct->content->choice.signedData->tbsData->headerInfo.inlineP2pcdRequest, asn_digest);
}
}
void SecuredMessage::set_dummy_signature()
{
if (m_struct->content->present == Vanetza_Security_Ieee1609Dot2Content_PR_signedData) {
asn1::SignedData* signed_data = m_struct->content->choice.signedData;
if (signed_data) {
// Reset the signature structure
asn1::reset(signed_data->signature);
// Set the signature type to ECDSA NIST P256
signed_data->signature.present = Vanetza_Security_Signature_PR_ecdsaNistP256Signature;
// Initialize rSig part of the signature
signed_data->signature.choice.ecdsaNistP256Signature.rSig.present = Vanetza_Security_EccP256CurvePoint_PR_x_only;
std::vector<uint8_t> dummy_r(32, 0); // Correct length for P256 signature part
dummy_r[0] = 0; // Ensure the leading byte is set to zero if needed
assign(&signed_data->signature.choice.ecdsaNistP256Signature.rSig.choice.x_only, dummy_r);
// Initialize sSig part of the signature
std::vector<uint8_t> dummy_s(32, 0); // Correct length for P256 signature part
assign(&signed_data->signature.choice.ecdsaNistP256Signature.sSig, dummy_s);
}
}
}
void SecuredMessage::set_signature(const Signature& signature)
{
if (m_struct->content->present == Vanetza_Security_Ieee1609Dot2Content_PR_signedData) {
asn1::SignedData* signed_data = m_struct->content->choice.signedData;
if (signed_data) {
// Reset the signature structure
asn1::reset(signed_data->signature);
// Set the signature type to ECDSA NIST P256
switch (signature.type)
{
case vanetza::security::KeyType::NistP256:
signed_data->signature.present = Vanetza_Security_Signature_PR_ecdsaNistP256Signature;
// Initialize rSig and sSig part of the signature
// Check the type (x_only, y-1, y-0 or uncompressed ??????)
signed_data->signature.choice.ecdsaNistP256Signature.rSig.present = Vanetza_Security_EccP256CurvePoint_PR_x_only;
assign(&signed_data->signature.choice.ecdsaNistP256Signature.rSig.choice.x_only, signature.r);
assign(&signed_data->signature.choice.ecdsaNistP256Signature.sSig, signature.s);
break;
case vanetza::security::KeyType::BrainpoolP256r1 :
signed_data->signature.present = Vanetza_Security_Signature_PR_ecdsaBrainpoolP256r1Signature;
// Check the type (x_only, y-1, y-0 or uncompressed ??????)
signed_data->signature.choice.ecdsaBrainpoolP256r1Signature.rSig.present = Vanetza_Security_EccP256CurvePoint_PR_x_only;
assign(&signed_data->signature.choice.ecdsaBrainpoolP256r1Signature.rSig.choice.x_only, signature.r);
assign(&signed_data->signature.choice.ecdsaBrainpoolP256r1Signature.sSig, signature.s);
break;
case vanetza::security::KeyType::BrainpoolP384r1 :
signed_data->signature.present = Vanetza_Security_Signature_PR_ecdsaBrainpoolP384r1Signature;
// Check the type (x_only, y-1, y-0 or uncompressed ??????)
signed_data->signature.choice.ecdsaBrainpoolP384r1Signature.rSig.present = Vanetza_Security_EccP384CurvePoint_PR_x_only;
assign(&signed_data->signature.choice.ecdsaBrainpoolP384r1Signature.rSig.choice.x_only, signature.r);
assign(&signed_data->signature.choice.ecdsaBrainpoolP384r1Signature.sSig, signature.s);
break;
default:
this->set_dummy_signature();
break;
}
}
}
}
void SecuredMessage::set_signature(const SomeEcdsaSignature& signature)
{
struct signature_visitor : public boost::static_visitor<asn1::Signature>
{
asn1::Signature operator()(const EcdsaSignature& signature) const
{
asn1::Signature final_signature = {};
final_signature.present = Vanetza_Security_Signature_PR_ecdsaNistP256Signature;
assign(&final_signature.choice.ecdsaNistP256Signature.sSig, signature.s);
final_signature.choice.ecdsaNistP256Signature.rSig = to_asn1(signature.R);
return final_signature;
}
asn1::Signature operator()(const EcdsaSignatureFuture& signature) const
{
return this->operator()(signature.get());
}
};
asn1::reset(m_struct->content->choice.signedData->signature);
m_struct->content->choice.signedData->signature = boost::apply_visitor(signature_visitor(), signature);
}
PacketVariant SecuredMessage::payload() const
{
ByteBuffer buffer;
switch (m_struct->content->present) {
case Vanetza_Security_Ieee1609Dot2Content_PR_unsecuredData:
buffer = get_payload(&m_struct->content->choice.unsecuredData);
break;
case Vanetza_Security_Ieee1609Dot2Content_PR_signedData:
buffer = get_payload(m_struct->content->choice.signedData);
break;
default:
// empty buffer as fallback
break;
}
return CohesivePacket { std::move(buffer), OsiLayer::Network };
}
void SecuredMessage::set_payload(const ByteBuffer& payload)
{
switch (m_struct->content->present) {
case Vanetza_Security_Ieee1609Dot2Content_PR_unsecuredData:
vanetza::security::v3::set_payload(&m_struct->content->choice.unsecuredData, payload);
break;
case Vanetza_Security_Ieee1609Dot2Content_PR_signedData:
vanetza::security::v3::set_payload(&m_struct->content->choice.signedData->tbsData->payload->data->content->choice.unsecuredData, payload);
break;
default:
// cannot copy payload into secured message
break;
}
}
void SecuredMessage::set_external_payload_hash(const Sha256Digest& hash)
{
assert(m_struct->content->present == Vanetza_Security_Ieee1609Dot2Content_PR_signedData);
asn1::HashedData* hashed_data = m_struct->content->choice.signedData->tbsData->payload->extDataHash;
asn1::reset(hashed_data);
hashed_data->present = Vanetza_Security_HashedData_PR_sha256HashedData;
OCTET_STRING_fromBuf(&hashed_data->choice.sha256HashedData, reinterpret_cast<const char*>(hash.data()), hash.size());
}
HashAlgorithm SecuredMessage::hash_id() const
{
HashAlgorithm algo = HashAlgorithm::Unspecified;
const asn1::SignedData* signed_data = get_signed_data(m_struct);
if (signed_data) {
switch (signed_data->hashId) {
case Vanetza_Security_HashAlgorithm_sha256:
algo = HashAlgorithm::SHA256;
break;
case Vanetza_Security_HashAlgorithm_sha384:
algo = HashAlgorithm::SHA384;
break;
default:
break;
}
}
return algo;
}
void SecuredMessage::set_hash_id(HashAlgorithm hash)
{
assert(m_struct->content->present == Vanetza_Security_Ieee1609Dot2Content_PR_signedData);
switch (hash) {
case HashAlgorithm::SHA256:
m_struct->content->choice.signedData->hashId = Vanetza_Security_HashAlgorithm_sha256;
break;
case HashAlgorithm::SHA384:
m_struct->content->choice.signedData->hashId = Vanetza_Security_HashAlgorithm_sha384;
break;
default:
m_struct->content->choice.signedData->hashId = -1;
}
}
void SecuredMessage::set_signer_identifier_self()
{
assert(m_struct->content->present == Vanetza_Security_Ieee1609Dot2Content_PR_signedData);
asn1::SignerIdentifier* signer = &m_struct->content->choice.signedData->signer;
asn1::reset(signer);
signer->present = Vanetza_Security_SignerIdentifier_PR_self;
}
void SecuredMessage::set_signer_identifier(const HashedId8& digest)
{
assert(m_struct->content->present == Vanetza_Security_Ieee1609Dot2Content_PR_signedData);
asn1::SignerIdentifier* signer = &m_struct->content->choice.signedData->signer;
asn1::reset(signer);
signer->present = Vanetza_Security_SignerIdentifier_PR_digest;
OCTET_STRING_fromBuf(&signer->choice.digest, reinterpret_cast<const char*>(digest.data()), digest.size());
}
void SecuredMessage::set_signer_identifier(const Certificate& cert)
{
assert(m_struct->content->present == Vanetza_Security_Ieee1609Dot2Content_PR_signedData);
asn1::SignerIdentifier* signer = &m_struct->content->choice.signedData->signer;
asn1::reset(signer);
signer->present = Vanetza_Security_SignerIdentifier_PR_certificate;
ASN_SEQUENCE_ADD(&signer->choice.certificate, asn1::copy(asn_DEF_Vanetza_Security_EtsiTs103097Certificate, cert.content()));
}
void SecuredMessage::get_aes_ccm_ciphertext(ByteBuffer &ccm_ciphertext, std::array<uint8_t, 12> &nonce) const
{
assert(m_struct->content->present == Vanetza_Security_Ieee1609Dot2Content_PR_encryptedData);
const asn1::SymmetricCiphertext &symmetric_ciphertext = m_struct->content->choice.encryptedData.ciphertext;
assert(symmetric_ciphertext.present == Vanetza_Security_SymmetricCiphertext_PR_aes128ccm);
const asn1::AesCcmCiphertext &aes_ccm_ciphertext = symmetric_ciphertext.choice.aes128ccm;
ccm_ciphertext = copy_octets(aes_ccm_ciphertext.ccmCiphertext);
std::memcpy(nonce.data(), aes_ccm_ciphertext.nonce.buf, nonce.size());
}
void SecuredMessage::set_aes_ccm_ciphertext(const ByteBuffer &ccm_ciphertext, const std::array<uint8_t, 12> &nonce)
{
assert(m_struct->content->present == Vanetza_Security_Ieee1609Dot2Content_PR_encryptedData);
asn1::SymmetricCiphertext &symmetric_ciphertext = m_struct->content->choice.encryptedData.ciphertext;
CHOICE_variant_set_presence(&asn_DEF_Vanetza_Security_SymmetricCiphertext, &symmetric_ciphertext, Vanetza_Security_SymmetricCiphertext_PR_aes128ccm);
asn1::AesCcmCiphertext &aes_ccm_ciphertext = symmetric_ciphertext.choice.aes128ccm;
OCTET_STRING_fromBuf(&aes_ccm_ciphertext.ccmCiphertext, reinterpret_cast<const char *>(ccm_ciphertext.data()), ccm_ciphertext.size());
OCTET_STRING_fromBuf(&aes_ccm_ciphertext.nonce, reinterpret_cast<const char *>(nonce.data()), nonce.size());
}
void SecuredMessage::set_cert_recip_info(const HashedId8& recipient_id,
const std::array<uint8_t, 16>& ecies_ciphertext,
const std::array<uint8_t, 16>& ecies_tag,
const PublicKey& ecies_pub_key)
{
assert(m_struct->content->present == Vanetza_Security_Ieee1609Dot2Content_PR_encryptedData);
// own the RecipientInfo until it is handed to the message (exception safety)
auto cert_recip_info = asn1::make_unique<asn1::RecipientInfo>();
CHOICE_variant_set_presence(&asn_DEF_Vanetza_Security_RecipientInfo, cert_recip_info.get(), Vanetza_Security_RecipientInfo_PR_certRecipInfo);
asn1::PKRecipientInfo &pk_recip_info = cert_recip_info->choice.certRecipInfo;
// Set recipient certificate digest
OCTET_STRING_fromBuf(&pk_recip_info.recipientId, reinterpret_cast<const char *>(recipient_id.data()), recipient_id.size());
asn1::EncryptedDataEncryptionKey &enc_data_enc_key = pk_recip_info.encKey;
asn1::EciesP256EncryptedKey *ecies_enc_key = nullptr;
if (ecies_pub_key.type == KeyType::NistP256) {
enc_data_enc_key.present = Vanetza_Security_EncryptedDataEncryptionKey_PR_eciesNistP256;
ecies_enc_key = &enc_data_enc_key.choice.eciesNistP256;
} else if (ecies_pub_key.type == KeyType::BrainpoolP256r1) {
enc_data_enc_key.present = Vanetza_Security_EncryptedDataEncryptionKey_PR_eciesBrainpoolP256r1;
ecies_enc_key = &enc_data_enc_key.choice.eciesBrainpoolP256r1;
} else {
throw std::invalid_argument("Unsupported EC curve");
}
// Set ECIES ciphertext and tag
OCTET_STRING_fromBuf(&ecies_enc_key->c, reinterpret_cast<const char *>(ecies_ciphertext.data()), ecies_ciphertext.size());
OCTET_STRING_fromBuf(&ecies_enc_key->t, reinterpret_cast<const char *>(ecies_tag.data()), ecies_tag.size());
// Set ECIES ephemeral public key
ecies_enc_key->v = to_asn1(make_ecc_point(ecies_pub_key));
ASN_SEQUENCE_ADD(&m_struct->content->choice.encryptedData.recipients.list, cert_recip_info.release());
}
bool SecuredMessage::check_psk_match(const std::array<uint8_t, 16>& psk) const
{
assert(m_struct->content->present == Vanetza_Security_Ieee1609Dot2Content_PR_encryptedData);
// Wrap the given PSK into a PSKRecipientInfo_t to calculate the HashedId8
asn1::asn1c_oer_wrapper<asn1::SymmetricEncryptionKey> psk_key(asn_DEF_Vanetza_Security_SymmetricEncryptionKey);
asn1::SymmetricEncryptionKey *psk_key_ptr = &(*psk_key);
CHOICE_variant_set_presence(
&asn_DEF_Vanetza_Security_SymmetricEncryptionKey,
psk_key_ptr,
Vanetza_Security_SymmetricEncryptionKey_PR_aes128Ccm);
OCTET_STRING_fromBuf(&psk_key_ptr->choice.aes128Ccm, reinterpret_cast<const char *>(psk.data()), psk.size());
ByteBuffer bytes = psk_key.encode();
Sha256Digest psk_digest = calculate_sha256_digest(bytes.data(), bytes.size());
HashedId8 psk_id = create_hashed_id8(psk_digest);
// Check every RecipientInfo for a PSKRecipientInfo with matching PSK
const auto &recipient_list = m_struct->content->choice.encryptedData.recipients.list;
for (int i = 0; i < recipient_list.count; i++) {
const asn1::RecipientInfo &recipient_info = *recipient_list.array[i];
if (recipient_info.present != Vanetza_Security_RecipientInfo_PR_pskRecipInfo) {
continue;
}
HashedId8 message_psk_id;
std::memcpy(message_psk_id.data(), recipient_info.choice.pskRecipInfo.buf, message_psk_id.size());
if (psk_id == message_psk_id) {
return true;
}
}
return false;
}
bool SecuredMessage::is_signed() const
{
return m_struct->content->present == Vanetza_Security_Ieee1609Dot2Content_PR_signedData;
}
bool SecuredMessage::is_encrypted() const
{
return m_struct->content->present == Vanetza_Security_Ieee1609Dot2Content_PR_encryptedData;
}
boost::optional<SecuredMessage::Time64> SecuredMessage::generation_time() const
{
boost::optional<Time64> gen_time;
auto header_info = get_header_info(m_struct);
if (header_info) {
std::uintmax_t tmp;
if (asn_INTEGER2umax(header_info->generationTime, &tmp) == 0) {
gen_time = tmp;
}
}
return gen_time;
}
boost::optional<Signature> SecuredMessage::signature() const
{
const asn1::SignedData* signed_data = get_signed_data(m_struct);
if (signed_data) {
const asn1::Signature& asn = signed_data->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 = copy_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 = copy_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 = copy_octets(asn.choice.ecdsaBrainpoolP384r1Signature.sSig);
break;
default:
return boost::none;
}
return sig;
}
return boost::none;
}
SecuredMessage::SignerIdentifier SecuredMessage::signer_identifier() const
{
const asn1::SignedData* signed_data = get_signed_data(m_struct);
if (signed_data) {
if (signed_data->signer.present == Vanetza_Security_SignerIdentifier_PR_digest) {
const asn1::HashedId8* digest = &signed_data->signer.choice.digest;
return digest;
} else if (signed_data->signer.present == Vanetza_Security_SignerIdentifier_PR_certificate) {
const asn1::SequenceOfCertificate& certificates = signed_data->signer.choice.certificate;
// TS 103 097 v1.3.1 contraints this to exactly one certificate in clause 5.2
if (certificates.list.count == 1) {
const asn1::Certificate* cert = certificates.list.array[0];
return cert;
}
}
}
return static_cast<asn1::HashedId8*>(nullptr);
}
ByteBuffer SecuredMessage::signing_payload() const
{
const asn1::SignedData* signed_data = get_signed_data(m_struct);
if (signed_data) {
return asn1::encode_oer(asn_DEF_Vanetza_Security_ToBeSignedData, signed_data->tbsData);
} else {
return ByteBuffer {};
}
}
void SecuredMessage::set_requested_certificate(const Certificate& cert)
{
const asn1::SignedData* signed_data = get_signed_data(m_struct);
if (signed_data && signed_data->tbsData) {
if (signed_data->tbsData->headerInfo.requestedCertificate) {
ASN_STRUCT_FREE(asn_DEF_Vanetza_Security_Certificate, signed_data->tbsData->headerInfo.requestedCertificate);
}
signed_data->tbsData->headerInfo.requestedCertificate =
static_cast<Vanetza_Security_Certificate*>(asn1::copy(asn_DEF_Vanetza_Security_Certificate, cert.content()));
}
}
size_t get_size(const SecuredMessage& message)
{
return message.size();
}
void serialize(OutputArchive& ar, const SecuredMessage& msg)
{
ByteBuffer buffer = msg.encode();
ar.save_binary(buffer.data(), buffer.size());
}
size_t deserialize(InputArchive& ar, SecuredMessage& msg)
{
std::size_t len = ar.remaining_bytes();
// TODO optimize decoding step without buffer allocation
ByteBuffer buffer;
buffer.resize(len);
ar.load_binary(buffer.data(), len);
return msg.decode(buffer) ? len : 0;
}
ByteBuffer get_payload(const asn1::Opaque* unsecured)
{
ByteBuffer buffer;
buffer.reserve(unsecured->size);
std::copy_n(unsecured->buf, unsecured->size, std::back_inserter(buffer));
return buffer;
}
ByteBuffer convert_to_payload(vanetza::DownPacket packet)
{
ByteBuffer buf;
byte_buffer_sink sink(buf);
boost::iostreams::stream_buffer<byte_buffer_sink> stream(sink);
OutputArchive ar(stream);
serialize(ar, packet);
stream.close();
return buf;
}
void set_payload(asn1::Opaque* unsecured, const ByteBuffer& buffer)
{
OCTET_STRING_fromBuf(unsecured, reinterpret_cast<const char*>(buffer.data()), buffer.size());
}
ByteBuffer get_payload(const asn1::SignedData* signed_data)
{
ByteBuffer buffer;
if (signed_data->tbsData && signed_data->tbsData->payload) {
const asn1::SignedDataPayload* signed_payload = signed_data->tbsData->payload;
if (signed_payload->data && signed_payload->data->content) {
const asn1::Ieee1609Dot2Content* content = signed_payload->data->content;
if (content->present == Vanetza_Security_Ieee1609Dot2Content_PR_unsecuredData) {
buffer = get_payload(&content->choice.unsecuredData);
}
}
}
return buffer;
}
boost::optional<HashedId8> get_certificate_id(const SecuredMessage::SignerIdentifier& identifier)
{
using result_type = boost::optional<HashedId8>;
struct cert_id_visitor : public boost::static_visitor<result_type> {
result_type operator()(const asn1::HashedId8* digest) const
{
return digest ? make_hashed_id8(*digest) : result_type { };
}
result_type operator()(const asn1::Certificate* cert) const
{
return cert ? calculate_digest(*cert) : result_type { };
}
};
return boost::apply_visitor(cert_id_visitor(), identifier);
}
bool contains_certificate(const SecuredMessage::SignerIdentifier& identifier)
{
struct visitor : public boost::static_visitor<bool> {
bool operator()(const asn1::HashedId8*) const
{
return false;
}
bool operator()(const asn1::Certificate*) const
{
return true;
}
};
return boost::apply_visitor(visitor(), identifier);
}
const asn1::Certificate* get_certificate(const SecuredMessage::SignerIdentifier& identifier)
{
struct visitor : public boost::static_visitor<const asn1::Certificate*> {
const asn1::Certificate* operator()(const asn1::HashedId8*) const
{
return nullptr;
}
const asn1::Certificate* operator()(const asn1::Certificate* cert) const
{
return cert;
}
};
return boost::apply_visitor(visitor(), identifier);
}
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,126 @@
#ifndef SECURED_MESSAGE_HPP_DCBC74AC
#define SECURED_MESSAGE_HPP_DCBC74AC
#include <vanetza/asn1/asn1c_wrapper.hpp>
#include <vanetza/asn1/security_profile.hpp>
#include VANETZA_ASN1_SECURITY_HEADER(EtsiTs103097Data.h)
#include <vanetza/common/archives.hpp>
#include <vanetza/common/its_aid.hpp>
#include <vanetza/net/packet_variant.hpp>
#include <vanetza/security/hash_algorithm.hpp>
#include <vanetza/security/hashed_id.hpp>
#include <vanetza/security/public_key.hpp>
#include <vanetza/security/signature.hpp>
#include <vanetza/security/v3/asn1_types.hpp>
#include <vanetza/security/v3/certificate.hpp>
#include <boost/optional/optional_fwd.hpp>
#include <boost/variant/variant_fwd.hpp>
#include <array>
#include <cstdint>
#include <list>
namespace vanetza
{
namespace security
{
namespace v3
{
struct SecuredMessage : public asn1::asn1c_oer_wrapper<asn1::EtsiTs103097Data>
{
using Time64 = std::uint64_t;
using SignerIdentifier = boost::variant<const asn1::HashedId8*, const asn1::Certificate*>;
SecuredMessage();
static SecuredMessage with_signed_data();
static SecuredMessage with_signed_data_hash();
static SecuredMessage with_encrypted_data();
uint8_t protocol_version() const;
ItsAid its_aid() const;
PacketVariant payload() const;
boost::optional<HashedId8> certificate_id() const;
bool is_signed() const;
bool is_encrypted() const;
boost::optional<Time64> generation_time() const;
boost::optional<Signature> signature() const;
SignerIdentifier signer_identifier() const;
ByteBuffer signing_payload() const;
HashAlgorithm hash_id() const;
void set_its_aid(ItsAid its_aid);
void set_generation_time(Time64 time);
void set_generation_location(const asn1::ThreeDLocation& location);
void set_payload(const ByteBuffer& payload);
void set_external_payload_hash(const Sha256Digest& hash);
void set_hash_id(HashAlgorithm);
void set_signature(const Signature& signature);
std::list<HashedId3> get_inline_p2pcd_request() const;
void set_inline_p2pcd_request(std::list<HashedId3> requests);
void add_inline_p2pcd_request(HashedId3 unkown_certificate_digest);
void set_signature(const SomeEcdsaSignature& signature);
void set_dummy_signature();
void set_signer_identifier_self();
void set_signer_identifier(const HashedId8&);
void set_signer_identifier(const Certificate&);
void set_requested_certificate(const Certificate&);
void get_aes_ccm_ciphertext(ByteBuffer& ccm_ciphertext, std::array<uint8_t, 12>& nonce) const;
void set_aes_ccm_ciphertext(const ByteBuffer& ccm_ciphertext, const std::array<uint8_t, 12>& nonce);
void set_cert_recip_info(const HashedId8& recipient_id,
const std::array<uint8_t, 16>& ecies_ciphertext,
const std::array<uint8_t, 16>& ecies_tag,
const PublicKey& ecies_pub_key);
bool check_psk_match(const std::array<uint8_t, 16>& psk) const;
};
/**
* \brief Calculate size of encoded secured message
* \param msg secured message
* \return number of octets needed to serialize this message
*/
size_t get_size(const SecuredMessage& msg);
/**
* \brief Serialize a secured message
*
* @param ar output archive
* @param msg message to be serialized
*/
void serialize(OutputArchive& ar, const SecuredMessage& msg);
/**
* \brief Deserialize a secured message
*
* \param ar input archive
* \param msg destination message object
* \return size of deserialized message
*/
size_t deserialize(InputArchive& ar, SecuredMessage& msg);
ByteBuffer get_payload(const asn1::Opaque*);
ByteBuffer get_payload(const asn1::SignedData*);
void set_payload(asn1::Opaque* unsecured, const ByteBuffer& buffer);
ByteBuffer convert_to_payload(vanetza::ChunkPacket packet);
boost::optional<HashedId8> get_certificate_id(const SecuredMessage::SignerIdentifier&);
/**
* Check if signer identifier contains a full certificate
* \param signer_identifier to check
* \param true if signer identifier contains a full certificate
*/
bool contains_certificate(const SecuredMessage::SignerIdentifier& signer_identifier);
/**
* Fetch certificate from identifier if full certificate is included.
* \return certificate or nullptr
*/
const asn1::Certificate* get_certificate(const SecuredMessage::SignerIdentifier&);
} // namespace v3
} // namespace security
} // namespace vanetza
#endif /* SECURED_MESSAGE_HPP_DCBC74AC */
@@ -0,0 +1,136 @@
#include <vanetza/common/its_aid.hpp>
#include <vanetza/common/position_provider.hpp>
#include <vanetza/security/sign_service.hpp>
#include <vanetza/security/v3/certificate.hpp>
#include <vanetza/security/v3/certificate_provider.hpp>
#include <vanetza/security/v3/sign_header_policy.hpp>
#include <cmath>
namespace vanetza
{
namespace security
{
namespace v3
{
asn1::ThreeDLocation build_location(const PositionFix& fix)
{
asn1::ThreeDLocation location;
static constexpr long latitude_scale = Vanetza_Security_NinetyDegreeInt_max;
static constexpr long longitude_scale = Vanetza_Security_OneEightyDegreeInt_max;
long lat = std::round((fix.latitude / units::degree / 90.0) * latitude_scale);
if (lat >= Vanetza_Security_NinetyDegreeInt_min && lat <= Vanetza_Security_NinetyDegreeInt_max) {
location.latitude = lat;
} else {
location.latitude = Vanetza_Security_NinetyDegreeInt_unknown;
}
long lon = std::round((fix.longitude / units::degree / 180.0) * longitude_scale);
if (lon >= Vanetza_Security_OneEightyDegreeInt_min && lon <= Vanetza_Security_OneEightyDegreeInt_max) {
location.longitude = lon;
} else {
location.longitude = Vanetza_Security_OneEightyDegreeInt_unknown;
}
location.elevation = 4096; // no "not available" value specified, set 0m as fallback
if (fix.altitude) {
long elev_dm = std::round(fix.altitude->value() / units::si::meter * 10.0);
if (elev_dm >= -4095 && elev_dm <= 61439) {
location.elevation = elev_dm + 4096;
}
}
return location;
}
DefaultSignHeaderPolicy::DefaultSignHeaderPolicy(const Runtime& rt, PositionProvider& positioning, CertificateProvider& certs) :
m_runtime(rt), m_positioning(positioning), m_cert_provider(certs),
m_cam_next_certificate(m_runtime.now()),
m_cert_requested(false)
{
}
void DefaultSignHeaderPolicy::prepare_header(const SignRequest& request, SecuredMessage& secured_message)
{
const auto now = m_runtime.now();
secured_message.set_its_aid(request.its_aid);
secured_message.set_generation_time(vanetza::security::v2::convert_time64(now));
if (request.its_aid == aid::CA) {
bool signer_full_cert = false;
const auto& at_cert = m_cert_provider.own_certificate();
const auto maybe_at_digest = at_cert.calculate_digest();
// include full certificate if its digest has been requested by a peer
if (maybe_at_digest && m_incoming_requests.is_pending(truncate(*maybe_at_digest))) {
m_cert_requested = true;
m_incoming_requests.discard_request(truncate(*maybe_at_digest));
}
// section 7.1.1 in TS 103 097 v2.1.1
if (now < m_cam_next_certificate && !m_cert_requested) {
if (maybe_at_digest) {
secured_message.set_signer_identifier(*maybe_at_digest);
}
} else {
signer_full_cert = true;
m_cert_requested = false;
secured_message.set_signer_identifier(at_cert);
m_cam_next_certificate = now + std::chrono::seconds(1) - std::chrono::milliseconds(50);
}
// peer-to-peer certificate distribution
secured_message.set_inline_p2pcd_request(m_outgoing_requests.all());
if (!signer_full_cert) {
while (auto p2p_hid = m_incoming_requests.next_one()) {
// provide requested CA certificates (no AT certificates here)
auto p2p_cert = m_cert_provider.cache().lookup(*p2p_hid);
if (p2p_cert && p2p_cert->is_ca_certificate()) {
secured_message.set_requested_certificate(*p2p_cert);
break;
}
}
}
} else if (request.its_aid == aid::DEN) {
// section 7.1.2 in TS 103 097 v2.1.1
secured_message.set_signer_identifier(m_cert_provider.own_certificate());
secured_message.set_generation_location(build_location(m_positioning.position_fix()));
} else if (request.its_aid == aid::SCR) {
// section 6.2.3.2 and 6.2.3.3 in TS 102 941 v2.1.1
// some structures in the SCR are self-signed
if (request.self_signed)
secured_message.set_signer_identifier_self();
else {
const auto digest = m_cert_provider.own_certificate().calculate_digest();
if (digest)
secured_message.set_signer_identifier(*digest);
}
} else {
secured_message.set_signer_identifier(m_cert_provider.own_certificate());
}
}
void DefaultSignHeaderPolicy::request_unrecognized_certificate(HashedId8 id)
{
m_outgoing_requests.add_request(truncate(id));
}
void DefaultSignHeaderPolicy::request_certificate()
{
m_cert_requested = true;
}
void DefaultSignHeaderPolicy::enqueue_p2p_request(HashedId3 id)
{
m_incoming_requests.add_request(id);
}
void DefaultSignHeaderPolicy::discard_p2p_request(HashedId3 id)
{
m_incoming_requests.discard_request(id);
}
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,96 @@
#pragma once
#include <vanetza/common/clock.hpp>
#include <vanetza/common/runtime.hpp>
#include <vanetza/security/hashed_id.hpp>
#include <vanetza/security/peer_request_tracker.hpp>
#include <boost/optional/optional.hpp>
namespace vanetza
{
// forward declaration
class PositionProvider;
namespace security
{
// forward declarations
class CertificateProvider;
struct SignRequest;
namespace v3
{
/**
* SignHeaderPolicy is used while signing messages
*
* SignHeaderPolicy determines the header fields to be included in the secured message.
* Other components can influence the policy's behaviour by calling one of its "report" methods.
*/
class SignHeaderPolicy
{
public:
/**
* Prepare header fields for next secured message.
*
* \param req signing request (including ITS-AID for example)
* \param secured_message output message
* \return header fields
*/
virtual void prepare_header(const SignRequest& req, SecuredMessage& secured_message) = 0;
/**
* Mark certificate as unrecognized in next secured message
* \param id hash of unknown certificate
*/
virtual void request_unrecognized_certificate(HashedId8 id) = 0;
/**
* Request a full certificate to be included in next secured message
*/
virtual void request_certificate() = 0;
/**
* Enqueue a certificate for P2P distribution.
* \param id hash of requested certificate
*/
virtual void enqueue_p2p_request(HashedId3 id) = 0;
/**
* Discard a P2P certificate request.
* \param id hash of requested certificate
*/
virtual void discard_p2p_request(HashedId3 id) = 0;
virtual ~SignHeaderPolicy() = default;
};
/**
* DefaultSignHeaderPolicy implements the default behaviour specified by ETSI TS 103 097 V2.1.1
*/
class DefaultSignHeaderPolicy : public SignHeaderPolicy
{
public:
DefaultSignHeaderPolicy(const Runtime&, PositionProvider& positioning, CertificateProvider&);
void prepare_header(const SignRequest& request, SecuredMessage& secured_message) override;
void request_unrecognized_certificate(HashedId8 id) override;
void request_certificate() override;
void enqueue_p2p_request(HashedId3) override;
void discard_p2p_request(HashedId3) override;
private:
const Runtime& m_runtime;
PositionProvider& m_positioning;
CertificateProvider& m_cert_provider;
Clock::time_point m_cam_next_certificate;
bool m_cert_requested;
PeerRequestTracker m_incoming_requests;
PeerRequestTracker m_outgoing_requests;
};
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,62 @@
#include <vanetza/common/its_aid.hpp>
#include <vanetza/common/runtime.hpp>
#include <vanetza/security/v2/basic_elements.hpp>
#include <vanetza/security/backend.hpp>
#include <vanetza/security/v3/hash.hpp>
#include <vanetza/security/v3/sign_service.hpp>
#include <vanetza/security/v3/secured_message.hpp>
namespace vanetza
{
namespace security
{
namespace v3
{
StraightSignService::StraightSignService(CertificateProvider& provider, Backend& backend, SignHeaderPolicy& policy, CertificateValidator& validator) :
m_certificates(provider), m_backend(backend), m_policy(policy), m_validator(validator)
{
}
SignConfirm StraightSignService::sign(SignRequest&& request)
{
const auto& signing_cert = m_certificates.own_certificate();
const auto hash_algo = specified_hash_algorithm(signing_cert.get_verification_key_type());
SecuredMessage secured_message = SecuredMessage::with_signed_data();
secured_message.set_hash_id(hash_algo);
secured_message.set_payload(convert_to_payload(request.plain_message));
m_policy.prepare_header(request, secured_message);
if (m_validator.valid_for_signing(signing_cert, request.its_aid) != CertificateValidator::Verdict::Valid) {
return SignConfirm::failure(SignConfirmError::No_Certificate);
}
ByteBuffer digest = calculate_message_hash(m_backend, hash_algo, secured_message.signing_payload(), signing_cert);
Signature signature = m_backend.sign_digest(m_certificates.own_private_key(), digest);
secured_message.set_signature(signature);
return SignConfirm::success(std::move(secured_message));
}
DummySignService::DummySignService(const Runtime& runtime) :
m_runtime(runtime)
{
}
SignConfirm DummySignService::sign(SignRequest&& request)
{
SecuredMessage secured_message = SecuredMessage::with_signed_data();
ByteBuffer payload;
payload = convert_to_payload(request.plain_message);
secured_message.set_payload(payload);
secured_message.set_dummy_signature();
secured_message.set_its_aid(request.its_aid);
secured_message.set_generation_time(vanetza::security::v2::convert_time64(m_runtime.now()));
secured_message->content->choice.signedData->signer.present = Vanetza_Security_SignerIdentifier_PR_self;
return SignConfirm::success(std::move(secured_message));
}
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,53 @@
#pragma once
#include <vanetza/security/sign_service.hpp>
#include <vanetza/security/v3/certificate_provider.hpp>
#include <vanetza/security/v3/certificate_validator.hpp>
#include <vanetza/security/v3/sign_header_policy.hpp>
namespace vanetza
{
namespace security
{
// forward declarations
class Backend;
namespace v3
{
/**
* SignService immediately signing the message using given
*/
class StraightSignService : public SignService
{
public:
StraightSignService(CertificateProvider&, Backend&, SignHeaderPolicy&, CertificateValidator&);
SignConfirm sign(SignRequest&&) override;
private:
CertificateProvider & m_certificates;
Backend& m_backend;
SignHeaderPolicy& m_policy;
CertificateValidator& m_validator;
};
/**
* SignService without real cryptography but dummy signature
*/
class DummySignService : public SignService
{
public:
/**
* \param rt runtime for appropriate generation time
*/
DummySignService(const Runtime& rt);
SignConfirm sign(SignRequest&&) override;
private:
const Runtime& m_runtime;
};
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,29 @@
#include <vanetza/security/v3/static_certificate_provider.hpp>
namespace vanetza
{
namespace security
{
namespace v3
{
StaticCertificateProvider::StaticCertificateProvider(const Certificate& authorization_ticket,
const PrivateKey& authorization_ticket_key) :
authorization_ticket(authorization_ticket),
authorization_ticket_key(authorization_ticket_key)
{
}
const PrivateKey& StaticCertificateProvider::own_private_key()
{
return authorization_ticket_key;
}
const Certificate& StaticCertificateProvider::own_certificate()
{
return authorization_ticket;
}
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,47 @@
#pragma once
#include <vanetza/security/v3/certificate_provider.hpp>
#include <vanetza/security/v3/persistence.hpp>
namespace vanetza
{
namespace security
{
namespace v3
{
/**
* \brief A simple certificate provider
*
* This certificate provider uses a static certificate and key pair that is pre-generated.
*/
class StaticCertificateProvider : public BaseCertificateProvider
{
public:
/**
* Create static certificate provider with empty chain
* \param authorization_ticket
* \param ticket_key private key of given authorization ticket
*/
StaticCertificateProvider(const Certificate& authorization_ticket, const PrivateKey& ticket_key);
/**
* Get own certificate to use for signing
* \return own certificate
*/
virtual const Certificate& own_certificate() override;
/**
* Get private key associated with own certificate
* \return private key
*/
virtual const PrivateKey& own_private_key() override;
private:
Certificate authorization_ticket;
PrivateKey authorization_ticket_key;
std::list<Certificate> chain;
};
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,5 @@
include(UseGTest)
configure_gtest_directory(LINK_LIBRARIES geodesy security
COMPILE_DEFINITIONS ASSET_DIR="${SECURITY_TEST_ASSET_DIR}")
add_gtest(Distance distance.cpp)
add_gtest(DefaultCertificateValidatorV3 default_certificate_validator_v3.cpp)
@@ -0,0 +1,573 @@
#include <vanetza/common/byte_order.hpp>
#include <vanetza/common/manual_runtime.hpp>
#include <vanetza/common/stored_position_provider.hpp>
#include <vanetza/geodesy/country_database.hpp>
#include <vanetza/geonet/units.hpp>
#include <vanetza/security/v3/certificate_validator.hpp>
#include <vanetza/security/v3/issuer_memory_lookup.hpp>
#include <vanetza/security/v3/location_checker.hpp>
#include <vanetza/security/v3/naive_certificate_provider.hpp>
#include <vanetza/security/v3/revocation_lookup.hpp>
#include <vanetza/security/v3/trust_store.hpp>
#include <gtest/gtest.h>
#include <vanetza/asn1/support/OCTET_STRING.h>
#include <cstring>
using namespace vanetza;
using namespace vanetza::security;
using namespace vanetza::security::v3;
class DefaultCertificateValidatorTest : public ::testing::Test
{
public:
DefaultCertificateValidatorTest() :
runtime(Clock::at("2016-08-01 00:00")),
backend(create_backend("default")),
cert_provider(runtime)
{
cert_validator.use_runtime(&runtime);
cert_validator.use_issuer_lookup(&issuer_lookup);
PositionFix position_fix;
position_fix.latitude = 49.014420 * units::degree;
position_fix.longitude = 8.404417 * units::degree;
position_fix.confidence.semi_major = 25.0 * units::si::meter;
position_fix.confidence.semi_minor = 25.0 * units::si::meter;
assert(position_fix.confidence);
position_provider.position_fix(position_fix);
cert_validator.use_position_provider(&position_provider);
trust_store.insert(cert_provider.root_certificate());
issuer_lookup.insert(cert_provider.aa_certificate());
}
protected:
ManualRuntime runtime;
StoredPositionProvider position_provider;
std::unique_ptr<Backend> backend;
NaiveCertificateProvider cert_provider;
TrustStore trust_store;
IssuerMemoryLookup issuer_lookup;
DefaultCertificateValidator cert_validator;
};
TEST_F(DefaultCertificateValidatorTest, region_validator)
{
Certificate cert = cert_provider.generate_authorization_ticket();
cert_validator.disable_location_checks(true);
CertificateValidator::Verdict validity = cert_validator.valid_for_signing(cert, vanetza::aid::CA);
// Location checks are disabled
EXPECT_EQ(CertificateValidator::Verdict::Valid, validity);
cert_validator.disable_location_checks(false);
validity = cert_validator.valid_for_signing(cert, vanetza::aid::CA);
// No location checker was provided
EXPECT_EQ(CertificateValidator::Verdict::Misconfiguration, validity);
vanetza::security::v3::asn1::GeographicRegion* region = cert->toBeSigned.region;
if (!region) {
region = vanetza::asn1::allocate<vanetza::security::v3::asn1::GeographicRegion>();
cert->toBeSigned.region = region;
}
region->present = Vanetza_Security_GeographicRegion_PR_circularRegion;
region->choice.circularRegion.center.latitude = 12564.0;
region->choice.circularRegion.center.longitude = 654321.0;
region->choice.circularRegion.radius = 1337.0;
// Always fail
DenyLocationChecker denyLocationChecker;
cert_validator.use_location_checker(&denyLocationChecker);
validity = cert_validator.valid_for_signing(cert, vanetza::aid::CA);
EXPECT_EQ(CertificateValidator::Verdict::OutsideRegion, validity);
// Always success
AllowLocationChecker allowLocationChecker;
cert_validator.use_location_checker(&allowLocationChecker);
validity = cert_validator.valid_for_signing(cert, vanetza::aid::CA);
EXPECT_EQ(CertificateValidator::Verdict::Valid, validity);
// Actual check - invalid circular area
DefaultLocationChecker defaultLocationChecker;
cert_validator.use_location_checker(&defaultLocationChecker);
validity = cert_validator.valid_for_signing(cert, vanetza::aid::CA);
EXPECT_EQ(CertificateValidator::Verdict::OutsideRegion, validity);
// Valid circular area
vanetza::security::v3::asn1::CircularRegion& reg = region->choice.circularRegion;
reg.center.latitude = 490144200;
reg.center.longitude = 84044170;
reg.radius = 1000;
validity = cert_validator.valid_for_signing(cert, vanetza::aid::CA);
EXPECT_EQ(CertificateValidator::Verdict::Valid, validity);
// Invalid rectangular area
struct Vanetza_Security_RectangularRegion* rectReg = vanetza::asn1::allocate<Vanetza_Security_RectangularRegion_t>();
rectReg->northWest.longitude = 84044170;
rectReg->northWest.latitude = 490144200;
rectReg->southEast.longitude = 84044170;
rectReg->southEast.latitude = 490144200;
// Re-allocate the structure
vanetza::asn1::free(asn_DEF_Vanetza_Security_GeographicRegion, cert->toBeSigned.region);
region = vanetza::asn1::allocate<vanetza::security::v3::asn1::GeographicRegion>();
region->present = Vanetza_Security_GeographicRegion_PR_rectangularRegion;
cert->toBeSigned.region = region;
asn_sequence_add(&region->choice.rectangularRegion, rectReg);
validity = cert_validator.valid_for_signing(cert, vanetza::aid::CA);
EXPECT_EQ(CertificateValidator::Verdict::OutsideRegion, validity);
// Valid rectangular area
asn_sequence_empty(&region->choice.rectangularRegion);
rectReg->northWest.longitude = 83506870;
rectReg->northWest.latitude = 490464060;
rectReg->southEast.longitude = 84234710;
rectReg->southEast.latitude = 489982120;
asn_sequence_add(&region->choice.rectangularRegion, rectReg);
validity = cert_validator.valid_for_signing(cert, vanetza::aid::CA);
EXPECT_EQ(CertificateValidator::Verdict::Valid, validity);
// IdentifiedRegion — flag OFF (default conservative behaviour): OutsideRegion
{
DefaultLocationChecker strictChecker;
// permissive_identified_region_ defaults to false
cert_validator.use_location_checker(&strictChecker);
vanetza::asn1::free(asn_DEF_Vanetza_Security_GeographicRegion, cert->toBeSigned.region);
region = vanetza::asn1::allocate<vanetza::security::v3::asn1::GeographicRegion>();
region->present = Vanetza_Security_GeographicRegion_PR_identifiedRegion;
cert->toBeSigned.region = region;
validity = cert_validator.valid_for_signing(cert, vanetza::aid::CA);
EXPECT_EQ(CertificateValidator::Verdict::OutsideRegion, validity);
}
// IdentifiedRegion — flag ON (operator opt-in permissive fallback, issue #262): Valid
{
DefaultLocationChecker permissiveChecker;
permissiveChecker.set_permissive_identified_region(true);
cert_validator.use_location_checker(&permissiveChecker);
vanetza::asn1::free(asn_DEF_Vanetza_Security_GeographicRegion, cert->toBeSigned.region);
region = vanetza::asn1::allocate<vanetza::security::v3::asn1::GeographicRegion>();
region->present = Vanetza_Security_GeographicRegion_PR_identifiedRegion;
cert->toBeSigned.region = region;
validity = cert_validator.valid_for_signing(cert, vanetza::aid::CA);
EXPECT_EQ(CertificateValidator::Verdict::Valid, validity);
cert_validator.use_location_checker(&defaultLocationChecker);
}
}
namespace
{
template<vanetza::ByteOrder Order, typename T>
void append(std::vector<uint8_t>& buf, T v)
{
vanetza::EndianType<T, Order> e;
e = vanetza::host_cast(v);
auto raw = e.get();
const auto* p = reinterpret_cast<const uint8_t*>(&raw);
buf.insert(buf.end(), p, p + sizeof(raw));
}
void append_u16le(std::vector<uint8_t>& buf, uint16_t v)
{
append<vanetza::ByteOrder::LittleEndian>(buf, v);
}
void append_u32le(std::vector<uint8_t>& buf, uint32_t v)
{
append<vanetza::ByteOrder::LittleEndian>(buf, v);
}
void append_f64le(std::vector<uint8_t>& buf, double v)
{
uint64_t bits;
std::memcpy(&bits, &v, sizeof(bits));
append<vanetza::ByteOrder::LittleEndian>(buf, bits);
}
// Build a country data binary with Germany bounding box around Karlsruhe (test position: 49.014, 8.404)
std::vector<uint8_t> make_germany_data()
{
std::vector<uint8_t> wkb;
wkb.push_back(0x01); // LE
append_u32le(wkb, 3); // Polygon
append_u32le(wkb, 1); // 1 ring
append_u32le(wkb, 5); // 5 points (closed)
double ring[][2] = {{5.9, 47.3}, {15.0, 47.3}, {15.0, 55.1}, {5.9, 55.1}, {5.9, 47.3}};
for (const auto& p : ring) {
append_f64le(wkb, p[0]);
append_f64le(wkb, p[1]);
}
std::vector<uint8_t> data;
append_u16le(data, 1); // format version
append_u16le(data, 276); // Germany M.49
append_u32le(data, static_cast<uint32_t>(wkb.size()));
data.insert(data.end(), wkb.begin(), wkb.end());
return data;
}
void set_issuer_digest(Certificate& cert, const HashedId8& digest)
{
cert->issuer.present = Vanetza_Security_IssuerIdentifier_PR_sha256AndDigest;
OCTET_STRING_fromBuf(
&cert->issuer.choice.sha256AndDigest,
reinterpret_cast<const char*>(digest.data()),
digest.size()
);
}
} // anonymous namespace
TEST_F(DefaultCertificateValidatorTest, identified_region_country_only_with_database)
{
auto country_data = make_germany_data();
geodesy::CountryDatabase country_db;
ASSERT_TRUE(country_db.load(country_data.data(), country_data.size()));
Certificate cert = cert_provider.generate_authorization_ticket();
auto* region = vanetza::asn1::allocate<vanetza::security::v3::asn1::GeographicRegion>();
region->present = Vanetza_Security_GeographicRegion_PR_identifiedRegion;
cert->toBeSigned.region = region;
auto* id_region = vanetza::asn1::allocate<Vanetza_Security_IdentifiedRegion_t>();
id_region->present = Vanetza_Security_IdentifiedRegion_PR_countryOnly;
id_region->choice.countryOnly = 276;
asn_sequence_add(&region->choice.identifiedRegion, id_region);
// With database: position (49.014, 8.404) is inside Germany box
DefaultLocationChecker checker;
checker.use_country_database(&country_db);
cert_validator.use_location_checker(&checker);
auto validity = cert_validator.valid_for_signing(cert, vanetza::aid::CA);
EXPECT_EQ(CertificateValidator::Verdict::Valid, validity);
// Change country to France (250), not in database, permissive=false
id_region->choice.countryOnly = 250;
validity = cert_validator.valid_for_signing(cert, vanetza::aid::CA);
EXPECT_EQ(CertificateValidator::Verdict::OutsideRegion, validity);
// Same but with permissive=true (still reject with countryOnly)
checker.set_permissive_identified_region(true);
validity = cert_validator.valid_for_signing(cert, vanetza::aid::CA);
EXPECT_EQ(CertificateValidator::Verdict::OutsideRegion, validity);
}
TEST_F(DefaultCertificateValidatorTest, identified_region_without_database_permissive)
{
Certificate cert = cert_provider.generate_authorization_ticket();
auto* region = vanetza::asn1::allocate<vanetza::security::v3::asn1::GeographicRegion>();
region->present = Vanetza_Security_GeographicRegion_PR_identifiedRegion;
cert->toBeSigned.region = region;
auto* id_region = vanetza::asn1::allocate<Vanetza_Security_IdentifiedRegion_t>();
id_region->present = Vanetza_Security_IdentifiedRegion_PR_countryOnly;
id_region->choice.countryOnly = 276;
asn_sequence_add(&region->choice.identifiedRegion, id_region);
// No database, permissive=false
DefaultLocationChecker checker;
cert_validator.use_location_checker(&checker);
auto validity = cert_validator.valid_for_signing(cert, vanetza::aid::CA);
EXPECT_EQ(CertificateValidator::Verdict::OutsideRegion, validity);
// No database, permissive=true
checker.set_permissive_identified_region(true);
validity = cert_validator.valid_for_signing(cert, vanetza::aid::CA);
EXPECT_EQ(CertificateValidator::Verdict::Valid, validity);
}
TEST_F(DefaultCertificateValidatorTest, crl_not_attached_is_valid)
{
Certificate cert = cert_provider.generate_authorization_ticket();
cert_validator.disable_location_checks(true);
EXPECT_EQ(CertificateValidator::Verdict::Valid,
cert_validator.valid_for_signing(cert, vanetza::aid::CA));
}
TEST_F(DefaultCertificateValidatorTest, crl_empty_is_valid)
{
Certificate cert = cert_provider.generate_authorization_ticket();
cert_validator.disable_location_checks(true);
RevocationMemoryLookup revocation_lookup;
cert_validator.use_revocation_lookup(&revocation_lookup);
EXPECT_EQ(CertificateValidator::Verdict::Valid,
cert_validator.valid_for_signing(cert, vanetza::aid::CA));
}
TEST_F(DefaultCertificateValidatorTest, crl_at_revoked_by_aa)
{
Certificate cert = cert_provider.generate_authorization_ticket();
cert_validator.disable_location_checks(true);
RevocationMemoryLookup revocation_lookup;
cert_validator.use_revocation_lookup(&revocation_lookup);
auto aa_digest = cert_provider.aa_certificate().calculate_digest();
auto at_digest = cert.calculate_digest();
ASSERT_TRUE(aa_digest);
ASSERT_TRUE(at_digest);
revocation_lookup.revoke(*aa_digest, *at_digest);
EXPECT_EQ(CertificateValidator::Verdict::Revoked,
cert_validator.valid_for_signing(cert, vanetza::aid::CA));
}
TEST_F(DefaultCertificateValidatorTest, crl_aa_revoked_by_root)
{
Certificate cert = cert_provider.generate_authorization_ticket();
cert_validator.disable_location_checks(true);
// Make root reachable so the chain walk can step AT -> AA -> root.
ASSERT_TRUE(issuer_lookup.insert(cert_provider.root_certificate()));
RevocationMemoryLookup revocation_lookup;
cert_validator.use_revocation_lookup(&revocation_lookup);
auto root_digest = cert_provider.root_certificate().calculate_digest();
auto aa_digest = cert_provider.aa_certificate().calculate_digest();
ASSERT_TRUE(root_digest);
ASSERT_TRUE(aa_digest);
revocation_lookup.revoke(*root_digest, *aa_digest);
EXPECT_EQ(CertificateValidator::Verdict::Revoked,
cert_validator.valid_for_signing(cert, vanetza::aid::CA));
}
TEST_F(DefaultCertificateValidatorTest, crl_unrelated_entry_does_not_revoke)
{
Certificate cert = cert_provider.generate_authorization_ticket();
cert_validator.disable_location_checks(true);
RevocationMemoryLookup revocation_lookup;
cert_validator.use_revocation_lookup(&revocation_lookup);
HashedId8 other_issuer { { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 } };
HashedId8 other_cert { { 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11 } };
revocation_lookup.revoke(other_issuer, other_cert);
EXPECT_EQ(CertificateValidator::Verdict::Valid,
cert_validator.valid_for_signing(cert, vanetza::aid::CA));
}
TEST_F(DefaultCertificateValidatorTest, crl_expiry_reported_before_revocation)
{
Certificate cert = cert_provider.generate_authorization_ticket();
cert_validator.disable_location_checks(true);
RevocationMemoryLookup revocation_lookup;
cert_validator.use_revocation_lookup(&revocation_lookup);
auto aa_digest = cert_provider.aa_certificate().calculate_digest();
auto at_digest = cert.calculate_digest();
ASSERT_TRUE(aa_digest);
ASSERT_TRUE(at_digest);
revocation_lookup.revoke(*aa_digest, *at_digest);
// Expiry is evaluated before revocation: an expired AT reports Expired, not Revoked.
runtime.trigger(Clock::at("2099-01-01 00:00"));
EXPECT_EQ(CertificateValidator::Verdict::Expired,
cert_validator.valid_for_signing(cert, vanetza::aid::CA));
}
TEST_F(DefaultCertificateValidatorTest, identified_region_country_and_regions_fallback)
{
Certificate cert = cert_provider.generate_authorization_ticket();
auto* region = vanetza::asn1::allocate<vanetza::security::v3::asn1::GeographicRegion>();
region->present = Vanetza_Security_GeographicRegion_PR_identifiedRegion;
cert->toBeSigned.region = region;
auto* id_region = vanetza::asn1::allocate<Vanetza_Security_IdentifiedRegion_t>();
id_region->present = Vanetza_Security_IdentifiedRegion_PR_countryAndRegions;
id_region->choice.countryAndRegions.countryOnly = 276;
asn_sequence_add(&region->choice.identifiedRegion, id_region);
// countryAndRegions always falls back to permissive regardless of database
DefaultLocationChecker checker;
checker.set_permissive_identified_region(false);
cert_validator.use_location_checker(&checker);
auto validity = cert_validator.valid_for_signing(cert, vanetza::aid::CA);
EXPECT_EQ(CertificateValidator::Verdict::OutsideRegion, validity);
checker.set_permissive_identified_region(true);
validity = cert_validator.valid_for_signing(cert, vanetza::aid::CA);
EXPECT_EQ(CertificateValidator::Verdict::Valid, validity);
}
TEST_F(DefaultCertificateValidatorTest, trust_store_not_attached_is_valid)
{
Certificate cert = cert_provider.generate_authorization_ticket();
cert_validator.disable_location_checks(true);
// No use_trust_store() call: anchoring check is skipped, validator falls through to Valid.
EXPECT_EQ(CertificateValidator::Verdict::Valid,
cert_validator.valid_for_signing(cert, vanetza::aid::CA));
}
TEST_F(DefaultCertificateValidatorTest, anchored_chain_is_valid)
{
Certificate cert = cert_provider.generate_authorization_ticket();
cert_validator.disable_location_checks(true);
ASSERT_TRUE(issuer_lookup.insert(cert_provider.root_certificate()));
cert_validator.use_trust_store(&trust_store);
EXPECT_EQ(CertificateValidator::Verdict::Valid,
cert_validator.valid_for_signing(cert, vanetza::aid::CA));
}
TEST_F(DefaultCertificateValidatorTest, untrusted_when_root_not_in_trust_store)
{
Certificate cert = cert_provider.generate_authorization_ticket();
cert_validator.disable_location_checks(true);
ASSERT_TRUE(issuer_lookup.insert(cert_provider.root_certificate()));
TrustStore empty_trust_store;
cert_validator.use_trust_store(&empty_trust_store);
EXPECT_EQ(CertificateValidator::Verdict::Untrusted,
cert_validator.valid_for_signing(cert, vanetza::aid::CA));
}
TEST_F(DefaultCertificateValidatorTest, untrusted_when_chain_breaks)
{
// Root is in trust_store but NOT in issuer_lookup, so the chain walk hits an
// unknown issuer at the AA step and cannot reach the trusted root.
Certificate cert = cert_provider.generate_authorization_ticket();
cert_validator.disable_location_checks(true);
cert_validator.use_trust_store(&trust_store);
EXPECT_EQ(CertificateValidator::Verdict::Untrusted,
cert_validator.valid_for_signing(cert, vanetza::aid::CA));
}
TEST_F(DefaultCertificateValidatorTest, untrusted_reported_before_revocation)
{
Certificate cert = cert_provider.generate_authorization_ticket();
cert_validator.disable_location_checks(true);
cert_validator.use_trust_store(&trust_store);
RevocationMemoryLookup revocation_lookup;
cert_validator.use_revocation_lookup(&revocation_lookup);
auto aa_digest = cert_provider.aa_certificate().calculate_digest();
auto at_digest = cert.calculate_digest();
ASSERT_TRUE(aa_digest);
ASSERT_TRUE(at_digest);
revocation_lookup.revoke(*aa_digest, *at_digest);
// Untrusted is reported, not Revoked — anchoring check runs first.
EXPECT_EQ(CertificateValidator::Verdict::Untrusted,
cert_validator.valid_for_signing(cert, vanetza::aid::CA));
}
TEST_F(DefaultCertificateValidatorTest, consistency_rejects_subject_validity_outside_issuer_validity)
{
Certificate cert = cert_provider.generate_authorization_ticket();
cert->toBeSigned.validityPeriod.start = v2::convert_time32(runtime.now() - std::chrono::hours(2));
cert_validator.disable_location_checks(true);
EXPECT_EQ(CertificateValidator::Verdict::InconsistentChain,
cert_validator.valid_for_signing(cert, vanetza::aid::CA));
}
TEST_F(DefaultCertificateValidatorTest, consistency_rejects_permission_not_issued_by_parent)
{
Certificate cert = cert_provider.generate_authorization_ticket();
cert_validator.disable_location_checks(true);
EXPECT_EQ(CertificateValidator::Verdict::InconsistentChain,
cert_validator.valid_for_signing(cert, vanetza::aid::IPV6_ROUTING));
}
TEST_F(DefaultCertificateValidatorTest, consistency_accepts_permission_issued_by_parent_with_all_permissions)
{
Certificate aa = cert_provider.aa_certificate();
ASSERT_TRUE(aa->toBeSigned.certIssuePermissions);
ASSERT_GT(aa->toBeSigned.certIssuePermissions->list.count, 0);
ASSERT_TRUE(aa->toBeSigned.certIssuePermissions->list.array[0]);
auto& subject_permissions = aa->toBeSigned.certIssuePermissions->list.array[0]->subjectPermissions;
vanetza::asn1::reset(asn_DEF_Vanetza_Security_SubjectPermissions, &subject_permissions);
subject_permissions.present = Vanetza_Security_SubjectPermissions_PR_all;
subject_permissions.choice.all = 0;
auto aa_digest = aa.calculate_digest();
ASSERT_TRUE(aa_digest);
IssuerMemoryLookup local_issuer_lookup;
ASSERT_TRUE(local_issuer_lookup.insert(aa));
cert_validator.use_issuer_lookup(&local_issuer_lookup);
cert_validator.disable_location_checks(true);
Certificate cert = cert_provider.generate_authorization_ticket();
set_issuer_digest(cert, *aa_digest);
EXPECT_EQ(CertificateValidator::Verdict::Valid,
cert_validator.valid_for_signing(cert, vanetza::aid::IPV6_ROUTING));
}
TEST_F(DefaultCertificateValidatorTest, consistency_rejects_subject_assurance_without_issuer_assurance)
{
Certificate cert = cert_provider.generate_authorization_ticket();
cert->toBeSigned.assuranceLevel = vanetza::asn1::allocate<Vanetza_Security_SubjectAssurance_t>();
const char assurance = 0x20;
OCTET_STRING_fromBuf(cert->toBeSigned.assuranceLevel, &assurance, sizeof(assurance));
cert_validator.disable_location_checks(true);
EXPECT_EQ(CertificateValidator::Verdict::InconsistentChain,
cert_validator.valid_for_signing(cert, vanetza::aid::CA));
}
TEST_F(DefaultCertificateValidatorTest, consistency_can_be_disabled)
{
Certificate cert = cert_provider.generate_authorization_ticket();
cert->toBeSigned.assuranceLevel = vanetza::asn1::allocate<Vanetza_Security_SubjectAssurance_t>();
const char assurance = 0x20;
OCTET_STRING_fromBuf(cert->toBeSigned.assuranceLevel, &assurance, sizeof(assurance));
cert_validator.disable_location_checks(true);
EXPECT_EQ(CertificateValidator::Verdict::InconsistentChain,
cert_validator.valid_for_signing(cert, vanetza::aid::CA));
cert_validator.disable_chain_consistency_checks(true);
EXPECT_EQ(CertificateValidator::Verdict::Valid,
cert_validator.valid_for_signing(cert, vanetza::aid::CA));
}
TEST_F(DefaultCertificateValidatorTest, consistency_rejects_subject_region_outside_issuer_region)
{
Certificate aa = cert_provider.aa_certificate();
aa->toBeSigned.region = vanetza::asn1::allocate<vanetza::security::v3::asn1::GeographicRegion>();
aa->toBeSigned.region->present = Vanetza_Security_GeographicRegion_PR_circularRegion;
aa->toBeSigned.region->choice.circularRegion.center.latitude = 490144200;
aa->toBeSigned.region->choice.circularRegion.center.longitude = 84044170;
aa->toBeSigned.region->choice.circularRegion.radius = 500;
auto aa_digest = aa.calculate_digest();
ASSERT_TRUE(aa_digest);
IssuerMemoryLookup local_issuer_lookup;
ASSERT_TRUE(local_issuer_lookup.insert(aa));
cert_validator.use_issuer_lookup(&local_issuer_lookup);
cert_validator.disable_location_checks(true);
Certificate cert = cert_provider.generate_authorization_ticket();
set_issuer_digest(cert, *aa_digest);
cert->toBeSigned.region = vanetza::asn1::allocate<vanetza::security::v3::asn1::GeographicRegion>();
cert->toBeSigned.region->present = Vanetza_Security_GeographicRegion_PR_circularRegion;
cert->toBeSigned.region->choice.circularRegion.center.latitude = 490144200;
cert->toBeSigned.region->choice.circularRegion.center.longitude = 84044170;
cert->toBeSigned.region->choice.circularRegion.radius = 1000;
EXPECT_EQ(CertificateValidator::Verdict::InconsistentChain,
cert_validator.valid_for_signing(cert, vanetza::aid::CA));
cert_validator.disable_region_consistency_checks(true);
EXPECT_EQ(CertificateValidator::Verdict::Valid,
cert_validator.valid_for_signing(cert, vanetza::aid::CA));
cert_validator.disable_region_consistency_checks(false);
cert->toBeSigned.region->choice.circularRegion.radius = 100;
EXPECT_EQ(CertificateValidator::Verdict::Valid,
cert_validator.valid_for_signing(cert, vanetza::aid::CA));
}
@@ -0,0 +1,72 @@
#include <gtest/gtest.h>
#include <vanetza/asn1/security_profile.hpp>
#include VANETZA_ASN1_SECURITY_HEADER(Latitude.h)
#include VANETZA_ASN1_SECURITY_HEADER(Longitude.h)
#include VANETZA_ASN1_SECURITY_HEADER(TwoDLocation.h)
#include <vanetza/common/position_fix.hpp>
#include <vanetza/security/v3/distance.hpp>
#include <boost/units/cmath.hpp>
using namespace vanetza::security::v3;
namespace units = vanetza::units;
namespace boost {
namespace units {
void PrintTo(const vanetza::units::Length& l, std::ostream* out)
{
*out << l.value() << " m";
}
void PrintTo(const vanetza::units::GeoAngle& a, std::ostream* out)
{
*out << a.value() << " deg";
}
} // namespace units
} // namespace boost
#define EXPECT_ANGLE_EQ(a, b) { \
double a_value = units::GeoAngle { a }.value(); \
double b_value = units::GeoAngle { b }.value(); \
EXPECT_NEAR(a_value, b_value, 1e-6); \
}
#define EXPECT_LENGTH_EQ(a, b) { \
double a_value = units::Length { a }.value(); \
double b_value = units::Length { b }.value(); \
EXPECT_NEAR(a_value, b_value, 1.0); \
}
TEST(Distance, distance)
{
vanetza::PositionFix one;
one.latitude = 48.0 * units::degree;
one.longitude = 11.0 * units::degree;
asn1::TwoDLocation other;
other.latitude = 480000000;
other.longitude = 110000000;
EXPECT_LENGTH_EQ(distance(one, other), 0.0 * units::si::meter);
other.latitude = 481234000;
other.longitude = 109876000;
EXPECT_LENGTH_EQ(distance(one, other), 13752.4 * units::si::meter);
}
TEST(Distance, convert_latitude)
{
EXPECT_ANGLE_EQ(convert_latitude(Vanetza_Security_NinetyDegreeInt_min), -90.0 * units::degree);
EXPECT_ANGLE_EQ(convert_latitude(Vanetza_Security_NinetyDegreeInt_max), 90 * units::degree);
EXPECT_ANGLE_EQ(convert_latitude(0), 0 * units::degree);
EXPECT_FALSE(boost::units::isfinite(convert_latitude(Vanetza_Security_NinetyDegreeInt_unknown)));
}
TEST(Distance, convert_longitude)
{
EXPECT_ANGLE_EQ(convert_longitude(Vanetza_Security_OneEightyDegreeInt_min), -179.999999 * units::degree);
EXPECT_ANGLE_EQ(convert_longitude(Vanetza_Security_OneEightyDegreeInt_max), 180 * units::degree);
EXPECT_ANGLE_EQ(convert_longitude(0), 0 * units::degree);
EXPECT_FALSE(boost::units::isfinite(convert_longitude(Vanetza_Security_OneEightyDegreeInt_unknown)));
}
@@ -0,0 +1,39 @@
#include <vanetza/security/v3/trust_store.hpp>
#include <boost/optional.hpp>
#include <stdexcept>
namespace vanetza
{
namespace security
{
namespace v3
{
void TrustStore::insert(const Certificate& certificate)
{
if (!certificate.issuer_is_self()) {
throw std::runtime_error("Only root certificate authorities may be added to the trust store");
}
auto id = certificate.calculate_digest();
if (!id) {
throw std::runtime_error("Cannot calculate hash for certificate");
}
m_certificates.insert(std::make_pair(*id, certificate));
}
std::list<Certificate> TrustStore::lookup(HashedId8 id) const
{
using iterator = std::multimap<HashedId8, Certificate>::const_iterator;
std::pair<iterator, iterator> range = m_certificates.equal_range(id);
std::list<Certificate> matches;
for (auto item = range.first; item != range.second; ++item) {
matches.push_back(item->second);
}
return matches;
}
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,37 @@
#pragma once
#include <vanetza/security/hashed_id.hpp>
#include <vanetza/security/v3/certificate.hpp>
#include <list>
#include <map>
namespace vanetza
{
namespace security
{
namespace v3
{
class TrustStore
{
public:
/**
* Lookup certificates based on the passed HashedId8.
*
* \param id hash identifier of the certificate
* \return all stored certificates matching the passed identifier
*/
std::list<Certificate> lookup(HashedId8 id) const;
/**
* Insert a certificate into store, i.e. consider it as trustworthy.
* \param trusted_certificate a trustworthy certificate copied into TrustStore
*/
void insert(const Certificate& trusted_certificate);
protected:
std::multimap<HashedId8, Certificate> m_certificates;
};
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,18 @@
#include <vanetza/security/v3/validity_restriction.hpp>
namespace vanetza
{
namespace security
{
namespace v3
{
StartAndEndValidity::StartAndEndValidity(Time32 start, Time32 end) :
start_validity(start), end_validity(end)
{
}
} // namespace v3
} // namespace security
} // namespace vanetza
@@ -0,0 +1,24 @@
#pragma once
#include <cstdint>
namespace vanetza
{
namespace security
{
namespace v3
{
using Time32 = std::uint32_t;
struct StartAndEndValidity
{
StartAndEndValidity() = default;
StartAndEndValidity(Time32 start, Time32 end);
Time32 start_validity;
Time32 end_validity;
};
} // namespace v3
} // namespace security
} // namespace vanetza