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:
@@ -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(®ion), 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
|
||||
+50
@@ -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
|
||||
+261
@@ -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
|
||||
+97
@@ -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
|
||||
+36
@@ -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
|
||||
+34
@@ -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
|
||||
+305
@@ -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
|
||||
+102
@@ -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 */
|
||||
+136
@@ -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
|
||||
+29
@@ -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
|
||||
+47
@@ -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)
|
||||
Vendored
+573
@@ -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(®ion->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(®ion->choice.rectangularRegion);
|
||||
rectReg->northWest.longitude = 83506870;
|
||||
rectReg->northWest.latitude = 490464060;
|
||||
rectReg->southEast.longitude = 84234710;
|
||||
rectReg->southEast.latitude = 489982120;
|
||||
asn_sequence_add(®ion->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(®ion->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(®ion->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(®ion->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
|
||||
+18
@@ -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
|
||||
+24
@@ -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
|
||||
Reference in New Issue
Block a user